feat: v0.1.1 迭代——人设外键重构、Chat UI优化、意识节点防幻觉、日志双视图

1. 人设外键重构:persona_template 成为 system_prompt 唯一权威来源,
   agent/系统节点通过 persona_id FK 引用,含数据迁移脚本
2. Chat UI:去掉底部AI提示、加号改为弹出菜单、新建对话乐观跳转
3. 意识节点:无可用worker时禁止编造agent_id,只能自行完成或拒绝
4. 日志页面:双tab布局(系统日志 + 工作流日志列表选择)
5. 其他:SSE流式聊天、对话删除/重命名、standalone模式修复

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 06:18:47 +00:00
parent e3b8686d45
commit 6f1bc27101
39 changed files with 2904 additions and 524 deletions
+3 -2
View File
@@ -28,7 +28,7 @@ if not _STANDALONE:
from .agent import agent_router
from .auth import auth_router
from .system import system_router
from .system import system_router, system_api_router
from .platform.frontend import client_router
from .platform.onebot import onebot_router
from .provider import provider_router
@@ -93,7 +93,8 @@ async def request_id_middleware(request: Request, call_next):
response.headers["X-Request-Id"] = request_id
return response
app.include_router(system_router) # 健康探针 + 系统信息
app.include_router(system_router) # 健康探针
app.include_router(system_api_router) # 系统信息(/api/v1/system
app.include_router(client_router) # 客户端路径
app.include_router(onebot_router) # OneBot v11 路径
app.include_router(auth_router) # 用户路径
+19 -48
View File
@@ -36,7 +36,7 @@ class AgentRegister(BaseModel):
model_id: str
individual_name: str
tools: Optional[List[str]] = None
custom_system_prompt: Optional[str] = None
persona_id: Optional[str] = None
display_name: Optional[str] = None
@@ -79,13 +79,19 @@ async def load_agent(
agent_register.provider_title,
agent_register.model_id,
agent_register.tools,
agent_register.custom_system_prompt,
agent_register.persona_id,
agent_register.display_name,
)
scope = agent_register.individual_name
toolsets = await get_all_toolsets_for_scope(scope)
custom_prompt = agent_register.custom_system_prompt
# Resolve persona system_prompt from DB
persona_prompt = None
if agent_register.persona_id:
tpl = await postgres_database.get_template.remote(agent_register.persona_id)
if tpl:
persona_prompt = tpl.system_prompt
match scope:
case "regulatory_node":
@@ -97,7 +103,7 @@ async def load_agent(
agent_register.tools,
toolsets,
accept_lang,
custom_prompt,
persona_prompt,
)
case "consciousness_node":
node = ray_actor_hook("consciousness_node").consciousness_node
@@ -108,7 +114,7 @@ async def load_agent(
agent_register.tools,
toolsets,
accept_lang,
custom_prompt,
persona_prompt,
)
case _:
pass
@@ -130,7 +136,7 @@ class WorkerIndividualCreate(BaseModel):
description: str
provider_title: str
model_id: str
system_prompt: str
persona_id: str
output_template: dict
bound_skill: Dict[str, List[str]]
workspace: List[str]
@@ -153,7 +159,7 @@ class WorkerIndividualUpdate(BaseModel):
description: Optional[str] = None
provider_title: Optional[str] = None
model_id: Optional[str] = None
system_prompt: Optional[str] = None
persona_id: Optional[str] = None
output_template: Optional[dict] = None
bound_skill: Optional[Dict[str, List[str]]] = None
workspace: Optional[List[str]] = None
@@ -280,34 +286,21 @@ async def delete_worker_individual(
class PersonaTemplateCreate(BaseModel):
name: str
description: str = ""
system_prompt: str = ""
agent_type: AgentType = "ordinary"
provider_title: Optional[str] = None
model_id: Optional[str] = None
tools: Optional[List[str]] = None
tags: Optional[List[str]] = None
class PersonaTemplateUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
system_prompt: Optional[str] = None
agent_type: Optional[AgentType] = None
provider_title: Optional[str] = None
model_id: Optional[str] = None
tools: Optional[List[str]] = None
tags: Optional[List[str]] = None
@agent_router.get("/template")
async def list_templates(
include_builtin: bool = True,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
templates = await postgres_database.list_templates.remote(
owner_id=token_data.user_id, include_builtin=include_builtin
owner_id=token_data.user_id
)
return {"templates": templates}
@@ -319,7 +312,9 @@ async def create_template(
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
tpl = await postgres_database.add_template.remote(
**data.model_dump(), owner_id=token_data.user_id, is_builtin=False
name=data.name,
system_prompt=data.system_prompt,
owner_id=token_data.user_id,
)
return {"message": "success", "template_id": tpl.template_id}
@@ -334,7 +329,7 @@ async def update_template(
tpl = await postgres_database.get_template.remote(template_id)
if not tpl:
raise HTTPException(status_code=404, detail="Template not found")
if not tpl.is_builtin and tpl.owner_id != token_data.user_id:
if tpl.owner_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
updated = await postgres_database.update_template.remote(
template_id, **data.model_dump(exclude_unset=True)
@@ -351,31 +346,7 @@ async def delete_template(
tpl = await postgres_database.get_template.remote(template_id)
if not tpl:
raise HTTPException(status_code=404, detail="Template not found")
if tpl.is_builtin or tpl.owner_id != token_data.user_id:
if tpl.owner_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
await postgres_database.delete_template.remote(template_id)
return {"message": "success"}
@agent_router.post("/worker/from-template/{template_id}")
async def create_worker_from_template(
template_id: str,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""从人设模板快速创建一个 Worker Agent,字段直接从模板复制。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
tpl = await postgres_database.get_template.remote(template_id)
if not tpl:
raise HTTPException(status_code=404, detail="Template not found")
worker = await postgres_database.add_worker_individual.remote(
agent_name=tpl.name,
agent_type=tpl.agent_type,
description=tpl.description,
system_prompt=tpl.system_prompt,
provider_title=tpl.provider_title or "",
model_id=tpl.model_id or "",
tools=tpl.tools or [],
owner_id=token_data.user_id,
template_origin_id=template_id,
)
return {"message": "success", "agent_id": worker.agent_id}
+10 -3
View File
@@ -38,9 +38,16 @@ async def create_user(user_register: UserRegister, request: Request):
"""注册新用户:异步线程池里做 argon2 哈希,再交由 PostgresDatabase Actor 落库。"""
register_limiter.check(request)
postgres_database = ray_actor_hook("postgres_database").postgres_database
hashed_password = await run_in_threadpool(
Accessor.hash_password, user_register.password
)
try:
hashed_password = await run_in_threadpool(
Accessor.hash_password, user_register.password
)
except ValueError as e:
from fastapi.responses import JSONResponse
return JSONResponse(
status_code=400,
content={"code": "password_invalid", "message": str(e)},
)
user = await postgres_database.add_user.remote(
user_register.user_name, hashed_password
)
+119 -1
View File
@@ -12,7 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from fastapi import APIRouter, Depends
import json
import asyncio
import httpx
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.access import Accessor, TokenData
@@ -136,3 +140,117 @@ async def send_chat_message(
)
return {"reply": response_msg}
@chat_router.delete("/{chat_id}")
async def delete_chat_session(
chat_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
session = await postgres_database.get_chat_session.remote(chat_id=chat_id)
if not session:
raise HTTPException(status_code=404, detail="Chat session not found")
if session.user_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
await postgres_database.delete_chat_session.remote(chat_id=chat_id)
return {"message": "success"}
@chat_router.post("/{chat_id}/stream")
async def stream_chat_message(
chat_id: str,
request_body: SendMessageRequest,
request: Request,
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""SSE 流式聊天端点:逐 token 推送 AI 回复。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
# 存用户消息
await postgres_database.add_chat_message.remote(
chat_id=chat_id, message=request_body.message, message_owner="user"
)
# 获取 regulatory_node 的 provider 配置
node_config = await postgres_database.get_system_node_config.remote("regulatory_node")
if not node_config:
raise HTTPException(status_code=500, detail="Regulatory node not configured")
# 获取 provider 详情
from kilostar.core.global_state_machine.gsm_snapshot import fetch_snapshot
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
snapshot = await fetch_snapshot(gsm_actor=global_state_machine)
provider = snapshot.providers.get(node_config.provider_title)
if not provider:
raise HTTPException(status_code=500, detail="Provider not available")
# 加载历史消息作为上下文
history_msgs = await postgres_database.list_chat_messages.remote(chat_id=chat_id)
messages = []
system_prompt = "你是 KiloStar 助手,友善、简洁地回答用户的问题。"
if node_config.persona_id:
tpl = await postgres_database.get_template.remote(node_config.persona_id)
if tpl and tpl.system_prompt:
system_prompt += "\n" + tpl.system_prompt
messages.append({"role": "system", "content": system_prompt})
for msg in history_msgs:
role = "user" if msg.message_owner == "user" else "assistant"
messages.append({"role": role, "content": msg.message})
async def event_generator():
full_response = ""
try:
async with httpx.AsyncClient(timeout=120.0) as client:
url = provider.provider_url.rstrip("/") + "/chat/completions"
payload = {
"model": node_config.model_id,
"messages": messages,
"stream": True,
}
async with client.stream(
"POST",
url,
json=payload,
headers={
"Authorization": f"Bearer {provider.provider_apikey}",
"Content-Type": "application/json",
},
) as resp:
async for line in resp.aiter_lines():
if await request.is_disconnected():
break
if not line.startswith("data: "):
continue
data_str = line[6:]
if data_str.strip() == "[DONE]":
break
try:
chunk = json.loads(data_str)
delta = chunk.get("choices", [{}])[0].get("delta", {})
token = delta.get("content", "")
if token:
full_response += token
yield f"data: {json.dumps({'token': token})}\n\n"
except (json.JSONDecodeError, IndexError, KeyError):
continue
except Exception as e:
from kilostar.utils.logger import get_logger
get_logger("chat_stream").exception(f"Stream error: {e}")
if not full_response:
full_response = "抱歉,生成回复时出错。"
yield f"data: {json.dumps({'token': full_response})}\n\n"
# 流结束,存入数据库
if full_response:
await postgres_database.add_chat_message.remote(
chat_id=chat_id,
message=full_response,
message_owner="regulatory_node",
)
yield f"data: {json.dumps({'done': True, 'full_message': full_response})}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
+7 -8
View File
@@ -35,6 +35,7 @@ from kilostar.utils.config_loader import (
)
system_router = APIRouter(tags=["system"])
system_api_router = APIRouter(prefix="/api/v1/system", tags=["system"])
@system_router.get("/health/live", include_in_schema=True)
@@ -69,15 +70,15 @@ async def readiness():
)
@system_router.get("/config/workflow")
@system_api_router.get("/config/workflow")
async def get_workflow_config_endpoint(
_: TokenData = Depends(Accessor.get_current_user),
):
config = get_workflow_config()
return {"config": config.model_dump()}
return config.model_dump()
@system_router.put("/config/workflow")
@system_api_router.put("/config/workflow")
async def update_workflow_config_endpoint(
update: WorkflowConfig,
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
@@ -86,7 +87,7 @@ async def update_workflow_config_endpoint(
return {"status": "ok", "config": update.model_dump()}
@system_router.get("/logs")
@system_api_router.get("/logs")
async def query_system_logs(
trace_id: str | None = None,
event_type: str | None = None,
@@ -95,9 +96,7 @@ async def query_system_logs(
offset: int = 0,
_: TokenData = Depends(Accessor.get_current_user),
):
from kilostar.utils.ray_hook import ray_actor_hook
pg = await ray_actor_hook.get_actor("postgres_database")
pg = ray_actor_hook("postgres_database").postgres_database
logs = await pg.query_event_logs.remote(
trace_id=trace_id,
event_type=event_type,
@@ -108,7 +107,7 @@ async def query_system_logs(
return {"logs": logs, "count": len(logs)}
@system_router.get("/api/v1/system/node-labels")
@system_api_router.get("/node-labels")
async def get_node_labels(
_: TokenData = Depends(Accessor.get_current_user),
):
@@ -122,8 +122,7 @@ class ProviderManager:
async def delete_provider(self, provider_title: str, postgres_database) -> None:
"""从内存注册表 + Postgres 中一并删除指定 Provider;不存在时静默返回。"""
if provider_title in self.provider_register:
provider = self.provider_register[provider_title]
await postgres_database.delete_provider_db.remote(
provider_id=provider.provider_id
await postgres_database.delete_provider_by_title.remote(
provider_title=provider_title
)
del self.provider_register[provider_title]
@@ -87,6 +87,12 @@ class ConsciousnessNode:
prompt += "你可以直接将以下 Skill Individual 安排进工作流的步骤中(设置 node 为 skill_individual,并将 agent_id 设置为对应 Skill Individual 的真实 agent_id,不要用名称!),作为可调用的工具。\n"
for skill in ctx.deps.available_skills:
prompt += f"- 真实 agent_id: {skill.get('agent_id')}\n 名称: {skill['name']}\n 描述: {skill['description']}\n"
else:
prompt += "\n=== 重要:当前无可用 Worker Individual ===\n"
prompt += "系统中当前没有注册任何 Worker Individual。在生成工作流时,你有且仅有以下两种选择:\n"
prompt += "1. 将步骤分配给 consciousness_node 自己完成(设置 node 为 consciousness_nodeagent_id 为 null)。\n"
prompt += "2. 如果任务确实需要专用工具或技能才能完成,则拒绝执行并在输出中说明需要先创建对应的 Worker。\n"
prompt += "绝对禁止编造不存在的 agent_id!\n"
return prompt
@@ -37,14 +37,13 @@ class BaseIndividualModel(BaseDataModel):
agent_id: Mapped[str] = mapped_column(String(64), primary_key=True)
agent_name: Mapped[str] = mapped_column(String(100), index=True, nullable=False)
description: Mapped[str] = mapped_column(Text, nullable=False)
system_prompt: Mapped[Optional[str]] = mapped_column(Text)
provider_title: Mapped[str] = mapped_column(String(50))
model_id: Mapped[str] = mapped_column(String(100))
owner_id: Mapped[str] = mapped_column(String(64), index=True)
agent_type: Mapped[str] = mapped_column(String(32))
node_affinity: Mapped[str] = mapped_column(String(32), nullable=False, default="cpu")
template_origin_id: Mapped[Optional[str]] = mapped_column(
persona_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("persona_template.template_id", ondelete="SET NULL"),
nullable=True,
index=True,
@@ -1,6 +1,5 @@
from typing import List, Optional
from sqlalchemy import String, Text, Boolean, text
from sqlalchemy.dialects.postgresql import JSONB
from typing import Optional
from sqlalchemy import String, Text
from sqlalchemy.orm import Mapped, mapped_column
from .base import BaseDataModel
@@ -11,16 +10,5 @@ class PersonaTemplate(BaseDataModel):
template_id: Mapped[str] = mapped_column(String(64), primary_key=True)
name: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
system_prompt: Mapped[str] = mapped_column(Text, nullable=False, default="")
agent_type: Mapped[str] = mapped_column(String(32), nullable=False, default="ordinary")
provider_title: Mapped[Optional[str]] = mapped_column(String(50))
model_id: Mapped[Optional[str]] = mapped_column(String(100))
tools: Mapped[Optional[List[str]]] = mapped_column(
JSONB, default=list, server_default=text("'[]'::jsonb")
)
tags: Mapped[Optional[List[str]]] = mapped_column(
JSONB, default=list, server_default=text("'[]'::jsonb")
)
is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
owner_id: Mapped[Optional[str]] = mapped_column(String(64), index=True)
@@ -13,7 +13,7 @@
# limitations under the License.
from typing import List, Optional
from sqlalchemy import String, Text
from sqlalchemy import String, ForeignKey
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from .base import BaseDataModel
@@ -35,6 +35,8 @@ class SystemNodeConfigModel(BaseDataModel):
tools: Mapped[Optional[List[str]]] = mapped_column(
JSONB, default=list, comment="节点可调用的工具标识列表"
)
custom_system_prompt: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="管理员自定义追加的提示词,拼接在默认 system prompt 之后"
persona_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("persona_template.template_id", ondelete="SET NULL"),
nullable=True,
index=True,
)
@@ -85,3 +85,29 @@ class ChatHistoryDatabase:
)
results = await session.execute(statement)
return results.scalars().all()
@database_exception
async def get_chat_session(self, chat_id: str) -> ChatHistoryRegister | None:
async with self.async_session_maker() as session:
statement = select(ChatHistoryRegister).where(
ChatHistoryRegister.chat_id == chat_id
)
results = await session.execute(statement)
return results.scalar_one_or_none()
@database_exception
async def delete_chat_session(self, chat_id: str) -> None:
from sqlalchemy import delete as sa_delete
async with self.async_session_maker() as session:
await session.execute(
sa_delete(ChatHistoryMessage).where(
ChatHistoryMessage.chat_id == chat_id
)
)
await session.execute(
sa_delete(ChatHistoryRegister).where(
ChatHistoryRegister.chat_id == chat_id
)
)
await session.commit()
@@ -27,18 +27,11 @@ class PersonaTemplateDatabase:
return result.scalar_one_or_none()
@database_exception
async def list_templates(self, owner_id: str = None, include_builtin: bool = True):
async def list_templates(self, owner_id: str = None, **kwargs):
async with self.async_session_maker() as session:
stmt = select(PersonaTemplate)
if owner_id and include_builtin:
from sqlalchemy import or_
stmt = stmt.where(
or_(PersonaTemplate.owner_id == owner_id, PersonaTemplate.is_builtin == True)
)
elif owner_id:
if owner_id:
stmt = stmt.where(PersonaTemplate.owner_id == owner_id)
elif include_builtin:
stmt = stmt.where(PersonaTemplate.is_builtin == True)
result = await session.execute(stmt)
return list(result.scalars().all())
@@ -93,6 +93,19 @@ class ProviderDatabase:
session.delete(provider)
await session.commit()
@database_exception
async def delete_provider_by_title(self, provider_title: str) -> None:
"""按 provider_title 删除 Provider;不存在时静默返回。"""
async with self.async_session_maker() as session:
statement = select(ProviderModel).where(
ProviderModel.provider_title == provider_title
)
result = await session.execute(statement)
provider = result.scalar_one_or_none()
if provider is not None:
await session.delete(provider)
await session.commit()
@database_exception
async def update_provider(self, provider_id: str, **kwargs) -> None:
"""部分更新指定 Provider 的字段;``provider_apikey`` 写入前自动加密。"""
@@ -31,7 +31,7 @@ class SystemNodeDatabase:
provider_title: str,
model_id: str,
tools: Optional[List[str]] = None,
custom_system_prompt: Optional[str] = None,
persona_id: Optional[str] = None,
display_name: Optional[str] = None,
) -> SystemNodeConfigModel:
"""按 node_name 插入或更新一个系统节点的模型配置。"""
@@ -46,8 +46,8 @@ class SystemNodeDatabase:
config.model_id = model_id
if tools is not None:
config.tools = tools
if custom_system_prompt is not None:
config.custom_system_prompt = custom_system_prompt
if persona_id is not None:
config.persona_id = persona_id
if display_name is not None:
config.display_name = display_name
else:
@@ -56,7 +56,7 @@ class SystemNodeDatabase:
provider_title=provider_title,
model_id=model_id,
tools=tools,
custom_system_prompt=custom_system_prompt,
persona_id=persona_id,
display_name=display_name,
)
session.add(config)
+24 -4
View File
@@ -171,6 +171,11 @@ class PostgresDatabase:
await self.ready_event.wait()
return await self._provider_database.delete_provider(provider_id)
async def delete_provider_by_title(self, provider_title: str):
"""按 provider_title 删除模型 Provider 记录。"""
await self.ready_event.wait()
return await self._provider_database.delete_provider_by_title(provider_title)
async def update_provider_db(self, provider_id: str, **kwargs):
"""部分更新指定 Provider 的字段。"""
await self.ready_event.wait()
@@ -183,13 +188,13 @@ class PostgresDatabase:
provider_title: str,
model_id: str,
tools: list[str] = None,
custom_system_prompt: str = None,
persona_id: str = None,
display_name: str = None,
):
"""插入或更新某个系统节点(如 consciousness/regulatory)的模型配置。"""
await self.ready_event.wait()
return await self._system_node_database.upsert_system_node_config(
node_name, provider_title, model_id, tools, custom_system_prompt, display_name
node_name, provider_title, model_id, tools, persona_id, display_name
)
async def get_all_system_node_configs(self):
@@ -197,6 +202,11 @@ class PostgresDatabase:
await self.ready_event.wait()
return await self._system_node_database.get_all_system_node_configs()
async def get_system_node_config(self, node_name: str):
"""按 node_name 取出单个系统节点的模型配置。"""
await self.ready_event.wait()
return await self._system_node_database.get_system_node_config(node_name)
# Individual Database Methods
async def add_worker_individual(self, **kwargs):
"""登记一个新的 Worker Individual 配置。"""
@@ -306,6 +316,16 @@ class PostgresDatabase:
await self.ready_event.wait()
return await self._chat_history_database.list_chat_messages(chat_id)
async def get_chat_session(self, chat_id: str):
"""按 chat_id 取出单个聊天会话。"""
await self.ready_event.wait()
return await self._chat_history_database.get_chat_session(chat_id)
async def delete_chat_session(self, chat_id: str):
"""删除聊天会话及其所有消息。"""
await self.ready_event.wait()
return await self._chat_history_database.delete_chat_session(chat_id)
# MCP Server Database Methods
async def upsert_mcp_server(self, server_id: str, config: dict):
"""插入或更新一条 MCP 服务器配置;env 中敏感字段自动加密。"""
@@ -423,9 +443,9 @@ class PostgresDatabase:
await self.ready_event.wait()
return await self._persona_template_database.get_template(template_id)
async def list_templates(self, owner_id: str = None, include_builtin: bool = True):
async def list_templates(self, owner_id: str = None, **kwargs):
await self.ready_event.wait()
return await self._persona_template_database.list_templates(owner_id, include_builtin)
return await self._persona_template_database.list_templates(owner_id=owner_id)
async def update_template(self, template_id: str, **kwargs):
await self.ready_event.wait()
+5 -6
View File
@@ -167,11 +167,10 @@ class Accessor:
"""对明文口令做强哈希;空值或不满足复杂度要求会抛 ValueError。"""
if not password:
raise ValueError("密码不能为空")
if len(password) < 8:
raise ValueError("密码长度不能小于 8")
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
if len(password) < 6:
raise ValueError("密码长度不能小于 6")
has_alpha = any(c.isalpha() for c in password)
has_digit = any(c.isdigit() for c in password)
if not (has_upper and has_lower and has_digit):
raise ValueError("密码必须包含大写字母、小写字母和数字")
if not (has_alpha and has_digit):
raise ValueError("密码必须同时包含字母和数字")
return password_hasher.hash(password)
+6 -3
View File
@@ -104,7 +104,7 @@ class WorkerCluster:
if self.task_queue is None:
await asyncio.sleep(0.1)
continue
task = await self.task_queue.get_async()
task = await self.task_queue.get() if _STANDALONE else await self.task_queue.get_async()
task_id = task.get("task_id")
agent_id = task.get("agent_id")
task_event = task.get("task_event")
@@ -150,7 +150,10 @@ class WorkerCluster:
self.results_futures[task_id] = future
task = {"task_id": task_id, "agent_id": agent_id, "task_event": task_event}
await self.task_queue.put_async(task)
if _STANDALONE:
await self.task_queue.put(task)
else:
await self.task_queue.put_async(task)
self.logger.debug(f"[WorkerCluster] 任务 {task_id} 已加入队列。")
try:
@@ -165,5 +168,5 @@ class WorkerCluster:
"active_worker_count": len(self._active_workers),
"max_capacity": self.max_capacity,
"cached_agent_ids": list(self._active_workers.keys()),
"queue_size": self.task_queue.size(),
"queue_size": self.task_queue.qsize() if _STANDALONE else self.task_queue.size(),
}