feat: improve agent collaboration observability
This commit is contained in:
@@ -116,17 +116,12 @@ class IntentNode(BaseNode):
|
|||||||
error='请至少配置一个意图',
|
error='请至少配置一个意图',
|
||||||
)
|
)
|
||||||
|
|
||||||
if not model_id:
|
llm_service = LLMService(context.db_session)
|
||||||
return NodeResult(
|
model_id = await llm_service.resolve_chat_model_id(model_id)
|
||||||
success=False,
|
|
||||||
error='请选择用于意图识别的模型',
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查模型是否支持 Function Calling
|
# 检查模型是否支持 Function Calling
|
||||||
supports_fc = self._check_model_supports_function_call(model_id)
|
supports_fc = self._check_model_supports_function_call(model_id)
|
||||||
|
|
||||||
llm_service = LLMService(context.db_session)
|
|
||||||
|
|
||||||
if supports_fc:
|
if supports_fc:
|
||||||
# 使用 Function Calling 方式(更准确)
|
# 使用 Function Calling 方式(更准确)
|
||||||
intent_name, confidence, total_tokens = await self._execute_with_function_calling_async(
|
intent_name, confidence, total_tokens = await self._execute_with_function_calling_async(
|
||||||
@@ -169,6 +164,7 @@ class IntentNode(BaseNode):
|
|||||||
'intent': intent_name,
|
'intent': intent_name,
|
||||||
'confidence': confidence,
|
'confidence': confidence,
|
||||||
'matched_branch': next_node_id,
|
'matched_branch': next_node_id,
|
||||||
|
'model_id': model_id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ class LLMNode(BaseNode):
|
|||||||
|
|
||||||
# 调用 LLM(异步)
|
# 调用 LLM(异步)
|
||||||
llm_service = LLMService(context.db_session)
|
llm_service = LLMService(context.db_session)
|
||||||
|
model_id = await llm_service.resolve_chat_model_id(model_id)
|
||||||
output_var = self.config.get('output_variable', 'llm_response')
|
output_var = self.config.get('output_variable', 'llm_response')
|
||||||
|
|
||||||
# 根据输出模式选择执行方式
|
# 根据输出模式选择执行方式
|
||||||
@@ -172,6 +173,11 @@ class LLMNode(BaseNode):
|
|||||||
llm_service, model_id, messages, temperature, max_tokens,
|
llm_service, model_id, messages, temperature, max_tokens,
|
||||||
output_schema, output_var, start_time
|
output_schema, output_var, start_time
|
||||||
)
|
)
|
||||||
|
result.metadata = {
|
||||||
|
**(result.metadata or {}),
|
||||||
|
'model_id': str(model_id),
|
||||||
|
'output_variable': output_var,
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
# 普通文本输出模式(异步)
|
# 普通文本输出模式(异步)
|
||||||
@@ -194,9 +200,11 @@ class LLMNode(BaseNode):
|
|||||||
tokens_used=response.total_tokens,
|
tokens_used=response.total_tokens,
|
||||||
elapsed_time=elapsed_time,
|
elapsed_time=elapsed_time,
|
||||||
metadata={
|
metadata={
|
||||||
|
'model_id': str(model_id),
|
||||||
'model': response.model,
|
'model': response.model,
|
||||||
'prompt_tokens': response.prompt_tokens,
|
'prompt_tokens': response.prompt_tokens,
|
||||||
'completion_tokens': response.completion_tokens,
|
'completion_tokens': response.completion_tokens,
|
||||||
|
'output_variable': output_var,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -314,6 +322,11 @@ class LLMNode(BaseNode):
|
|||||||
llm_service, model_id, messages, temperature, max_tokens,
|
llm_service, model_id, messages, temperature, max_tokens,
|
||||||
output_schema, output_var, start_time
|
output_schema, output_var, start_time
|
||||||
)
|
)
|
||||||
|
result.metadata = {
|
||||||
|
**(result.metadata or {}),
|
||||||
|
'model_id': str(model_id),
|
||||||
|
'output_variable': output_var,
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
# 普通文本输出模式
|
# 普通文本输出模式
|
||||||
@@ -350,6 +363,10 @@ class LLMNode(BaseNode):
|
|||||||
},
|
},
|
||||||
tokens_used=total_tokens,
|
tokens_used=total_tokens,
|
||||||
elapsed_time=elapsed_time,
|
elapsed_time=elapsed_time,
|
||||||
|
metadata={
|
||||||
|
'model_id': str(model_id),
|
||||||
|
'output_variable': output_var,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ class AgentMessageResponse(BaseModel):
|
|||||||
content: str = ""
|
content: str = ""
|
||||||
attachments: List[AttachmentResponse] = Field(default_factory=list)
|
attachments: List[AttachmentResponse] = Field(default_factory=list)
|
||||||
status: str = "completed"
|
status: str = "completed"
|
||||||
reasoning_steps: List[ReasoningStep] = Field(default_factory=list)
|
reasoning_steps: List[Dict[str, Any]] = Field(default_factory=list)
|
||||||
tool_calls: List[ToolCallRecord] = Field(default_factory=list)
|
tool_calls: List[ToolCallRecord] = Field(default_factory=list)
|
||||||
prompt_tokens: int = 0
|
prompt_tokens: int = 0
|
||||||
completion_tokens: int = 0
|
completion_tokens: int = 0
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -45,8 +46,94 @@ class AgentService:
|
|||||||
for key, value in self._agent_collaboration_metadata(agent).items():
|
for key, value in self._agent_collaboration_metadata(agent).items():
|
||||||
collaboration.setdefault(key, value)
|
collaboration.setdefault(key, value)
|
||||||
enriched["collaboration"] = collaboration
|
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
|
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(
|
async def chat(
|
||||||
self,
|
self,
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
@@ -209,16 +296,31 @@ class AgentService:
|
|||||||
)
|
)
|
||||||
self._db.add(assistant_msg)
|
self._db.add(assistant_msg)
|
||||||
await self._db.flush()
|
await self._db.flush()
|
||||||
|
reasoning_steps: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
yield {
|
yield self._attach_agent_collaboration({
|
||||||
'type': 'start',
|
'type': 'start',
|
||||||
'conversation_id': str(conversation.id),
|
'conversation_id': str(conversation.id),
|
||||||
'message_id': str(assistant_msg.id),
|
'message_id': str(assistant_msg.id),
|
||||||
}
|
}, agent)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 构建系统提示词
|
# 构建系统提示词
|
||||||
system_prompt = self._build_system_prompt_simple(agent)
|
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,直接返回标注答案)
|
# 标注直接回复检查(高相似度时跳过 LLM,直接返回标注答案)
|
||||||
annotation_reply = await self._check_annotation_direct_reply(agent, user_message)
|
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)
|
knowledge_context = await self._retrieve_knowledge_context(agent, user_message)
|
||||||
if knowledge_context:
|
if knowledge_context:
|
||||||
system_prompt = self._inject_knowledge_context(system_prompt, knowledge_context)
|
system_prompt = self._inject_knowledge_context(system_prompt, knowledge_context)
|
||||||
yield {
|
event = {
|
||||||
'type': 'knowledge_retrieval',
|
'type': 'knowledge_retrieval',
|
||||||
'content': knowledge_context['summary'],
|
'content': knowledge_context['summary'],
|
||||||
'result_count': knowledge_context['result_count'],
|
'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)
|
history = await self._get_conversation_history(conversation, limit=10)
|
||||||
@@ -297,6 +404,7 @@ class AgentService:
|
|||||||
'type': 'llm_chunk',
|
'type': 'llm_chunk',
|
||||||
'content': chunk.content,
|
'content': chunk.content,
|
||||||
'accumulated_content': accumulated_content,
|
'accumulated_content': accumulated_content,
|
||||||
|
'model_id': model_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunk.is_finished:
|
if chunk.is_finished:
|
||||||
@@ -317,6 +425,7 @@ class AgentService:
|
|||||||
yield {
|
yield {
|
||||||
'type': 'answer',
|
'type': 'answer',
|
||||||
'content': final_answer,
|
'content': final_answer,
|
||||||
|
'model_id': model_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 更新助手消息
|
# 更新助手消息
|
||||||
@@ -325,6 +434,7 @@ class AgentService:
|
|||||||
assistant_msg.status = 'completed'
|
assistant_msg.status = 'completed'
|
||||||
assistant_msg.total_tokens = total_tokens
|
assistant_msg.total_tokens = total_tokens
|
||||||
assistant_msg.elapsed_time = elapsed_time
|
assistant_msg.elapsed_time = elapsed_time
|
||||||
|
assistant_msg.reasoning_steps = reasoning_steps.copy()
|
||||||
|
|
||||||
# 更新对话统计
|
# 更新对话统计
|
||||||
conversation.message_count = (conversation.message_count or 0) + 2
|
conversation.message_count = (conversation.message_count or 0) + 2
|
||||||
@@ -352,6 +462,10 @@ class AgentService:
|
|||||||
logger.exception(f'Agent chat error: {e}')
|
logger.exception(f'Agent chat error: {e}')
|
||||||
assistant_msg.status = 'failed'
|
assistant_msg.status = 'failed'
|
||||||
assistant_msg.error_message = str(e)
|
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()
|
await self._db.commit()
|
||||||
yield {
|
yield {
|
||||||
'type': 'error',
|
'type': 'error',
|
||||||
@@ -438,6 +552,7 @@ class AgentService:
|
|||||||
)
|
)
|
||||||
self._db.add(assistant_msg)
|
self._db.add(assistant_msg)
|
||||||
await self._db.flush()
|
await self._db.flush()
|
||||||
|
reasoning_steps: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
yield {
|
yield {
|
||||||
'type': 'start',
|
'type': 'start',
|
||||||
@@ -508,6 +623,25 @@ class AgentService:
|
|||||||
if output:
|
if output:
|
||||||
final_content = 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
|
yield event
|
||||||
else:
|
else:
|
||||||
# 新对话,启动工作流 - 使用异步方法
|
# 新对话,启动工作流 - 使用异步方法
|
||||||
@@ -574,6 +708,25 @@ class AgentService:
|
|||||||
if output:
|
if output:
|
||||||
final_content = 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
|
yield event
|
||||||
|
|
||||||
# 更新助手消息
|
# 更新助手消息
|
||||||
@@ -585,6 +738,7 @@ class AgentService:
|
|||||||
assistant_msg.status = 'completed'
|
assistant_msg.status = 'completed'
|
||||||
assistant_msg.elapsed_time = elapsed_time
|
assistant_msg.elapsed_time = elapsed_time
|
||||||
assistant_msg.total_tokens = total_tokens
|
assistant_msg.total_tokens = total_tokens
|
||||||
|
assistant_msg.reasoning_steps = reasoning_steps.copy()
|
||||||
|
|
||||||
# 更新对话统计
|
# 更新对话统计
|
||||||
conversation.message_count = (conversation.message_count or 0) + 2
|
conversation.message_count = (conversation.message_count or 0) + 2
|
||||||
@@ -614,6 +768,10 @@ class AgentService:
|
|||||||
logger.exception(f'Dialog flow error: {e}')
|
logger.exception(f'Dialog flow error: {e}')
|
||||||
assistant_msg.status = 'failed'
|
assistant_msg.status = 'failed'
|
||||||
assistant_msg.error_message = str(e)
|
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()
|
await self._db.commit()
|
||||||
yield {
|
yield {
|
||||||
'type': 'error',
|
'type': 'error',
|
||||||
|
|||||||
@@ -30,6 +30,38 @@ class LLMService:
|
|||||||
self._provider_cache: Dict[str, BaseLLMProvider] = {}
|
self._provider_cache: Dict[str, BaseLLMProvider] = {}
|
||||||
self._db = db
|
self._db = db
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_missing_model_id(model_id: Optional[str]) -> bool:
|
||||||
|
if model_id is None:
|
||||||
|
return True
|
||||||
|
return str(model_id).strip().lower() in {"", "none", "null", "undefined"}
|
||||||
|
|
||||||
|
async def resolve_chat_model_id(self, model_id: Optional[str]) -> str:
|
||||||
|
"""解析 chat 模型。未指定时使用当前启用的默认 chat 模型。"""
|
||||||
|
if not self._is_missing_model_id(model_id):
|
||||||
|
return str(model_id)
|
||||||
|
|
||||||
|
if not self._db:
|
||||||
|
raise ValueError("未找到可用的 chat 模型,请先在模型配置中启用一个模型")
|
||||||
|
|
||||||
|
from ai_platform.models import LLMModel
|
||||||
|
|
||||||
|
result = await self._db.execute(
|
||||||
|
select(LLMModel)
|
||||||
|
.where(
|
||||||
|
LLMModel.is_deleted == False,
|
||||||
|
LLMModel.is_active == True,
|
||||||
|
LLMModel.model_type == "chat",
|
||||||
|
)
|
||||||
|
.order_by(LLMModel.sort.desc(), LLMModel.sys_create_datetime.desc())
|
||||||
|
)
|
||||||
|
model = result.scalars().first()
|
||||||
|
if not model:
|
||||||
|
raise ValueError("未找到可用的 chat 模型,请先在模型配置中启用一个模型")
|
||||||
|
|
||||||
|
logger.info("No model_id supplied, fallback to default chat model %s", model.id)
|
||||||
|
return str(model.id)
|
||||||
|
|
||||||
async def _get_provider_async(self, model_id: str) -> tuple:
|
async def _get_provider_async(self, model_id: str) -> tuple:
|
||||||
"""
|
"""
|
||||||
异步获取模型对应的提供商实例
|
异步获取模型对应的提供商实例
|
||||||
@@ -45,6 +77,8 @@ class LLMService:
|
|||||||
if not self._db:
|
if not self._db:
|
||||||
raise ValueError("数据库会话未初始化")
|
raise ValueError("数据库会话未初始化")
|
||||||
|
|
||||||
|
model_id = await self.resolve_chat_model_id(model_id)
|
||||||
|
|
||||||
# 查询模型
|
# 查询模型
|
||||||
result = await self._db.execute(
|
result = await self._db.execute(
|
||||||
select(LLMModel).where(
|
select(LLMModel).where(
|
||||||
@@ -319,14 +353,18 @@ class LLMService:
|
|||||||
async def get_provider():
|
async def get_provider():
|
||||||
return await self._get_provider_async(model_id)
|
return await self._get_provider_async(model_id)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
try:
|
||||||
if loop.is_running():
|
loop = asyncio.get_event_loop()
|
||||||
# 如果已有事件循环在运行,使用线程池
|
except RuntimeError:
|
||||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
provider, model_name = asyncio.run(get_provider())
|
||||||
future = executor.submit(asyncio.run, get_provider())
|
|
||||||
provider, model_name = future.result()
|
|
||||||
else:
|
else:
|
||||||
provider, model_name = loop.run_until_complete(get_provider())
|
if loop.is_running():
|
||||||
|
# 如果已有事件循环在运行,使用线程池
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
future = executor.submit(asyncio.run, get_provider())
|
||||||
|
provider, model_name = future.result()
|
||||||
|
else:
|
||||||
|
provider, model_name = loop.run_until_complete(get_provider())
|
||||||
|
|
||||||
# 使用获取到的 provider 进行流式调用
|
# 使用获取到的 provider 进行流式调用
|
||||||
yield from self.chat_stream_sync_with_provider(
|
yield from self.chat_stream_sync_with_provider(
|
||||||
|
|||||||
@@ -50,10 +50,48 @@ def _node_label_from_map(node_map: Dict[str, Any], node_id: str) -> str:
|
|||||||
return data.get('label') or node.get('label') or node_id
|
return data.get('label') or node.get('label') or node_id
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_preview(value: Any, max_length: int = 220) -> str:
|
||||||
|
if value is None or value == '':
|
||||||
|
return ''
|
||||||
|
if isinstance(value, str):
|
||||||
|
text = value
|
||||||
|
else:
|
||||||
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
text = json.dumps(value, ensure_ascii=False)
|
||||||
|
except TypeError:
|
||||||
|
text = str(value)
|
||||||
|
text = text.strip()
|
||||||
|
return text if len(text) <= max_length else f'{text[:max_length]}...'
|
||||||
|
|
||||||
|
|
||||||
|
def _node_collaboration_mode(node_type: str, data: Dict[str, Any]) -> str:
|
||||||
|
if data.get('agent_code'):
|
||||||
|
return 'agent'
|
||||||
|
if node_type == 'parallel':
|
||||||
|
return 'parallel'
|
||||||
|
if node_type == 'subflow':
|
||||||
|
return 'subflow'
|
||||||
|
if node_type == 'intent':
|
||||||
|
return 'router'
|
||||||
|
if node_type in ('confirm', 'question', 'choice'):
|
||||||
|
return 'human_input'
|
||||||
|
if node_type == 'llm':
|
||||||
|
return 'llm'
|
||||||
|
return 'workflow'
|
||||||
|
|
||||||
|
|
||||||
def _node_collaboration_metadata(node_map: Dict[str, Any], node_id: str, node_type: str) -> dict:
|
def _node_collaboration_metadata(node_map: Dict[str, Any], node_id: str, node_type: str) -> dict:
|
||||||
node = node_map.get(node_id) or {}
|
node = node_map.get(node_id) or {}
|
||||||
data = node.get('data') or {}
|
data = node.get('data') or {}
|
||||||
metadata: Dict[str, Any] = {}
|
metadata: Dict[str, Any] = {
|
||||||
|
'node_id': node_id,
|
||||||
|
'node_type': node_type,
|
||||||
|
'node_label': _node_label_from_map(node_map, node_id),
|
||||||
|
'collaboration_role': data.get('label') or node_id,
|
||||||
|
'collaboration_mode': _node_collaboration_mode(node_type, data),
|
||||||
|
}
|
||||||
|
|
||||||
agent_code = (data.get('agent_code') or '').strip()
|
agent_code = (data.get('agent_code') or '').strip()
|
||||||
if agent_code:
|
if agent_code:
|
||||||
@@ -83,15 +121,96 @@ def _node_collaboration_metadata(node_map: Dict[str, Any], node_id: str, node_ty
|
|||||||
if branch.get('id')
|
if branch.get('id')
|
||||||
]
|
]
|
||||||
|
|
||||||
if node_type in ('template', 'llm') and agent_code:
|
|
||||||
metadata['collaboration_role'] = data.get('label') or node_id
|
|
||||||
|
|
||||||
if node_type == 'subflow':
|
|
||||||
metadata['collaboration_role'] = data.get('label') or node_id
|
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
|
def _build_communication_metadata(
|
||||||
|
node_map: Dict[str, Any],
|
||||||
|
node_id: str,
|
||||||
|
node_type: str,
|
||||||
|
*,
|
||||||
|
event_type: str,
|
||||||
|
status: Optional[str] = None,
|
||||||
|
branch_id: Optional[str] = None,
|
||||||
|
branch_label: Optional[str] = None,
|
||||||
|
target_node_id: Optional[str] = None,
|
||||||
|
output: Any = None,
|
||||||
|
error: Any = None,
|
||||||
|
waiting_config: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> dict:
|
||||||
|
actor = _node_collaboration_metadata(node_map, node_id, node_type)
|
||||||
|
message = _safe_preview(error or output)
|
||||||
|
if waiting_config:
|
||||||
|
message = (
|
||||||
|
waiting_config.get('title')
|
||||||
|
or waiting_config.get('question')
|
||||||
|
or waiting_config.get('content')
|
||||||
|
or message
|
||||||
|
)
|
||||||
|
|
||||||
|
communication: Dict[str, Any] = {
|
||||||
|
'event': event_type,
|
||||||
|
'status': status or '',
|
||||||
|
'actor': {
|
||||||
|
key: actor.get(key)
|
||||||
|
for key in (
|
||||||
|
'node_id',
|
||||||
|
'node_type',
|
||||||
|
'node_label',
|
||||||
|
'agent_code',
|
||||||
|
'agent_name',
|
||||||
|
'collaboration_role',
|
||||||
|
'collaboration_mode',
|
||||||
|
)
|
||||||
|
if actor.get(key)
|
||||||
|
},
|
||||||
|
'message': _safe_preview(message),
|
||||||
|
}
|
||||||
|
if branch_id:
|
||||||
|
communication['channel'] = 'parallel_branch'
|
||||||
|
communication['branch_id'] = branch_id
|
||||||
|
communication['branch_label'] = branch_label or branch_id
|
||||||
|
elif node_type == 'subflow':
|
||||||
|
communication['channel'] = 'subflow'
|
||||||
|
elif node_type in ('confirm', 'question', 'choice') or event_type == 'waiting_input':
|
||||||
|
communication['channel'] = 'human_input'
|
||||||
|
else:
|
||||||
|
communication['channel'] = 'workflow_edge'
|
||||||
|
if target_node_id:
|
||||||
|
target_type = (node_map.get(target_node_id) or {}).get('type', '')
|
||||||
|
target = _node_collaboration_metadata(node_map, target_node_id, target_type)
|
||||||
|
communication['target'] = {
|
||||||
|
key: target.get(key)
|
||||||
|
for key in (
|
||||||
|
'node_id',
|
||||||
|
'node_type',
|
||||||
|
'node_label',
|
||||||
|
'agent_code',
|
||||||
|
'agent_name',
|
||||||
|
'collaboration_role',
|
||||||
|
'collaboration_mode',
|
||||||
|
)
|
||||||
|
if target.get(key)
|
||||||
|
}
|
||||||
|
return communication
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_handoff_target(
|
||||||
|
current_node_id: str,
|
||||||
|
result: Optional[NodeResult],
|
||||||
|
edge_map: Dict[str, List[str]],
|
||||||
|
parallel_edge_map: Dict[str, Dict[str, str]],
|
||||||
|
node_map: Dict[str, Any],
|
||||||
|
) -> Optional[str]:
|
||||||
|
if result and result.next_node_id:
|
||||||
|
if result.next_node_id in parallel_edge_map.get(current_node_id, {}):
|
||||||
|
return parallel_edge_map[current_node_id][result.next_node_id]
|
||||||
|
if result.next_node_id in node_map:
|
||||||
|
return result.next_node_id
|
||||||
|
next_nodes = edge_map.get(current_node_id, [])
|
||||||
|
return next_nodes[0] if next_nodes else None
|
||||||
|
|
||||||
|
|
||||||
def _make_execution_log_entry(
|
def _make_execution_log_entry(
|
||||||
node_map: Dict[str, Any],
|
node_map: Dict[str, Any],
|
||||||
node_id: str,
|
node_id: str,
|
||||||
@@ -100,8 +219,25 @@ def _make_execution_log_entry(
|
|||||||
) -> dict:
|
) -> dict:
|
||||||
metadata = dict(extra.pop('metadata', {}) or {})
|
metadata = dict(extra.pop('metadata', {}) or {})
|
||||||
collaboration = _node_collaboration_metadata(node_map, node_id, node_type)
|
collaboration = _node_collaboration_metadata(node_map, node_id, node_type)
|
||||||
|
branch_id = extra.get('branch') or extra.get('branch_id')
|
||||||
|
branch_label = extra.get('branch_label')
|
||||||
|
if branch_id:
|
||||||
|
collaboration['branch_id'] = branch_id
|
||||||
|
collaboration['branch_label'] = branch_label or branch_id
|
||||||
if collaboration:
|
if collaboration:
|
||||||
metadata['collaboration'] = collaboration
|
metadata['collaboration'] = collaboration
|
||||||
|
metadata['communication'] = _build_communication_metadata(
|
||||||
|
node_map,
|
||||||
|
node_id,
|
||||||
|
node_type,
|
||||||
|
event_type='node_complete',
|
||||||
|
status=extra.get('status'),
|
||||||
|
branch_id=branch_id,
|
||||||
|
branch_label=branch_label,
|
||||||
|
target_node_id=extra.get('target_node_id'),
|
||||||
|
output=extra.get('output'),
|
||||||
|
error=extra.get('error'),
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
'node_id': node_id,
|
'node_id': node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
@@ -133,6 +269,10 @@ def _event_collaboration_metadata(
|
|||||||
'model_id',
|
'model_id',
|
||||||
'output_variable',
|
'output_variable',
|
||||||
'subflow_name',
|
'subflow_name',
|
||||||
|
'prompt_tokens',
|
||||||
|
'completion_tokens',
|
||||||
|
'confidence',
|
||||||
|
'matched_branch',
|
||||||
):
|
):
|
||||||
value = result_metadata.get(key)
|
value = result_metadata.get(key)
|
||||||
if value is not None and value != '':
|
if value is not None and value != '':
|
||||||
@@ -159,8 +299,27 @@ def _merge_event_collaboration(
|
|||||||
result: Optional[NodeResult] = None,
|
result: Optional[NodeResult] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
collaboration = _event_collaboration_metadata(node_map, node_id, node_type, result)
|
collaboration = _event_collaboration_metadata(node_map, node_id, node_type, result)
|
||||||
|
if event.get('branch_id'):
|
||||||
|
collaboration['branch_id'] = event.get('branch_id')
|
||||||
|
collaboration['branch_label'] = event.get('branch_label') or event.get('branch_id')
|
||||||
if collaboration:
|
if collaboration:
|
||||||
event['collaboration'] = collaboration
|
event['collaboration'] = collaboration
|
||||||
|
event_error = None
|
||||||
|
if event.get('type') == 'error':
|
||||||
|
event_error = event.get('error_message') or event.get('message') or event.get('content')
|
||||||
|
event['communication'] = _build_communication_metadata(
|
||||||
|
node_map,
|
||||||
|
node_id,
|
||||||
|
node_type,
|
||||||
|
event_type=event.get('type') or '',
|
||||||
|
status=event.get('status'),
|
||||||
|
branch_id=event.get('branch_id'),
|
||||||
|
branch_label=event.get('branch_label'),
|
||||||
|
target_node_id=event.get('target_node_id'),
|
||||||
|
output=(event.get('outputs') or {}).get('output') if isinstance(event.get('outputs'), dict) else event.get('outputs'),
|
||||||
|
error=event_error,
|
||||||
|
waiting_config=event.get('waiting_config') or event.get('config'),
|
||||||
|
)
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
@@ -653,6 +812,13 @@ class AIWorkflowService:
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
result = await node_instance.execute_async(context)
|
result = await node_instance.execute_async(context)
|
||||||
elapsed = int((time.time() - start_time) * 1000)
|
elapsed = int((time.time() - start_time) * 1000)
|
||||||
|
target_node_id = _resolve_handoff_target(
|
||||||
|
current_node_id,
|
||||||
|
result,
|
||||||
|
edge_map,
|
||||||
|
parallel_edge_map,
|
||||||
|
node_map,
|
||||||
|
)
|
||||||
|
|
||||||
# 记录日志
|
# 记录日志
|
||||||
logs.append(_make_execution_log_entry(
|
logs.append(_make_execution_log_entry(
|
||||||
@@ -666,6 +832,7 @@ class AIWorkflowService:
|
|||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
|
target_node_id=target_node_id,
|
||||||
))
|
))
|
||||||
|
|
||||||
total_tokens += result.tokens_used
|
total_tokens += result.tokens_used
|
||||||
@@ -819,6 +986,13 @@ class AIWorkflowService:
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
result = await node_instance.execute_async(branch_context)
|
result = await node_instance.execute_async(branch_context)
|
||||||
elapsed = int((time.time() - start_time) * 1000)
|
elapsed = int((time.time() - start_time) * 1000)
|
||||||
|
target_node_id = _resolve_handoff_target(
|
||||||
|
current_id,
|
||||||
|
result,
|
||||||
|
edge_map,
|
||||||
|
parallel_edge_map,
|
||||||
|
node_map,
|
||||||
|
)
|
||||||
|
|
||||||
branch_logs.append(_make_execution_log_entry(
|
branch_logs.append(_make_execution_log_entry(
|
||||||
node_map,
|
node_map,
|
||||||
@@ -832,6 +1006,7 @@ class AIWorkflowService:
|
|||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
branch=branch_id,
|
branch=branch_id,
|
||||||
branch_label=branch_labels.get(branch_id, branch_id),
|
branch_label=branch_labels.get(branch_id, branch_id),
|
||||||
|
target_node_id=target_node_id,
|
||||||
))
|
))
|
||||||
|
|
||||||
branch_tokens += result.tokens_used
|
branch_tokens += result.tokens_used
|
||||||
@@ -1072,6 +1247,7 @@ class AIWorkflowService:
|
|||||||
'output': result.output,
|
'output': result.output,
|
||||||
'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
|
'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
|
||||||
},
|
},
|
||||||
|
'target_node_id': target_node_id,
|
||||||
}, result), node_map, current_id, node_type, result)
|
}, result), node_map, current_id, node_type, result)
|
||||||
|
|
||||||
if not result.success:
|
if not result.success:
|
||||||
@@ -1782,6 +1958,13 @@ class AIWorkflowService:
|
|||||||
result = await node_instance.execute_async(context)
|
result = await node_instance.execute_async(context)
|
||||||
|
|
||||||
elapsed = int((time.time() - start_time) * 1000)
|
elapsed = int((time.time() - start_time) * 1000)
|
||||||
|
target_node_id = _resolve_handoff_target(
|
||||||
|
current_node_id,
|
||||||
|
result,
|
||||||
|
edge_map,
|
||||||
|
parallel_edge_map,
|
||||||
|
node_map,
|
||||||
|
)
|
||||||
|
|
||||||
# 处理节点事件(如消息节点发送消息)
|
# 处理节点事件(如消息节点发送消息)
|
||||||
if result.events:
|
if result.events:
|
||||||
@@ -1815,7 +1998,9 @@ class AIWorkflowService:
|
|||||||
status='waiting',
|
status='waiting',
|
||||||
output=result.output,
|
output=result.output,
|
||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
|
metadata=_node_result_metadata(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|
||||||
@@ -1838,6 +2023,7 @@ class AIWorkflowService:
|
|||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'config': result.waiting_config,
|
'config': result.waiting_config,
|
||||||
|
'waiting_config': result.waiting_config,
|
||||||
}, node_map, current_node_id, node_type, result)
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
# 暂停工作流执行,等待用户输入后续流
|
# 暂停工作流执行,等待用户输入后续流
|
||||||
@@ -1855,6 +2041,7 @@ class AIWorkflowService:
|
|||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|
||||||
@@ -1882,6 +2069,7 @@ class AIWorkflowService:
|
|||||||
'output': result.output,
|
'output': result.output,
|
||||||
'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
|
'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
|
||||||
},
|
},
|
||||||
|
'target_node_id': target_node_id,
|
||||||
}, result), node_map, current_node_id, node_type, result)
|
}, result), node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
if not result.success:
|
if not result.success:
|
||||||
@@ -2335,6 +2523,7 @@ class AIWorkflowService:
|
|||||||
output=result.output,
|
output=result.output,
|
||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|
||||||
@@ -2353,6 +2542,7 @@ class AIWorkflowService:
|
|||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'config': result.waiting_config,
|
'config': result.waiting_config,
|
||||||
|
'waiting_config': result.waiting_config,
|
||||||
}, node_map, current_node_id, node_type, result)
|
}, node_map, current_node_id, node_type, result)
|
||||||
# 如果在循环中,添加迭代信息
|
# 如果在循环中,添加迭代信息
|
||||||
if loop_state:
|
if loop_state:
|
||||||
@@ -2371,6 +2561,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|
||||||
@@ -2401,6 +2592,7 @@ class AIWorkflowService:
|
|||||||
'output': result.output,
|
'output': result.output,
|
||||||
'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
|
'output_variables': result.metadata.get('frontend_output_variables', result.output_variables),
|
||||||
},
|
},
|
||||||
|
'target_node_id': target_node_id,
|
||||||
}, result), node_map, current_node_id, node_type, result)
|
}, result), node_map, current_node_id, node_type, result)
|
||||||
# 如果在循环中,添加迭代信息
|
# 如果在循环中,添加迭代信息
|
||||||
if loop_state:
|
if loop_state:
|
||||||
|
|||||||
@@ -281,6 +281,9 @@ export interface ExecutionLogEntry {
|
|||||||
error?: string;
|
error?: string;
|
||||||
elapsed_time?: number;
|
elapsed_time?: number;
|
||||||
tokens_used?: number;
|
tokens_used?: number;
|
||||||
|
branch?: string;
|
||||||
|
branch_label?: string;
|
||||||
|
metadata?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowRunListItem {
|
export interface WorkflowRunListItem {
|
||||||
|
|||||||
@@ -194,6 +194,21 @@ const getStepColor = (step: ReasoningStep) => {
|
|||||||
|
|
||||||
const getStepMetaItems = (step: ReasoningStep) => {
|
const getStepMetaItems = (step: ReasoningStep) => {
|
||||||
const items: string[] = [];
|
const items: string[] = [];
|
||||||
|
const communication = step.communication;
|
||||||
|
const actor = communication?.actor || {};
|
||||||
|
const target = communication?.target || {};
|
||||||
|
const actorName =
|
||||||
|
actor.agent_name ||
|
||||||
|
actor.agent_code ||
|
||||||
|
actor.node_label ||
|
||||||
|
step.agent_name ||
|
||||||
|
step.agent_code;
|
||||||
|
const targetName = target.agent_name || target.agent_code || target.node_label;
|
||||||
|
if (actorName && targetName) {
|
||||||
|
items.push(`交接: ${actorName} -> ${targetName}`);
|
||||||
|
} else if (communication?.channel) {
|
||||||
|
items.push(`通道: ${communication.channel}`);
|
||||||
|
}
|
||||||
if (step.agent_name || step.agent_code) {
|
if (step.agent_name || step.agent_code) {
|
||||||
items.push(`智能体: ${step.agent_name || step.agent_code}`);
|
items.push(`智能体: ${step.agent_name || step.agent_code}`);
|
||||||
}
|
}
|
||||||
@@ -206,6 +221,9 @@ const getStepMetaItems = (step: ReasoningStep) => {
|
|||||||
if (step.model || step.model_id) {
|
if (step.model || step.model_id) {
|
||||||
items.push(`模型: ${step.model || step.model_id}`);
|
items.push(`模型: ${step.model || step.model_id}`);
|
||||||
}
|
}
|
||||||
|
if (communication?.message && communication.message !== step.content) {
|
||||||
|
items.push(`消息: ${communication.message}`);
|
||||||
|
}
|
||||||
return items;
|
return items;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,16 @@ export interface ReasoningStep {
|
|||||||
from_subflow?: boolean;
|
from_subflow?: boolean;
|
||||||
collaboration_role?: string;
|
collaboration_role?: string;
|
||||||
collaboration_mode?: string;
|
collaboration_mode?: string;
|
||||||
|
communication?: {
|
||||||
|
actor?: Record<string, any>;
|
||||||
|
branch_id?: string;
|
||||||
|
branch_label?: string;
|
||||||
|
channel?: string;
|
||||||
|
event?: string;
|
||||||
|
message?: string;
|
||||||
|
status?: string;
|
||||||
|
target?: Record<string, any>;
|
||||||
|
};
|
||||||
output?: any;
|
output?: any;
|
||||||
/** 步骤状态:running 执行中,completed 已完成 */
|
/** 步骤状态:running 执行中,completed 已完成 */
|
||||||
status?: 'completed' | 'running';
|
status?: 'completed' | 'running';
|
||||||
|
|||||||
@@ -750,6 +750,7 @@ onUnmounted(() => {
|
|||||||
:enable-image="enableImage"
|
:enable-image="enableImage"
|
||||||
:enable-voice="enableVoice"
|
:enable-voice="enableVoice"
|
||||||
:enable-feedback="enableFeedback"
|
:enable-feedback="enableFeedback"
|
||||||
|
:show-reasoning-steps="true"
|
||||||
:show-clear-button="showClearButton"
|
:show-clear-button="showClearButton"
|
||||||
:empty-text="emptyText"
|
:empty-text="emptyText"
|
||||||
:welcome-config="welcomeConfig"
|
:welcome-config="welcomeConfig"
|
||||||
|
|||||||
@@ -231,6 +231,17 @@ export function useChatApi(props: AiChatPanelProps): {
|
|||||||
params: step.params,
|
params: step.params,
|
||||||
node_id: step.node_id,
|
node_id: step.node_id,
|
||||||
node_type: step.node_type,
|
node_type: step.node_type,
|
||||||
|
branch_id: step.branch_id,
|
||||||
|
branch_label: step.branch_label,
|
||||||
|
agent_code: step.agent_code,
|
||||||
|
agent_name: step.agent_name,
|
||||||
|
model: step.model,
|
||||||
|
model_id: step.model_id,
|
||||||
|
subflow_name: step.subflow_name,
|
||||||
|
from_subflow: step.from_subflow,
|
||||||
|
collaboration_role: step.collaboration_role,
|
||||||
|
collaboration_mode: step.collaboration_mode,
|
||||||
|
communication: step.communication,
|
||||||
output: step.output,
|
output: step.output,
|
||||||
status: step.status,
|
status: step.status,
|
||||||
timestamp: step.timestamp,
|
timestamp: step.timestamp,
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
|
|
||||||
const buildReasoningMeta = (event: StreamEvent): Partial<ReasoningStep> => {
|
const buildReasoningMeta = (event: StreamEvent): Partial<ReasoningStep> => {
|
||||||
const collaboration = event.collaboration || {};
|
const collaboration = event.collaboration || {};
|
||||||
|
const communication = event.communication || {};
|
||||||
return {
|
return {
|
||||||
branch_id: event.branch_id,
|
branch_id: event.branch_id,
|
||||||
branch_label: event.branch_label,
|
branch_label: event.branch_label,
|
||||||
@@ -187,6 +188,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
collaboration_role:
|
collaboration_role:
|
||||||
collaboration.collaboration_role || collaboration.role || event.collaboration_role,
|
collaboration.collaboration_role || collaboration.role || event.collaboration_role,
|
||||||
collaboration_mode: event.collaboration_mode || collaboration.collaboration_mode,
|
collaboration_mode: event.collaboration_mode || collaboration.collaboration_mode,
|
||||||
|
communication,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -230,6 +232,35 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'annotation_reply':
|
||||||
|
case 'knowledge_retrieval':
|
||||||
|
case 'observation':
|
||||||
|
case 'thought':
|
||||||
|
case 'tool_call':
|
||||||
|
case 'tool_result': {
|
||||||
|
currentSteps.value.push({
|
||||||
|
type: event.type as ReasoningStep['type'],
|
||||||
|
content:
|
||||||
|
event.content ||
|
||||||
|
event.message ||
|
||||||
|
event.tool ||
|
||||||
|
event.type,
|
||||||
|
tool: event.tool,
|
||||||
|
params: event.params,
|
||||||
|
result: event.result,
|
||||||
|
node_id: event.node_id,
|
||||||
|
node_type: event.node_type,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
|
status: 'completed',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
updateAssistantMessage(msgId, {
|
||||||
|
reasoning_steps: [...currentSteps.value],
|
||||||
|
...buildMessageMeta(event),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'answer': {
|
case 'answer': {
|
||||||
const initialMsg = messages.value.find((m) => m.id === msgId);
|
const initialMsg = messages.value.find((m) => m.id === msgId);
|
||||||
if (initialMsg && !initialMsg.content?.trim()) {
|
if (initialMsg && !initialMsg.content?.trim()) {
|
||||||
|
|||||||
@@ -118,11 +118,25 @@ const collaborationTimeline = computed(() => {
|
|||||||
.map((log, index) => {
|
.map((log, index) => {
|
||||||
const meta = getLogMeta(log);
|
const meta = getLogMeta(log);
|
||||||
const collaboration = meta.collaboration || {};
|
const collaboration = meta.collaboration || {};
|
||||||
const agentCode = meta.agent_code || collaboration.agent_code;
|
const communication = meta.communication || {};
|
||||||
const agentName = meta.agent_name || collaboration.agent_name || agentCode;
|
const actor = communication.actor || {};
|
||||||
const branch = log.branch_label || log.branch || collaboration.branch_label;
|
const target = communication.target || {};
|
||||||
|
const agentCode = actor.agent_code || meta.agent_code || collaboration.agent_code;
|
||||||
|
const agentName =
|
||||||
|
actor.agent_name ||
|
||||||
|
actor.agent_code ||
|
||||||
|
meta.agent_name ||
|
||||||
|
collaboration.agent_name ||
|
||||||
|
agentCode;
|
||||||
|
const branch =
|
||||||
|
communication.branch_label ||
|
||||||
|
log.branch_label ||
|
||||||
|
log.branch ||
|
||||||
|
collaboration.branch_label;
|
||||||
const subflowName = collaboration.subflow_name || meta.subflow_name;
|
const subflowName = collaboration.subflow_name || meta.subflow_name;
|
||||||
const model = meta.model || collaboration.model || meta.model_id || collaboration.model_id;
|
const model = meta.model || collaboration.model || meta.model_id || collaboration.model_id;
|
||||||
|
const targetName =
|
||||||
|
target.agent_name || target.agent_code || target.node_label || '';
|
||||||
const title =
|
const title =
|
||||||
agentName ||
|
agentName ||
|
||||||
subflowName ||
|
subflowName ||
|
||||||
@@ -131,8 +145,10 @@ const collaborationTimeline = computed(() => {
|
|||||||
log.node_id ||
|
log.node_id ||
|
||||||
`#${index + 1}`;
|
`#${index + 1}`;
|
||||||
const summaryParts = [
|
const summaryParts = [
|
||||||
|
targetName ? `交接给:${targetName}` : '',
|
||||||
branch ? `分支:${branch}` : '',
|
branch ? `分支:${branch}` : '',
|
||||||
subflowName ? `子流程:${subflowName}` : '',
|
subflowName ? `子流程:${subflowName}` : '',
|
||||||
|
communication.channel ? `通道:${communication.channel}` : '',
|
||||||
model ? `模型:${model}` : '',
|
model ? `模型:${model}` : '',
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
@@ -143,9 +159,10 @@ const collaborationTimeline = computed(() => {
|
|||||||
subtitle: log.node_label || log.node_id,
|
subtitle: log.node_label || log.node_id,
|
||||||
status: log.status,
|
status: log.status,
|
||||||
summary: summaryParts.join(' · '),
|
summary: summaryParts.join(' · '),
|
||||||
|
message: communication.message || previewValue(log.error || log.output),
|
||||||
elapsed: log.elapsed_time,
|
elapsed: log.elapsed_time,
|
||||||
tokens: log.tokens_used || 0,
|
tokens: log.tokens_used || 0,
|
||||||
hasSignal: Boolean(agentName || branch || subflowName || model),
|
hasSignal: Boolean(agentName || branch || subflowName || model || communication.channel),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((item) => item.hasSignal);
|
.filter((item) => item.hasSignal);
|
||||||
@@ -248,6 +265,35 @@ function getLogMeta(log: any) {
|
|||||||
return log?.metadata || {};
|
return log?.metadata || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCommunication(log: any) {
|
||||||
|
return getLogMeta(log).communication || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollaboration(log: any) {
|
||||||
|
return getLogMeta(log).collaboration || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActorName(log: any) {
|
||||||
|
const communication = getCommunication(log);
|
||||||
|
const actor = communication.actor || {};
|
||||||
|
const collaboration = getCollaboration(log);
|
||||||
|
return (
|
||||||
|
actor.agent_name ||
|
||||||
|
actor.agent_code ||
|
||||||
|
collaboration.agent_name ||
|
||||||
|
collaboration.agent_code ||
|
||||||
|
actor.node_label ||
|
||||||
|
log.node_label ||
|
||||||
|
log.node_id ||
|
||||||
|
'-'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTargetName(log: any) {
|
||||||
|
const target = getCommunication(log).target || {};
|
||||||
|
return target.agent_name || target.agent_code || target.node_label || '';
|
||||||
|
}
|
||||||
|
|
||||||
function selectLog(nodeId?: string) {
|
function selectLog(nodeId?: string) {
|
||||||
if (!nodeId) return;
|
if (!nodeId) return;
|
||||||
activeNodeId.value = nodeId;
|
activeNodeId.value = nodeId;
|
||||||
@@ -468,29 +514,56 @@ defineExpose({ open });
|
|||||||
{{ log.error }}
|
{{ log.error }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="getLogMeta(log).agent_code"
|
v-if="getCollaboration(log).agent_code || getCommunication(log).channel"
|
||||||
class="border-border bg-muted/40 grid grid-cols-2 gap-2 rounded-md border p-2"
|
class="border-border bg-muted/40 grid grid-cols-2 gap-2 rounded-md border p-2"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
{{ $t('ai-platform.workflowRuns.detail.agent') }}
|
执行者
|
||||||
</div>
|
</div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ getLogMeta(log).agent_name || getLogMeta(log).agent_code }}
|
{{ getActorName(log) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
{{ $t('ai-platform.workflowRuns.detail.agentCode') }}
|
交接目标
|
||||||
|
</div>
|
||||||
|
<div class="font-medium">
|
||||||
|
{{ getTargetName(log) || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
协作通道
|
||||||
|
</div>
|
||||||
|
<div class="break-words">
|
||||||
|
{{ getCommunication(log).channel || getCollaboration(log).collaboration_mode || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">分支/子流程</div>
|
||||||
|
<div class="break-words">
|
||||||
|
{{
|
||||||
|
getCommunication(log).branch_label ||
|
||||||
|
getCollaboration(log).branch_label ||
|
||||||
|
getCollaboration(log).subflow_name ||
|
||||||
|
'-'
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono">{{ getLogMeta(log).agent_code }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
{{ $t('ai-platform.workflowRuns.detail.model') }}
|
{{ $t('ai-platform.workflowRuns.detail.model') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="break-words">
|
<div class="break-words">
|
||||||
{{ getLogMeta(log).model || getLogMeta(log).model_id || '-' }}
|
{{
|
||||||
|
getLogMeta(log).model ||
|
||||||
|
getCollaboration(log).model ||
|
||||||
|
getLogMeta(log).model_id ||
|
||||||
|
getCollaboration(log).model_id ||
|
||||||
|
'-'
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -502,6 +575,15 @@ defineExpose({ open });
|
|||||||
{{ getLogMeta(log).completion_tokens || 0 }}
|
{{ getLogMeta(log).completion_tokens || 0 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="getCommunication(log).message"
|
||||||
|
class="col-span-2"
|
||||||
|
>
|
||||||
|
<div class="text-muted-foreground">通信摘要</div>
|
||||||
|
<div class="break-words">
|
||||||
|
{{ getCommunication(log).message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground mb-1 font-medium">
|
<div class="text-muted-foreground mb-1 font-medium">
|
||||||
|
|||||||
Reference in New Issue
Block a user