Update agent orchestration and knowledge flow

Add sub-commander orchestration updates, align frontend integrations, and refine knowledge view behavior without including local data artifacts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 21:44:04 +08:00
parent aafa05dc1c
commit 0d89325b09
14 changed files with 529 additions and 650 deletions

View File

@@ -11,8 +11,16 @@ from app.agents.prompts import (
EXECUTOR_SYSTEM_PROMPT,
LIBRARIAN_SYSTEM_PROMPT,
ANALYST_SYSTEM_PROMPT,
PLANNER_SCOPE_PROMPT,
PLANNER_STEPS_PROMPT,
EXECUTOR_TASKS_PROMPT,
EXECUTOR_FORUM_PROMPT,
LIBRARIAN_RETRIEVAL_PROMPT,
LIBRARIAN_GRAPH_PROMPT,
ANALYST_PROGRESS_PROMPT,
ANALYST_INSIGHTS_PROMPT,
)
from app.agents.tools import ALL_TOOLS
from app.agents.tools import ALL_TOOLS, SUB_COMMANDER_TOOLSETS
from app.agents.skill_registry import build_skill_context
from app.services.llm_service import get_llm
from langchain_openai import ChatOpenAI
@@ -21,6 +29,32 @@ from langchain_ollama import ChatOllama
import httpx
SUB_COMMANDER_PROMPTS = {
"planner_scope": PLANNER_SCOPE_PROMPT,
"planner_steps": PLANNER_STEPS_PROMPT,
"executor_tasks": EXECUTOR_TASKS_PROMPT,
"executor_forum": EXECUTOR_FORUM_PROMPT,
"librarian_retrieval": LIBRARIAN_RETRIEVAL_PROMPT,
"librarian_graph": LIBRARIAN_GRAPH_PROMPT,
"analyst_progress": ANALYST_PROGRESS_PROMPT,
"analyst_insights": ANALYST_INSIGHTS_PROMPT,
}
ROLE_SUB_COMMANDERS = {
AgentRole.PLANNER: ["planner_scope", "planner_steps"],
AgentRole.EXECUTOR: ["executor_tasks", "executor_forum"],
AgentRole.LIBRARIAN: ["librarian_retrieval", "librarian_graph"],
AgentRole.ANALYST: ["analyst_progress", "analyst_insights"],
}
ROLE_SKILL_CONTEXT = {
AgentRole.PLANNER: "planner",
AgentRole.EXECUTOR: "executor",
AgentRole.LIBRARIAN: "librarian",
AgentRole.ANALYST: "analyst",
}
def _create_llm_from_config(config: dict):
"""根据用户模型配置创建 LLM 实例"""
provider = config.get("provider", "openai")
@@ -71,8 +105,9 @@ async def _ainvoke(llm, messages: list[BaseMessage]):
return await llm.invoke(messages)
async def _ainvoke_with_tools(llm, messages: list[BaseMessage]):
bound_llm = llm.bind_tools(ALL_TOOLS)
async def _ainvoke_with_tools(llm, messages: list[BaseMessage], tools=None):
toolset = tools if tools is not None else ALL_TOOLS
bound_llm = llm.bind_tools(toolset)
if hasattr(bound_llm, "ainvoke"):
return await bound_llm.ainvoke(messages)
return await bound_llm.invoke(messages)
@@ -116,6 +151,113 @@ def _is_capability_question(text: str) -> bool:
return normalized in {"你能做什么", "你可以做什么", "你会做什么"}
def _choose_sub_commander(role: AgentRole, user_query: str) -> str:
text = _normalize_user_text(user_query)
if role == AgentRole.PLANNER:
if any(keyword in text for keyword in ["步骤", "计划", "拆解", "排期", "优先级", "路线"]):
return "planner_steps"
return "planner_scope"
if role == AgentRole.EXECUTOR:
if any(keyword in text for keyword in ["论坛", "帖子", "发帖", "指令", "discussion", "instruction"]):
return "executor_forum"
return "executor_tasks"
if role == AgentRole.LIBRARIAN:
if any(keyword in text for keyword in ["图谱", "关系", "构建", "沉淀", "节点", "graph"]):
return "librarian_graph"
return "librarian_retrieval"
if role == AgentRole.ANALYST:
if any(keyword in text for keyword in ["趋势", "风险", "洞察", "建议", "机会", "insight"]):
return "analyst_insights"
return "analyst_progress"
return ROLE_SUB_COMMANDERS[role][0]
def _record_sub_commander(state: AgentState, sub_commander: str, user_query: str):
state["current_sub_commander"] = sub_commander
state["active_sub_commanders"] = state.get("active_sub_commanders", []) + [sub_commander]
state["sub_commander_trace"] = state.get("sub_commander_trace", []) + [{
"agent": state.get("current_agent", AgentRole.MASTER).value,
"sub_commander": sub_commander,
"query": user_query,
}]
def _build_system_messages(state: AgentState, system_prompt: str, role: AgentRole):
system_msgs: list[BaseMessage] = [SystemMessage(content=system_prompt)]
skill_ctx = build_skill_context(ROLE_SKILL_CONTEXT[role])
if skill_ctx:
system_msgs.append(SystemMessage(content=skill_ctx))
return system_msgs
async def _run_sub_commander(
state: AgentState,
role: AgentRole,
manager_prompt: str,
user_query: str,
*,
use_tools: bool,
summary_target: str | None = None,
):
llm = _get_llm_for_state(state)
sub_commander = _choose_sub_commander(role, user_query)
_record_sub_commander(state, sub_commander, user_query)
toolset = SUB_COMMANDER_TOOLSETS.get(sub_commander, [])
system_msgs = _build_system_messages(state, manager_prompt, role)
system_msgs.append(SystemMessage(content=f"本次应由子指挥官 `{sub_commander}` 接手。请严格按该角色职责输出。"))
system_msgs.append(SystemMessage(content=SUB_COMMANDER_PROMPTS[sub_commander]))
if use_tools and toolset:
response = await _ainvoke_with_tools(
llm,
system_msgs + [HumanMessage(content=f"用户请求: {user_query}")],
toolset,
)
tool_calls = getattr(response, "tool_calls", None) or []
if tool_calls:
results = []
for tc in tool_calls:
tool_name = tc.get("name")
args = tc.get("args", {})
for tool in toolset:
if tool.name == tool_name:
try:
result = tool.invoke(args)
results.append(f"[{tool_name}] {result}")
except Exception as e:
results.append(f"[{tool_name}] 执行失败: {e}")
break
state["tool_calls"] = tool_calls
state["last_tool_result"] = "\n".join(results)
follow_up = await _ainvoke(
llm,
[
SystemMessage(content=SUB_COMMANDER_PROMPTS[sub_commander]),
HumanMessage(content=f"工具执行结果:\n{state['last_tool_result']}")
]
)
state["final_response"] = follow_up.content
else:
state["final_response"] = response.content
else:
response = await _ainvoke(
llm,
system_msgs + [HumanMessage(content=f"用户请求: {user_query}")],
)
state["final_response"] = response.content
if summary_target:
state[summary_target] = state.get("final_response", "")
state["should_respond"] = True
return state
# ===================== 节点定义 (async) =====================
async def master_node(state: AgentState) -> AgentState:
@@ -142,14 +284,13 @@ async def master_node(state: AgentState) -> AgentState:
llm = _get_llm_for_state(state)
system_msgs: list[BaseMessage] = [SystemMessage(content=MASTER_SYSTEM_PROMPT)]
# 注入记忆上下文
memory_ctx = state.get("memory_context")
if memory_ctx:
system_msgs.append(
SystemMessage(content=f"\n\n【记忆上下文】\n{memory_ctx}\n\n---\n")
)
response: AIMessage = await _ainvoke(llm,system_msgs + messages)
response: AIMessage = await _ainvoke(llm, system_msgs + messages)
content = response.content.strip().lower()
if any(kw in content for kw in ["搜索", "查找", "知识", "检索"]):
@@ -166,6 +307,7 @@ async def master_node(state: AgentState) -> AgentState:
return state
state["current_agent"] = next_agent
state["current_sub_commander"] = None
state["active_agents"] = state.get("active_agents", [AgentRole.MASTER]) + [next_agent]
state["should_respond"] = True
return state
@@ -173,164 +315,30 @@ async def master_node(state: AgentState) -> AgentState:
async def planner_node(state: AgentState) -> AgentState:
"""规划Agent节点: 制定计划,拆解任务步骤"""
llm = _get_llm_for_state(state)
user_msgs = _filter_user_messages(state["messages"])
user_query = user_msgs[-1].content if user_msgs else ""
system_msgs = [SystemMessage(content=PLANNER_SYSTEM_PROMPT)]
skill_ctx = build_skill_context("planner")
if skill_ctx:
system_msgs.append(SystemMessage(content=skill_ctx))
response = await _ainvoke(llm,
system_msgs + [HumanMessage(content=f"用户请求: {user_query}")]
)
plan_text = response.content
steps = []
for i, line in enumerate(plan_text.split("\n")):
if line.strip() and (line[0].isdigit() or "- " in line):
steps.append({"step": i + 1, "description": line.strip()})
state["plan"] = plan_text
state["plan_steps"] = steps
state["final_response"] = plan_text
state["should_respond"] = True
return state
return await _run_sub_commander(state, AgentRole.PLANNER, PLANNER_SYSTEM_PROMPT, user_query, use_tools=False)
async def executor_node(state: AgentState) -> AgentState:
"""执行Agent节点: 调用工具执行具体任务"""
llm = _get_llm_for_state(state)
user_msgs = _filter_user_messages(state["messages"])
user_query = user_msgs[-1].content if user_msgs else ""
system_msgs = [SystemMessage(content=EXECUTOR_SYSTEM_PROMPT)]
skill_ctx = build_skill_context("executor")
if skill_ctx:
system_msgs.append(SystemMessage(content=skill_ctx))
response = await _ainvoke_with_tools(llm,
system_msgs + [HumanMessage(content=f"用户请求: {user_query}")]
)
tool_calls = getattr(response, "tool_calls", None) or []
if tool_calls:
results = []
for tc in tool_calls:
tool_name = tc.get("name")
args = tc.get("args", {})
for tool in ALL_TOOLS:
if tool.name == tool_name:
try:
result = tool.invoke(args)
results.append(f"[{tool_name}] {result}")
except Exception as e:
results.append(f"[{tool_name}] 执行失败: {e}")
break
state["tool_calls"] = tool_calls
state["last_tool_result"] = "\n".join(results)
follow_up = await _ainvoke(llm,
[SystemMessage(content=EXECUTOR_SYSTEM_PROMPT),
HumanMessage(content=f"工具执行结果:\n{state['last_tool_result']}")]
)
state["final_response"] = follow_up.content
else:
state["final_response"] = response.content
state["should_respond"] = True
return state
return await _run_sub_commander(state, AgentRole.EXECUTOR, EXECUTOR_SYSTEM_PROMPT, user_query, use_tools=True)
async def librarian_node(state: AgentState) -> AgentState:
"""知识管理员节点: 管理知识库和知识图谱"""
llm = _get_llm_for_state(state)
user_msgs = _filter_user_messages(state["messages"])
user_query = user_msgs[-1].content if user_msgs else ""
system_msgs = [SystemMessage(content=LIBRARIAN_SYSTEM_PROMPT)]
skill_ctx = build_skill_context("librarian")
if skill_ctx:
system_msgs.append(SystemMessage(content=skill_ctx))
response = await _ainvoke_with_tools(llm,
system_msgs + [HumanMessage(content=f"用户请求: {user_query}")]
)
tool_calls = getattr(response, "tool_calls", None) or []
if tool_calls:
results = []
for tc in tool_calls:
tool_name = tc.get("name")
args = tc.get("args", {})
for tool in ALL_TOOLS:
if tool.name == tool_name:
try:
result = tool.invoke(args)
results.append(f"[{tool_name}] {result}")
except Exception as e:
results.append(f"[{tool_name}] 执行失败: {e}")
break
state["tool_calls"] = tool_calls
state["last_tool_result"] = "\n".join(results)
follow_up = await _ainvoke(llm,
[SystemMessage(content=LIBRARIAN_SYSTEM_PROMPT),
HumanMessage(content=f"工具执行结果:\n{state['last_tool_result']}")]
)
state["final_response"] = follow_up.content
else:
state["final_response"] = response.content
state["knowledge_context"] = state.get("last_tool_result", "")
state["should_respond"] = True
return state
return await _run_sub_commander(state, AgentRole.LIBRARIAN, LIBRARIAN_SYSTEM_PROMPT, user_query, use_tools=True, summary_target="knowledge_context")
async def analyst_node(state: AgentState) -> AgentState:
"""分析师节点: 分析工作数据,生成报告"""
llm = _get_llm_for_state(state)
user_msgs = _filter_user_messages(state["messages"])
user_query = user_msgs[-1].content if user_msgs else ""
system_msgs = [SystemMessage(content=ANALYST_SYSTEM_PROMPT)]
skill_ctx = build_skill_context("analyst")
if skill_ctx:
system_msgs.append(SystemMessage(content=skill_ctx))
response = await _ainvoke_with_tools(llm,
system_msgs + [HumanMessage(content=f"用户请求: {user_query}")]
)
tool_calls = getattr(response, "tool_calls", None) or []
if tool_calls:
results = []
for tc in tool_calls:
tool_name = tc.get("name")
args = tc.get("args", {})
for tool in ALL_TOOLS:
if tool.name == tool_name:
try:
result = tool.invoke(args)
results.append(f"[{tool_name}] {result}")
except Exception as e:
results.append(f"[{tool_name}] 执行失败: {e}")
break
state["tool_calls"] = tool_calls
state["last_tool_result"] = "\n".join(results)
follow_up = await _ainvoke(llm,
[SystemMessage(content=ANALYST_SYSTEM_PROMPT),
HumanMessage(content=f"工具执行结果:\n{state['last_tool_result']}")]
)
state["final_response"] = follow_up.content
else:
state["final_response"] = response.content
state["analysis_report"] = state.get("final_response", "")
state["should_respond"] = True
return state
return await _run_sub_commander(state, AgentRole.ANALYST, ANALYST_SYSTEM_PROMPT, user_query, use_tools=True, summary_target="analysis_report")
def route_agent(state: AgentState) -> str: