feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator
This commit is contained in:
@@ -21,6 +21,7 @@ from app.models.conversation import Conversation, Message
|
||||
from app.models.user import User
|
||||
from app.agents.graph import get_agent_graph
|
||||
from app.agents.context import set_current_user, clear_current_user
|
||||
from app.agents.skills.registry import get_skill_registry
|
||||
from app.services import memory_service
|
||||
from app.services.brain_service import BrainService
|
||||
from app.services.llm_service import create_llm_from_config, resolve_provider_capabilities
|
||||
@@ -95,9 +96,8 @@ def _is_streaming_rejection_error(error: Exception, user_llm_config: dict | None
|
||||
]
|
||||
|
||||
if isinstance(error, BadRequestError):
|
||||
return (
|
||||
getattr(capabilities, "provider", None) not in {"openai", "claude"}
|
||||
and any(marker in error_text for marker in markers)
|
||||
return getattr(capabilities, "provider", None) not in {"openai", "claude"} and any(
|
||||
marker in error_text for marker in markers
|
||||
)
|
||||
|
||||
return any(marker in error_text for marker in markers)
|
||||
@@ -153,8 +153,23 @@ _CONTINUITY_SNAPSHOT_FIELDS = (
|
||||
"verification_status",
|
||||
"verification_summary",
|
||||
"verification_evidence",
|
||||
"isolation_mode",
|
||||
"isolation_id",
|
||||
"isolation_workspace_path",
|
||||
"isolation_parent_conversation_id",
|
||||
"isolation_metadata",
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"estimated_cost",
|
||||
"budget_warning",
|
||||
"cost_by_agent",
|
||||
"cost_thresholds",
|
||||
"budget_state",
|
||||
"collaboration_budget_history",
|
||||
"current_phase",
|
||||
"phase_history",
|
||||
"current_checkpoint",
|
||||
"checkpoint_history",
|
||||
)
|
||||
|
||||
|
||||
@@ -166,7 +181,11 @@ def _normalize_legacy_turn_context(turn_context: Any, current_agent: Any) -> dic
|
||||
active_sub_flow = normalized.pop("active_sub_flow", None)
|
||||
if isinstance(active_agent, str) and active_agent and "active_agent" not in normalized:
|
||||
normalized["active_agent"] = active_agent
|
||||
if isinstance(active_sub_flow, str) and active_sub_flow and "active_sub_commander" not in normalized:
|
||||
if (
|
||||
isinstance(active_sub_flow, str)
|
||||
and active_sub_flow
|
||||
and "active_sub_commander" not in normalized
|
||||
):
|
||||
normalized["active_sub_commander"] = active_sub_flow
|
||||
if not normalized.get("active_agent") and isinstance(current_agent, str) and current_agent:
|
||||
normalized["active_agent"] = current_agent
|
||||
@@ -342,11 +361,32 @@ class AgentService:
|
||||
"【当前时间】\n"
|
||||
f"- current_time_utc: {reference['current_time_iso']}\n"
|
||||
f"- current_date_utc: {reference['current_date_iso']}\n"
|
||||
"说明:解析‘今天/明天/后天/本周/下周’等相对时间时,请以 current_time_utc 为准。"
|
||||
"说明:解析'今天/明天/后天/本周/下周'等相对时间时,请以 current_time_utc 为准。"
|
||||
)
|
||||
return context, reference
|
||||
|
||||
async def _get_user_llm_config(self, user_id: str, model_name: str | None = None) -> dict | None:
|
||||
def build_skill_context(self, skill_names: list[str]) -> dict:
|
||||
"""构建 Skills 上下文
|
||||
|
||||
Args:
|
||||
skill_names: Skill 名称列表
|
||||
|
||||
Returns:
|
||||
包含 skills 上下文的字典
|
||||
"""
|
||||
registry = get_skill_registry()
|
||||
merged_context = registry.get_skill_context(skill_names)
|
||||
return {
|
||||
"skills_context": merged_context,
|
||||
"skills_metadata": {
|
||||
"skills": skill_names,
|
||||
"count": len(skill_names),
|
||||
},
|
||||
}
|
||||
|
||||
async def _get_user_llm_config(
|
||||
self, user_id: str, model_name: str | None = None
|
||||
) -> dict | None:
|
||||
"""获取用户的 LLM 模型配置"""
|
||||
user = await self.db.get(User, user_id)
|
||||
if not user or not user.llm_config:
|
||||
@@ -396,13 +436,15 @@ class AgentService:
|
||||
user_llm_config: dict | None,
|
||||
) -> dict[str, Any]:
|
||||
state = initial_state(user_id, conversation.id)
|
||||
state.update({
|
||||
"messages": [HumanMessage(content=full_message)],
|
||||
"memory_context": memory_context,
|
||||
"current_datetime_context": current_datetime_context,
|
||||
"current_datetime_reference": current_datetime_reference,
|
||||
"user_llm_config": user_llm_config,
|
||||
})
|
||||
state.update(
|
||||
{
|
||||
"messages": [HumanMessage(content=full_message)],
|
||||
"memory_context": memory_context,
|
||||
"current_datetime_context": current_datetime_context,
|
||||
"current_datetime_reference": current_datetime_reference,
|
||||
"user_llm_config": user_llm_config,
|
||||
}
|
||||
)
|
||||
previous_snapshot = await self._load_continuity_snapshot(conversation)
|
||||
if previous_snapshot:
|
||||
state.update(previous_snapshot)
|
||||
@@ -464,6 +506,7 @@ class AgentService:
|
||||
file_context = ""
|
||||
if file_ids:
|
||||
from app.services.document_service import DocumentService
|
||||
|
||||
doc_svc = DocumentService(self.db)
|
||||
for file_id in file_ids:
|
||||
content = await doc_svc.get_document_content(user_id, file_id)
|
||||
@@ -529,7 +572,9 @@ class AgentService:
|
||||
set_current_user(user_id)
|
||||
try:
|
||||
graph = get_agent_graph()
|
||||
current_datetime_context, current_datetime_reference = self._build_current_datetime_context()
|
||||
current_datetime_context, current_datetime_reference = (
|
||||
self._build_current_datetime_context()
|
||||
)
|
||||
|
||||
state = await self._build_agent_state(
|
||||
user_id=user_id,
|
||||
@@ -542,7 +587,9 @@ class AgentService:
|
||||
)
|
||||
state.update(_derive_role_memory_contexts(memory_ctx))
|
||||
|
||||
yield self._build_progress_event("thinking", "Jarvis 正在分析请求", agent="master", step="理解你的问题")
|
||||
yield self._build_progress_event(
|
||||
"thinking", "Jarvis 正在分析请求", agent="master", step="理解你的问题"
|
||||
)
|
||||
|
||||
try:
|
||||
async for event in graph.astream_events(state, version="v2"):
|
||||
@@ -551,7 +598,13 @@ class AgentService:
|
||||
metadata = event.get("metadata", {})
|
||||
data = event.get("data", {})
|
||||
|
||||
if kind == "on_chain_start" and event_name in {"master", "schedule_planner", "executor", "librarian", "analyst"}:
|
||||
if kind == "on_chain_start" and event_name in {
|
||||
"master",
|
||||
"schedule_planner",
|
||||
"executor",
|
||||
"librarian",
|
||||
"analyst",
|
||||
}:
|
||||
stage_map = {
|
||||
"master": ("thinking", "Jarvis 正在理解请求"),
|
||||
"schedule_planner": ("planning", "Jarvis 正在编排日程"),
|
||||
@@ -559,9 +612,13 @@ class AgentService:
|
||||
"librarian": ("tool", "Jarvis 正在检索知识"),
|
||||
"analyst": ("thinking", "Jarvis 正在分析信息"),
|
||||
}
|
||||
stage, label = stage_map.get(event_name, ("thinking", "Jarvis 正在思考"))
|
||||
yield self._build_progress_event(stage, label, agent=event_name, step=label)
|
||||
|
||||
stage, label = stage_map.get(
|
||||
event_name, ("thinking", "Jarvis 正在思考")
|
||||
)
|
||||
yield self._build_progress_event(
|
||||
stage, label, agent=event_name, step=label
|
||||
)
|
||||
|
||||
elif kind == "on_tool_start":
|
||||
yield self._build_progress_event(
|
||||
"tool",
|
||||
@@ -570,7 +627,7 @@ class AgentService:
|
||||
tool_name=event_name,
|
||||
step=f"正在执行 {event_name}",
|
||||
)
|
||||
|
||||
|
||||
elif kind == "on_tool_end":
|
||||
tool_result = data.get("output")
|
||||
step = f"已完成 {event_name}"
|
||||
@@ -583,14 +640,16 @@ class AgentService:
|
||||
tool_name=event_name,
|
||||
step=step,
|
||||
)
|
||||
|
||||
|
||||
elif kind == "on_chat_model_stream":
|
||||
chunk = data.get("chunk")
|
||||
content = _coerce_event_text(getattr(chunk, "content", "") if chunk else "")
|
||||
content = _coerce_event_text(
|
||||
getattr(chunk, "content", "") if chunk else ""
|
||||
)
|
||||
if content:
|
||||
collected += content
|
||||
yield {"type": "chunk", "content": content}
|
||||
|
||||
|
||||
elif kind == "on_chain_end":
|
||||
output = data.get("output")
|
||||
final_resp = None
|
||||
@@ -605,7 +664,9 @@ class AgentService:
|
||||
|
||||
elif kind == "on_chat_model_end":
|
||||
output = data.get("output")
|
||||
final_content = _coerce_event_text(getattr(output, "content", "") if output else "")
|
||||
final_content = _coerce_event_text(
|
||||
getattr(output, "content", "") if output else ""
|
||||
)
|
||||
if final_content:
|
||||
final_text = final_content
|
||||
if final_text != collected:
|
||||
@@ -614,12 +675,16 @@ class AgentService:
|
||||
|
||||
except Exception as e:
|
||||
if _is_streaming_rejection_error(e, user_llm_config) and not collected:
|
||||
yield self._build_progress_event("responding", "Jarvis 正在生成回复", agent="master", step="fallback")
|
||||
yield self._build_progress_event(
|
||||
"responding", "Jarvis 正在生成回复", agent="master", step="fallback"
|
||||
)
|
||||
try:
|
||||
result_state = await graph.ainvoke(state)
|
||||
if isinstance(result_state, dict):
|
||||
state.update(result_state)
|
||||
fallback_content = result_state.get("final_response") or str(result_state.get("messages", [AIMessage(content="")])[-1].content)
|
||||
fallback_content = result_state.get("final_response") or str(
|
||||
result_state.get("messages", [AIMessage(content="")])[-1].content
|
||||
)
|
||||
collected = str(fallback_content)
|
||||
yield {"type": "chunk", "content": collected}
|
||||
except Exception:
|
||||
@@ -643,14 +708,24 @@ class AgentService:
|
||||
if collected:
|
||||
assistant_msg.content = collected
|
||||
continuity_snapshot = _build_continuity_snapshot(state or {})
|
||||
assistant_msg.attachments = ([{
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
}] if continuity_snapshot else None)
|
||||
conv.agent_state = ({
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
} if continuity_snapshot else None)
|
||||
assistant_msg.attachments = (
|
||||
[
|
||||
{
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
}
|
||||
]
|
||||
if continuity_snapshot
|
||||
else None
|
||||
)
|
||||
conv.agent_state = (
|
||||
{
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
}
|
||||
if continuity_snapshot
|
||||
else None
|
||||
)
|
||||
await BrainService(self.db).create_event(
|
||||
user_id,
|
||||
**_build_assistant_event_payload(collected),
|
||||
@@ -728,12 +803,16 @@ class AgentService:
|
||||
importance_signal=1.0,
|
||||
)
|
||||
|
||||
memory_ctx = await memory_service.build_memory_context(self.db, user_id, conversation_id, message)
|
||||
memory_ctx = await memory_service.build_memory_context(
|
||||
self.db, user_id, conversation_id, message
|
||||
)
|
||||
|
||||
set_current_user(user_id)
|
||||
try:
|
||||
graph = get_agent_graph()
|
||||
current_datetime_context, current_datetime_reference = self._build_current_datetime_context()
|
||||
current_datetime_context, current_datetime_reference = (
|
||||
self._build_current_datetime_context()
|
||||
)
|
||||
state = await self._build_agent_state(
|
||||
user_id=user_id,
|
||||
conversation=conv,
|
||||
@@ -745,7 +824,9 @@ class AgentService:
|
||||
)
|
||||
state.update(_derive_role_memory_contexts(memory_ctx))
|
||||
result_state = await graph.ainvoke(state)
|
||||
response_content = result_state.get("final_response") or str(result_state.get("messages", [AIMessage(content="")])[-1].content)
|
||||
response_content = result_state.get("final_response") or str(
|
||||
result_state.get("messages", [AIMessage(content="")])[-1].content
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("agent_chat_simple_failed")
|
||||
response_content = "抱歉,发生错误。"
|
||||
@@ -766,15 +847,27 @@ class AgentService:
|
||||
)
|
||||
|
||||
assistant_msg.content = response_content
|
||||
continuity_snapshot = _build_continuity_snapshot(result_state) if 'result_state' in locals() else None
|
||||
assistant_msg.attachments = ([{
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
}] if continuity_snapshot else None)
|
||||
conv.agent_state = ({
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
} if continuity_snapshot else None)
|
||||
continuity_snapshot = (
|
||||
_build_continuity_snapshot(result_state) if "result_state" in locals() else None
|
||||
)
|
||||
assistant_msg.attachments = (
|
||||
[
|
||||
{
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
}
|
||||
]
|
||||
if continuity_snapshot
|
||||
else None
|
||||
)
|
||||
conv.agent_state = (
|
||||
{
|
||||
"kind": "agent_continuity_state",
|
||||
**continuity_snapshot,
|
||||
}
|
||||
if continuity_snapshot
|
||||
else None
|
||||
)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(assistant_msg)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user