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:
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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_node,agent_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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user