feat: improve agent collaboration observability

This commit is contained in:
2026-06-15 11:33:08 +08:00
parent 7b7c82309c
commit d5f1a4510f
13 changed files with 593 additions and 36 deletions
@@ -3,6 +3,7 @@
"""
import logging
import time
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy import select
@@ -45,7 +46,93 @@ class AgentService:
for key, value in self._agent_collaboration_metadata(agent).items():
collaboration.setdefault(key, value)
enriched["collaboration"] = collaboration
communication = dict(enriched.get("communication") or {})
actor = dict(communication.get("actor") or {})
for key in (
"agent_id",
"agent_code",
"agent_name",
"collaboration_role",
"collaboration_mode",
"model_id",
):
if collaboration.get(key):
actor.setdefault(key, collaboration[key])
communication.setdefault("event", enriched.get("type") or "")
communication.setdefault("status", "failed" if enriched.get("type") == "error" else "completed")
communication.setdefault("channel", "agent_chat")
communication.setdefault("actor", actor)
communication.setdefault(
"message",
enriched.get("content")
or enriched.get("message")
or enriched.get("accumulated_content")
or "",
)
enriched["communication"] = communication
return enriched
@staticmethod
def _event_to_reasoning_step(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
event_type = event.get('type')
if event_type not in {
'action',
'annotation_reply',
'error',
'knowledge_retrieval',
'loop_complete',
'loop_iteration_complete',
'loop_iteration_error',
'loop_iteration_start',
'node_complete',
'node_start',
'observation',
'parallel_complete',
'parallel_start',
'thought',
'tool_call',
'tool_result',
'waiting_input',
}:
return None
collaboration = event.get('collaboration') or {}
communication = event.get('communication') or {}
config = event.get('waiting_config') or event.get('config') or {}
content = (
event.get('node_label')
or event.get('content')
or communication.get('message')
or config.get('title')
or config.get('question')
or event.get('node_type')
or event_type
)
step = {
'type': event_type,
'content': content,
'tool': event.get('tool') or '',
'params': event.get('params') or config,
'node_id': event.get('node_id') or '',
'node_type': event.get('node_type') or '',
'branch_id': event.get('branch_id') or collaboration.get('branch_id') or '',
'branch_label': event.get('branch_label') or collaboration.get('branch_label') or '',
'agent_code': collaboration.get('agent_code') or event.get('agent_code') or '',
'agent_name': collaboration.get('agent_name') or event.get('agent_name') or '',
'model': collaboration.get('model') or event.get('model') or '',
'model_id': collaboration.get('model_id') or event.get('model_id') or '',
'subflow_name': collaboration.get('subflow_name') or event.get('subflow_name') or '',
'from_subflow': bool(collaboration.get('from_subflow') or event.get('from_subflow')),
'collaboration_role': collaboration.get('collaboration_role') or '',
'collaboration_mode': collaboration.get('collaboration_mode') or event.get('collaboration_mode') or '',
'communication': communication,
'output': event.get('outputs') or event.get('output'),
'status': 'running' if event_type in {'node_start', 'parallel_start', 'waiting_input', 'loop_iteration_start'} else 'completed',
'timestamp': datetime.now().isoformat(),
}
return {key: value for key, value in step.items() if value not in (None, '', {}, [])}
async def chat(
self,
@@ -209,16 +296,31 @@ class AgentService:
)
self._db.add(assistant_msg)
await self._db.flush()
reasoning_steps: List[Dict[str, Any]] = []
yield {
yield self._attach_agent_collaboration({
'type': 'start',
'conversation_id': str(conversation.id),
'message_id': str(assistant_msg.id),
}
}, agent)
try:
# 构建系统提示词
system_prompt = self._build_system_prompt_simple(agent)
model_id = await self._resolve_agent_model_id(agent)
if not model_id:
raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型')
event = self._attach_agent_collaboration({
'type': 'thought',
'content': '智能体开始分析用户需求并调用默认 chat 模型',
'model_id': model_id,
}, agent)
step = self._event_to_reasoning_step(event)
if step:
reasoning_steps.append(step)
assistant_msg.reasoning_steps = reasoning_steps.copy()
yield event
# 标注直接回复检查(高相似度时跳过 LLM,直接返回标注答案)
annotation_reply = await self._check_annotation_direct_reply(agent, user_message)
@@ -257,11 +359,16 @@ class AgentService:
knowledge_context = await self._retrieve_knowledge_context(agent, user_message)
if knowledge_context:
system_prompt = self._inject_knowledge_context(system_prompt, knowledge_context)
yield {
event = {
'type': 'knowledge_retrieval',
'content': knowledge_context['summary'],
'result_count': knowledge_context['result_count'],
}
step = self._event_to_reasoning_step(event)
if step:
reasoning_steps.append(step)
assistant_msg.reasoning_steps = reasoning_steps.copy()
yield event
# 获取对话历史
history = await self._get_conversation_history(conversation, limit=10)
@@ -297,6 +404,7 @@ class AgentService:
'type': 'llm_chunk',
'content': chunk.content,
'accumulated_content': accumulated_content,
'model_id': model_id,
}
if chunk.is_finished:
@@ -317,6 +425,7 @@ class AgentService:
yield {
'type': 'answer',
'content': final_answer,
'model_id': model_id,
}
# 更新助手消息
@@ -325,6 +434,7 @@ class AgentService:
assistant_msg.status = 'completed'
assistant_msg.total_tokens = total_tokens
assistant_msg.elapsed_time = elapsed_time
assistant_msg.reasoning_steps = reasoning_steps.copy()
# 更新对话统计
conversation.message_count = (conversation.message_count or 0) + 2
@@ -352,6 +462,10 @@ class AgentService:
logger.exception(f'Agent chat error: {e}')
assistant_msg.status = 'failed'
assistant_msg.error_message = str(e)
error_step = self._event_to_reasoning_step({'type': 'error', 'content': str(e)})
if error_step:
reasoning_steps.append(error_step)
assistant_msg.reasoning_steps = reasoning_steps.copy()
await self._db.commit()
yield {
'type': 'error',
@@ -438,6 +552,7 @@ class AgentService:
)
self._db.add(assistant_msg)
await self._db.flush()
reasoning_steps: List[Dict[str, Any]] = []
yield {
'type': 'start',
@@ -507,6 +622,25 @@ class AgentService:
output = (event.get('outputs') or {}).get('output', '')
if output:
final_content = output
step = self._event_to_reasoning_step(event)
if step:
existing_index = next(
(
index
for index, item in enumerate(reasoning_steps)
if item.get('node_id')
and item.get('node_id') == step.get('node_id')
and item.get('type') == 'node_start'
and step.get('type') == 'node_complete'
),
-1,
)
if existing_index >= 0:
reasoning_steps[existing_index] = step
else:
reasoning_steps.append(step)
assistant_msg.reasoning_steps = reasoning_steps.copy()
yield event
else:
@@ -573,6 +707,25 @@ class AgentService:
output = (event.get('outputs') or {}).get('output', '')
if output:
final_content = output
step = self._event_to_reasoning_step(event)
if step:
existing_index = next(
(
index
for index, item in enumerate(reasoning_steps)
if item.get('node_id')
and item.get('node_id') == step.get('node_id')
and item.get('type') == 'node_start'
and step.get('type') == 'node_complete'
),
-1,
)
if existing_index >= 0:
reasoning_steps[existing_index] = step
else:
reasoning_steps.append(step)
assistant_msg.reasoning_steps = reasoning_steps.copy()
yield event
@@ -585,6 +738,7 @@ class AgentService:
assistant_msg.status = 'completed'
assistant_msg.elapsed_time = elapsed_time
assistant_msg.total_tokens = total_tokens
assistant_msg.reasoning_steps = reasoning_steps.copy()
# 更新对话统计
conversation.message_count = (conversation.message_count or 0) + 2
@@ -614,6 +768,10 @@ class AgentService:
logger.exception(f'Dialog flow error: {e}')
assistant_msg.status = 'failed'
assistant_msg.error_message = str(e)
error_step = self._event_to_reasoning_step({'type': 'error', 'content': str(e)})
if error_step:
reasoning_steps.append(error_step)
assistant_msg.reasoning_steps = reasoning_steps.copy()
await self._db.commit()
yield {
'type': 'error',