feat: 工具系统迁移 + 重型插件骨架 + 前端交互增强

- 工具系统从 kilostar/plugin/tool_plugin/ 迁移到 data/toolset/(manifest.json 声明式)
- 新增 plugin_runtime 模块:BaseOrganization / GlobalPluginManager / loader / tool_bridge
- 新增 org_task + org_task_event 表及 DAO(alembic 0009)
- 新增 /api/v1/plugin 路由(submit/status/stream/install/reload)
- 新增 data/plugin/example_dept 示例重型插件
- regulatory_node 支持聊天历史上下文注入
- send_file 改为 artifact 存盘 + SSE 推送下载链接
- 前端 WorkflowFileCard 组件 + ToolSettings README 渲染
- utils 整理:合并 access/role_check、standalone_proxy→ray_compat、删除废弃模块
- 项目结构文档移至 docs/STRUCTURE.md 并详细展开

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 05:20:00 +00:00
parent 9b73ae4db4
commit 6d658b4f4d
74 changed files with 2591 additions and 1308 deletions
+46 -6
View File
@@ -26,6 +26,40 @@ from kilostar.core.individual.regulatory_node.template import (
chat_router = APIRouter(prefix="/api/v1/chat", tags=["chat"])
# 单次注入历史的最大轮数(user+assistant 算一轮),防止 token 爆炸。
_HISTORY_MAX_TURNS = 20
def _build_message_history(rows) -> list:
"""把 DB 中的 ChatHistoryMessage 列表转成 pydantic-ai message_history 格式。
历史按时间升序,截取末尾最多 _HISTORY_MAX_TURNS*2 条;user 消息映射为
``ModelRequest(parts=[UserPromptPart])``assistant``regulatory_node``)映射为
``ModelResponse(parts=[TextPart])``。其它 owner 跳过。
"""
from pydantic_ai.messages import (
ModelRequest, ModelResponse, UserPromptPart, TextPart,
)
trimmed = rows[-(_HISTORY_MAX_TURNS * 2):]
history: list = []
for row in trimmed:
owner = row.message_owner
text = row.message
if not text:
continue
if owner == "user":
history.append(ModelRequest(parts=[UserPromptPart(content=text)]))
elif owner == "regulatory_node":
history.append(ModelResponse(parts=[TextPart(content=text)]))
return history
async def _load_message_history(chat_id: str) -> list:
postgres_database = ray_actor_hook("postgres_database").postgres_database
rows = await postgres_database.list_chat_messages.remote(chat_id=chat_id)
return _build_message_history(rows or [])
def _extract_reply(resp: MessageResponse | None) -> str | None:
"""从 RegulatoryNode.working 的输出里取出对用户的回复文本。
@@ -39,7 +73,7 @@ def _extract_reply(resp: MessageResponse | None) -> str | None:
async def _ask_regulatory(
*, user_id: str, chat_id: str, message: str
*, user_id: str, chat_id: str, message: str, message_history: list | None = None
) -> str | None:
"""统一封装 chat 入口对 RegulatoryNode 的调用。"""
regulatory_node = ray_actor_hook("regulatory_node").regulatory_node
@@ -49,7 +83,9 @@ async def _ask_regulatory(
platform_id=chat_id,
message=message,
)
resp: MessageResponse | None = await regulatory_node.working.remote(payload)
resp: MessageResponse | None = await regulatory_node.working.remote(
payload, message_history
)
return _extract_reply(resp)
@@ -120,7 +156,8 @@ async def send_chat_message(
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
# 存用户消息
# 先取历史(不含当前输入),再写入用户消息,避免历史里出现重复
message_history = await _load_message_history(chat_id)
await postgres_database.add_chat_message.remote(
chat_id=chat_id, message=request.message, message_owner="user"
)
@@ -130,6 +167,7 @@ async def send_chat_message(
user_id=token_data.user_id,
chat_id=chat_id,
message=request.message,
message_history=message_history,
)
# 存回复
@@ -164,10 +202,12 @@ async def stream_chat_message(
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""SSE 流式聊天端点:standalone 模式下逐 token 流式输出;distributed 模式 fallback 到整段回复。"""
from kilostar.utils.standalone_proxy import _STANDALONE
from kilostar.utils.ray_compat import _STANDALONE
postgres_database = ray_actor_hook("postgres_database").postgres_database
message_history = await _load_message_history(chat_id)
await postgres_database.add_chat_message.remote(
chat_id=chat_id, message=request_body.message, message_owner="user"
)
@@ -183,7 +223,7 @@ async def stream_chat_message(
if not _STANDALONE:
async def fallback_generator():
resp = await regulatory_node.working.remote(payload)
resp = await regulatory_node.working.remote(payload, message_history)
full_response = resp.reply_message if resp else ""
if full_response:
await postgres_database.add_chat_message.remote(
@@ -195,7 +235,7 @@ async def stream_chat_message(
return StreamingResponse(fallback_generator(), media_type="text/event-stream")
token_queue = asyncio.Queue()
stream_task = regulatory_node.stream_working.remote(payload, token_queue)
stream_task = regulatory_node.stream_working.remote(payload, token_queue, message_history)
async def event_generator():
full_response = ""