feat: expose agent collaboration metadata
This commit is contained in:
@@ -27,6 +27,26 @@ class AgentService:
|
|||||||
self._db = db
|
self._db = db
|
||||||
self.llm_service = LLMService(db)
|
self.llm_service = LLMService(db)
|
||||||
|
|
||||||
|
def _agent_collaboration_metadata(self, agent: Agent) -> Dict[str, Any]:
|
||||||
|
metadata = {
|
||||||
|
"agent_id": str(agent.id),
|
||||||
|
"agent_code": agent.code,
|
||||||
|
"agent_name": agent.name,
|
||||||
|
"collaboration_role": agent.name or agent.code,
|
||||||
|
"collaboration_mode": agent.mode,
|
||||||
|
}
|
||||||
|
if agent.model_id:
|
||||||
|
metadata["model_id"] = str(agent.model_id)
|
||||||
|
return {key: value for key, value in metadata.items() if value}
|
||||||
|
|
||||||
|
def _attach_agent_collaboration(self, event: Dict[str, Any], agent: Agent) -> Dict[str, Any]:
|
||||||
|
enriched = dict(event)
|
||||||
|
collaboration = dict(enriched.get("collaboration") or {})
|
||||||
|
for key, value in self._agent_collaboration_metadata(agent).items():
|
||||||
|
collaboration.setdefault(key, value)
|
||||||
|
enriched["collaboration"] = collaboration
|
||||||
|
return enriched
|
||||||
|
|
||||||
async def chat(
|
async def chat(
|
||||||
self,
|
self,
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
@@ -52,12 +72,15 @@ class AgentService:
|
|||||||
"""
|
"""
|
||||||
if agent.mode == 'autonomous':
|
if agent.mode == 'autonomous':
|
||||||
async for event in self._chat_autonomous(agent, conversation, user_message, attachments):
|
async for event in self._chat_autonomous(agent, conversation, user_message, attachments):
|
||||||
yield event
|
yield self._attach_agent_collaboration(event, agent)
|
||||||
elif agent.mode == 'dialog_flow':
|
elif agent.mode == 'dialog_flow':
|
||||||
async for event in self._chat_dialog_flow(agent, conversation, user_message, application_id, form_code, attachments):
|
async for event in self._chat_dialog_flow(agent, conversation, user_message, application_id, form_code, attachments):
|
||||||
yield event
|
yield self._attach_agent_collaboration(event, agent)
|
||||||
else:
|
else:
|
||||||
yield {'type': 'error', 'content': f'不支持的模式: {agent.mode}'}
|
yield self._attach_agent_collaboration(
|
||||||
|
{'type': 'error', 'content': f'不支持的模式: {agent.mode}'},
|
||||||
|
agent,
|
||||||
|
)
|
||||||
|
|
||||||
async def _chat_autonomous(
|
async def _chat_autonomous(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -50,16 +50,63 @@ 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 _node_collaboration_metadata(node_map: Dict[str, Any], node_id: str, node_type: str) -> dict:
|
||||||
|
node = node_map.get(node_id) or {}
|
||||||
|
data = node.get('data') or {}
|
||||||
|
metadata: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
agent_code = (data.get('agent_code') or '').strip()
|
||||||
|
if agent_code:
|
||||||
|
metadata['agent_code'] = agent_code
|
||||||
|
if data.get('agent_name'):
|
||||||
|
metadata['agent_name'] = data.get('agent_name')
|
||||||
|
|
||||||
|
model_id = data.get('model_id')
|
||||||
|
if model_id:
|
||||||
|
metadata['model_id'] = str(model_id)
|
||||||
|
|
||||||
|
if data.get('subflow_name'):
|
||||||
|
metadata['subflow_name'] = data.get('subflow_name')
|
||||||
|
if data.get('show_subflow_messages') is not None:
|
||||||
|
metadata['show_subflow_messages'] = data.get('show_subflow_messages')
|
||||||
|
if data.get('forward_interactive') is not None:
|
||||||
|
metadata['forward_interactive'] = data.get('forward_interactive')
|
||||||
|
|
||||||
|
branches = data.get('branches') or []
|
||||||
|
if node_type == 'parallel' and branches:
|
||||||
|
metadata['branches'] = [
|
||||||
|
{
|
||||||
|
'id': branch.get('id'),
|
||||||
|
'name': branch.get('name') or branch.get('id'),
|
||||||
|
}
|
||||||
|
for branch in branches
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
node_type: str,
|
node_type: str,
|
||||||
**extra: Any,
|
**extra: Any,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
metadata = dict(extra.pop('metadata', {}) or {})
|
||||||
|
collaboration = _node_collaboration_metadata(node_map, node_id, node_type)
|
||||||
|
if collaboration:
|
||||||
|
metadata['collaboration'] = collaboration
|
||||||
return {
|
return {
|
||||||
'node_id': node_id,
|
'node_id': node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'node_label': _node_label_from_map(node_map, node_id),
|
'node_label': _node_label_from_map(node_map, node_id),
|
||||||
|
'metadata': metadata,
|
||||||
**extra,
|
**extra,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +115,55 @@ def _node_result_metadata(result: NodeResult) -> dict:
|
|||||||
return dict(result.metadata or {})
|
return dict(result.metadata or {})
|
||||||
|
|
||||||
|
|
||||||
|
def _event_collaboration_metadata(
|
||||||
|
node_map: Dict[str, Any],
|
||||||
|
node_id: str,
|
||||||
|
node_type: str,
|
||||||
|
result: Optional[NodeResult] = None,
|
||||||
|
) -> dict:
|
||||||
|
metadata = _node_collaboration_metadata(node_map, node_id, node_type)
|
||||||
|
if not result:
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
result_metadata = result.metadata or {}
|
||||||
|
for key in (
|
||||||
|
'agent_code',
|
||||||
|
'agent_name',
|
||||||
|
'model',
|
||||||
|
'model_id',
|
||||||
|
'output_variable',
|
||||||
|
'subflow_name',
|
||||||
|
):
|
||||||
|
value = result_metadata.get(key)
|
||||||
|
if value is not None and value != '':
|
||||||
|
metadata[key] = value
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
|
def _parallel_branch_labels(node_map: Dict[str, Any], parallel_node_id: str) -> Dict[str, str]:
|
||||||
|
node = node_map.get(parallel_node_id) or {}
|
||||||
|
branches = (node.get('data') or {}).get('branches') or []
|
||||||
|
labels: Dict[str, str] = {}
|
||||||
|
for branch in branches:
|
||||||
|
branch_id = branch.get('id')
|
||||||
|
if branch_id:
|
||||||
|
labels[branch_id] = branch.get('name') or branch_id
|
||||||
|
return labels
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_event_collaboration(
|
||||||
|
event: Dict[str, Any],
|
||||||
|
node_map: Dict[str, Any],
|
||||||
|
node_id: str,
|
||||||
|
node_type: str,
|
||||||
|
result: Optional[NodeResult] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
collaboration = _event_collaboration_metadata(node_map, node_id, node_type, result)
|
||||||
|
if collaboration:
|
||||||
|
event['collaboration'] = collaboration
|
||||||
|
return event
|
||||||
|
|
||||||
|
|
||||||
def _snapshot_node_inputs(context: NodeContext) -> dict:
|
def _snapshot_node_inputs(context: NodeContext) -> dict:
|
||||||
return {
|
return {
|
||||||
'variables': dict(context.variables),
|
'variables': dict(context.variables),
|
||||||
@@ -691,6 +787,7 @@ class AIWorkflowService:
|
|||||||
all_logs = []
|
all_logs = []
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
total_steps = 0
|
total_steps = 0
|
||||||
|
branch_labels = _parallel_branch_labels(node_map, parallel_node_id)
|
||||||
|
|
||||||
async def execute_branch(branch_id: str, branch_start_id: str, branch_context: NodeContext) -> Dict:
|
async def execute_branch(branch_id: str, branch_start_id: str, branch_context: NodeContext) -> Dict:
|
||||||
"""执行单个分支(异步)"""
|
"""执行单个分支(异步)"""
|
||||||
@@ -723,17 +820,19 @@ class AIWorkflowService:
|
|||||||
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)
|
||||||
|
|
||||||
branch_logs.append({
|
branch_logs.append(_make_execution_log_entry(
|
||||||
'node_id': current_id,
|
node_map,
|
||||||
'node_type': node_type,
|
current_id,
|
||||||
'status': 'completed' if result.success else 'failed',
|
node_type,
|
||||||
'output': result.output,
|
status='completed' if result.success else 'failed',
|
||||||
'error': result.error,
|
output=result.output,
|
||||||
'elapsed_time': elapsed,
|
error=result.error,
|
||||||
'tokens_used': result.tokens_used,
|
elapsed_time=elapsed,
|
||||||
'metadata': _node_result_metadata(result),
|
tokens_used=result.tokens_used,
|
||||||
'branch': branch_start_id,
|
metadata=_node_result_metadata(result),
|
||||||
})
|
branch=branch_id,
|
||||||
|
branch_label=branch_labels.get(branch_id, branch_id),
|
||||||
|
))
|
||||||
|
|
||||||
branch_tokens += result.tokens_used
|
branch_tokens += result.tokens_used
|
||||||
branch_steps += 1
|
branch_steps += 1
|
||||||
@@ -881,6 +980,7 @@ class AIWorkflowService:
|
|||||||
all_logs = []
|
all_logs = []
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
total_steps = 0
|
total_steps = 0
|
||||||
|
branch_labels = _parallel_branch_labels(node_map, parallel_node_id)
|
||||||
|
|
||||||
target_to_branch = {v: k for k, v in branch_mapping.items()}
|
target_to_branch = {v: k for k, v in branch_mapping.items()}
|
||||||
|
|
||||||
@@ -921,45 +1021,49 @@ class AIWorkflowService:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 发送节点开始事件
|
# 发送节点开始事件
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_start',
|
'type': 'node_start',
|
||||||
'node_id': current_id,
|
'node_id': current_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'node_label': node_label,
|
'node_label': node_label,
|
||||||
'branch_id': branch_id,
|
'branch_id': branch_id,
|
||||||
|
'branch_label': branch_labels.get(branch_id, branch_id),
|
||||||
'timestamp': datetime.now().isoformat(),
|
'timestamp': datetime.now().isoformat(),
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'variables': dict(branch_context.variables),
|
'variables': dict(branch_context.variables),
|
||||||
'previous_output': branch_context.previous_output,
|
'previous_output': branch_context.previous_output,
|
||||||
},
|
},
|
||||||
}
|
}, node_map, current_id, node_type)
|
||||||
|
|
||||||
# 执行节点(异步)
|
# 执行节点(异步)
|
||||||
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)
|
||||||
|
|
||||||
branch_logs.append({
|
branch_logs.append(_make_execution_log_entry(
|
||||||
'node_id': current_id,
|
node_map,
|
||||||
'node_type': node_type,
|
current_id,
|
||||||
'status': 'completed' if result.success else 'failed',
|
node_type,
|
||||||
'output': result.output,
|
status='completed' if result.success else 'failed',
|
||||||
'error': result.error,
|
output=result.output,
|
||||||
'elapsed_time': elapsed,
|
error=result.error,
|
||||||
'tokens_used': result.tokens_used,
|
elapsed_time=elapsed,
|
||||||
'metadata': _node_result_metadata(result),
|
tokens_used=result.tokens_used,
|
||||||
'branch': branch_id,
|
metadata=_node_result_metadata(result),
|
||||||
})
|
branch=branch_id,
|
||||||
|
branch_label=branch_labels.get(branch_id, branch_id),
|
||||||
|
))
|
||||||
|
|
||||||
branch_tokens += result.tokens_used
|
branch_tokens += result.tokens_used
|
||||||
branch_steps += 1
|
branch_steps += 1
|
||||||
|
|
||||||
# 发送节点完成事件
|
# 发送节点完成事件
|
||||||
yield _apply_node_result_warnings({
|
yield _merge_event_collaboration(_apply_node_result_warnings({
|
||||||
'type': 'node_complete',
|
'type': 'node_complete',
|
||||||
'node_id': current_id,
|
'node_id': current_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'branch_id': branch_id,
|
'branch_id': branch_id,
|
||||||
|
'branch_label': branch_labels.get(branch_id, branch_id),
|
||||||
'status': 'success' if result.success else 'failed',
|
'status': 'success' if result.success else 'failed',
|
||||||
'elapsed_time': elapsed,
|
'elapsed_time': elapsed,
|
||||||
'tokens_used': result.tokens_used,
|
'tokens_used': result.tokens_used,
|
||||||
@@ -968,7 +1072,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),
|
||||||
},
|
},
|
||||||
}, result)
|
}, result), node_map, current_id, node_type, result)
|
||||||
|
|
||||||
if not result.success:
|
if not result.success:
|
||||||
break
|
break
|
||||||
@@ -1205,7 +1309,7 @@ class AIWorkflowService:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 发送节点开始事件
|
# 发送节点开始事件
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_start',
|
'type': 'node_start',
|
||||||
'node_id': current_id,
|
'node_id': current_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
@@ -1219,7 +1323,7 @@ class AIWorkflowService:
|
|||||||
'_loop_total': context.get_variable('_loop_total'),
|
'_loop_total': context.get_variable('_loop_total'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}, node_map, current_id, node_type)
|
||||||
|
|
||||||
# 执行节点(异步)
|
# 执行节点(异步)
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -1228,23 +1332,24 @@ class AIWorkflowService:
|
|||||||
elapsed = int((time.time() - start_time) * 1000)
|
elapsed = int((time.time() - start_time) * 1000)
|
||||||
logger.info(f'[Loop] Child node result: success={result.success}, output={result.output}, error={result.error}')
|
logger.info(f'[Loop] Child node result: success={result.success}, output={result.output}, error={result.error}')
|
||||||
|
|
||||||
iteration_logs.append({
|
iteration_logs.append(_make_execution_log_entry(
|
||||||
'node_id': current_id,
|
node_map,
|
||||||
'node_type': node_type,
|
current_id,
|
||||||
'status': 'completed' if result.success else 'failed',
|
node_type,
|
||||||
'output': result.output,
|
status='completed' if result.success else 'failed',
|
||||||
'error': result.error,
|
output=result.output,
|
||||||
'elapsed_time': elapsed,
|
error=result.error,
|
||||||
'tokens_used': result.tokens_used,
|
elapsed_time=elapsed,
|
||||||
'metadata': _node_result_metadata(result),
|
tokens_used=result.tokens_used,
|
||||||
'loop_iteration': iteration,
|
metadata=_node_result_metadata(result),
|
||||||
})
|
loop_iteration=iteration,
|
||||||
|
))
|
||||||
|
|
||||||
iteration_tokens += result.tokens_used
|
iteration_tokens += result.tokens_used
|
||||||
iteration_steps += 1
|
iteration_steps += 1
|
||||||
|
|
||||||
# 发送节点执行完成事件
|
# 发送节点执行完成事件
|
||||||
yield _apply_node_result_warnings({
|
yield _merge_event_collaboration(_apply_node_result_warnings({
|
||||||
'type': 'node_complete',
|
'type': 'node_complete',
|
||||||
'node_id': current_id,
|
'node_id': current_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
@@ -1265,7 +1370,7 @@ class AIWorkflowService:
|
|||||||
'_loop_total': context.get_variable('_loop_total'),
|
'_loop_total': context.get_variable('_loop_total'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, result)
|
}, result), node_map, current_id, node_type, result)
|
||||||
|
|
||||||
# 发送节点产生的事件(如消息事件)
|
# 发送节点产生的事件(如消息事件)
|
||||||
if result.events:
|
if result.events:
|
||||||
@@ -1280,13 +1385,14 @@ class AIWorkflowService:
|
|||||||
# 检查是否需要等待用户输入(设计预览节点等)
|
# 检查是否需要等待用户输入(设计预览节点等)
|
||||||
if result.waiting_for_input:
|
if result.waiting_for_input:
|
||||||
# 记录日志
|
# 记录日志
|
||||||
iteration_logs.append({
|
iteration_logs.append(_make_execution_log_entry(
|
||||||
'node_id': current_id,
|
node_map,
|
||||||
'node_type': node_type,
|
current_id,
|
||||||
'status': 'waiting',
|
node_type,
|
||||||
'elapsed_time': elapsed,
|
status='waiting',
|
||||||
'loop_iteration': iteration,
|
elapsed_time=elapsed,
|
||||||
})
|
loop_iteration=iteration,
|
||||||
|
))
|
||||||
|
|
||||||
# 更新运行记录为等待状态
|
# 更新运行记录为等待状态
|
||||||
run.status = 'waiting'
|
run.status = 'waiting'
|
||||||
@@ -1304,13 +1410,13 @@ class AIWorkflowService:
|
|||||||
await self._db.commit()
|
await self._db.commit()
|
||||||
|
|
||||||
# 发送等待事件
|
# 发送等待事件
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'waiting_input',
|
'type': 'waiting_input',
|
||||||
'node_id': current_id,
|
'node_id': current_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'config': result.waiting_config,
|
'config': result.waiting_config,
|
||||||
'loop_iteration': iteration,
|
'loop_iteration': iteration,
|
||||||
}
|
}, node_map, current_id, node_type, result)
|
||||||
# 暂停循环执行,等待用户输入
|
# 暂停循环执行,等待用户输入
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1574,14 +1680,14 @@ class AIWorkflowService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 发送节点开始事件(包含时间戳和输入数据)
|
# 发送节点开始事件(包含时间戳和输入数据)
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_start',
|
'type': 'node_start',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'node_label': node_label,
|
'node_label': node_label,
|
||||||
'timestamp': datetime.now().isoformat(),
|
'timestamp': datetime.now().isoformat(),
|
||||||
'inputs': node_inputs,
|
'inputs': node_inputs,
|
||||||
}
|
}, node_map, current_node_id, node_type)
|
||||||
|
|
||||||
# 结束节点
|
# 结束节点
|
||||||
if node_type == 'end':
|
if node_type == 'end':
|
||||||
@@ -1597,14 +1703,14 @@ class AIWorkflowService:
|
|||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
))
|
))
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_complete',
|
'type': 'node_complete',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'elapsed_time': 0,
|
'elapsed_time': 0,
|
||||||
'tokens_used': 0,
|
'tokens_used': 0,
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
# 保存结束节点输出和上下文变量到运行记录
|
# 保存结束节点输出和上下文变量到运行记录
|
||||||
run.execution_log = logs.copy()
|
run.execution_log = logs.copy()
|
||||||
@@ -1659,12 +1765,12 @@ class AIWorkflowService:
|
|||||||
)
|
)
|
||||||
# 发送 LLM 流式内容事件
|
# 发送 LLM 流式内容事件
|
||||||
if chunk_event.content:
|
if chunk_event.content:
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'llm_chunk',
|
'type': 'llm_chunk',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'content': chunk_event.content,
|
'content': chunk_event.content,
|
||||||
'accumulated_content': chunk_event.accumulated_content,
|
'accumulated_content': chunk_event.accumulated_content,
|
||||||
}
|
}, node_map, current_node_id, node_type)
|
||||||
except StopIteration as e:
|
except StopIteration as e:
|
||||||
# 生成器结束,获取返回值(NodeResult)
|
# 生成器结束,获取返回值(NodeResult)
|
||||||
result = e.value
|
result = e.value
|
||||||
@@ -1682,18 +1788,22 @@ class AIWorkflowService:
|
|||||||
for event in result.events:
|
for event in result.events:
|
||||||
if event.get('type') == 'message':
|
if event.get('type') == 'message':
|
||||||
# 消息事件直接发送为 answer 类型
|
# 消息事件直接发送为 answer 类型
|
||||||
yield {
|
answer_event = _merge_event_collaboration({
|
||||||
'type': 'answer',
|
'type': 'answer',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'content': event.get('content', ''),
|
'content': event.get('content', ''),
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
if event.get('from_subflow'):
|
||||||
|
answer_event['from_subflow'] = True
|
||||||
|
answer_event['subflow_name'] = event.get('subflow_name')
|
||||||
|
yield answer_event
|
||||||
else:
|
else:
|
||||||
# 其他事件保持 node_event 格式
|
# 其他事件保持 node_event 格式
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_event',
|
'type': 'node_event',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'event': event,
|
'event': event,
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
# 检查是否需要等待用户输入(对话流节点)
|
# 检查是否需要等待用户输入(对话流节点)
|
||||||
if result.waiting_for_input:
|
if result.waiting_for_input:
|
||||||
@@ -1723,12 +1833,12 @@ class AIWorkflowService:
|
|||||||
# 注意:save由调用方处理
|
# 注意:save由调用方处理
|
||||||
|
|
||||||
# 发送等待输入事件
|
# 发送等待输入事件
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'waiting_input',
|
'type': 'waiting_input',
|
||||||
'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,
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
# 暂停工作流执行,等待用户输入后续流
|
# 暂停工作流执行,等待用户输入后续流
|
||||||
return
|
return
|
||||||
@@ -1760,7 +1870,7 @@ class AIWorkflowService:
|
|||||||
# 注意:save由调用方处理
|
# 注意:save由调用方处理
|
||||||
|
|
||||||
# 发送节点完成事件(包含输出数据)
|
# 发送节点完成事件(包含输出数据)
|
||||||
yield _apply_node_result_warnings({
|
yield _merge_event_collaboration(_apply_node_result_warnings({
|
||||||
'type': 'node_complete',
|
'type': 'node_complete',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
@@ -1772,7 +1882,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),
|
||||||
},
|
},
|
||||||
}, result)
|
}, result), node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
if not result.success:
|
if not result.success:
|
||||||
raise ValueError(f'节点执行失败: {result.error}')
|
raise ValueError(f'节点执行失败: {result.error}')
|
||||||
@@ -1791,11 +1901,15 @@ class AIWorkflowService:
|
|||||||
if node_type == 'parallel':
|
if node_type == 'parallel':
|
||||||
# 并行节点:执行所有分支(流式)
|
# 并行节点:执行所有分支(流式)
|
||||||
branch_ids = list(parallel_edge_map.get(current_node_id, {}).keys()) or edge_map.get(current_node_id, [])
|
branch_ids = list(parallel_edge_map.get(current_node_id, {}).keys()) or edge_map.get(current_node_id, [])
|
||||||
yield {
|
branch_labels = _parallel_branch_labels(node_map, current_node_id)
|
||||||
|
yield _merge_event_collaboration({
|
||||||
'type': 'parallel_start',
|
'type': 'parallel_start',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'branches': branch_ids,
|
'branches': branch_ids,
|
||||||
}
|
'branch_count': len(branch_ids),
|
||||||
|
'branch_labels': branch_labels,
|
||||||
|
'collaboration_mode': 'parallel',
|
||||||
|
}, node_map, current_node_id, node_type)
|
||||||
|
|
||||||
# 使用流式版本的并行分支执行(异步)
|
# 使用流式版本的并行分支执行(异步)
|
||||||
async for event in self._execute_parallel_branches_stream(
|
async for event in self._execute_parallel_branches_stream(
|
||||||
@@ -1809,12 +1923,15 @@ class AIWorkflowService:
|
|||||||
total_steps += parallel_results['total_steps']
|
total_steps += parallel_results['total_steps']
|
||||||
logs.extend(parallel_results['logs'])
|
logs.extend(parallel_results['logs'])
|
||||||
|
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'parallel_complete',
|
'type': 'parallel_complete',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'branch_results': parallel_results['results'],
|
'branch_results': parallel_results['results'],
|
||||||
'total_tokens': parallel_results['total_tokens'],
|
'total_tokens': parallel_results['total_tokens'],
|
||||||
}
|
'branch_count': len(parallel_results['results']),
|
||||||
|
'branch_labels': branch_labels,
|
||||||
|
'collaboration_mode': 'parallel',
|
||||||
|
}, node_map, current_node_id, node_type)
|
||||||
|
|
||||||
# 找到合并节点继续执行
|
# 找到合并节点继续执行
|
||||||
current_node_id = parallel_results.get('merge_node_id')
|
current_node_id = parallel_results.get('merge_node_id')
|
||||||
@@ -2126,14 +2243,14 @@ class AIWorkflowService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 发送节点开始事件
|
# 发送节点开始事件
|
||||||
node_start_event = {
|
node_start_event = _merge_event_collaboration({
|
||||||
'type': 'node_start',
|
'type': 'node_start',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'node_label': node_label,
|
'node_label': node_label,
|
||||||
'timestamp': datetime.now().isoformat(),
|
'timestamp': datetime.now().isoformat(),
|
||||||
'inputs': node_inputs,
|
'inputs': node_inputs,
|
||||||
}
|
}, node_map, current_node_id, node_type)
|
||||||
# 如果在循环中,添加迭代信息
|
# 如果在循环中,添加迭代信息
|
||||||
if loop_state:
|
if loop_state:
|
||||||
node_start_event['loop_iteration'] = loop_state.get('iteration', 0)
|
node_start_event['loop_iteration'] = loop_state.get('iteration', 0)
|
||||||
@@ -2152,14 +2269,14 @@ class AIWorkflowService:
|
|||||||
output=result.output,
|
output=result.output,
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
))
|
))
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_complete',
|
'type': 'node_complete',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'elapsed_time': 0,
|
'elapsed_time': 0,
|
||||||
'outputs': {'output': result.output},
|
'outputs': {'output': result.output},
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
# 更新运行记录,保存结束节点的输出
|
# 更新运行记录,保存结束节点的输出
|
||||||
run.status = 'completed'
|
run.status = 'completed'
|
||||||
@@ -2191,29 +2308,34 @@ class AIWorkflowService:
|
|||||||
for event in result.events:
|
for event in result.events:
|
||||||
if event.get('type') == 'message':
|
if event.get('type') == 'message':
|
||||||
# 消息事件直接发送为 answer 类型
|
# 消息事件直接发送为 answer 类型
|
||||||
yield {
|
answer_event = _merge_event_collaboration({
|
||||||
'type': 'answer',
|
'type': 'answer',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'content': event.get('content', ''),
|
'content': event.get('content', ''),
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
if event.get('from_subflow'):
|
||||||
|
answer_event['from_subflow'] = True
|
||||||
|
answer_event['subflow_name'] = event.get('subflow_name')
|
||||||
|
yield answer_event
|
||||||
else:
|
else:
|
||||||
# 其他事件保持 node_event 格式
|
# 其他事件保持 node_event 格式
|
||||||
yield {
|
yield _merge_event_collaboration({
|
||||||
'type': 'node_event',
|
'type': 'node_event',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'event': event,
|
'event': event,
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
|
|
||||||
# 检查是否需要等待用户输入
|
# 检查是否需要等待用户输入
|
||||||
if result.waiting_for_input:
|
if result.waiting_for_input:
|
||||||
log_entry = {
|
log_entry = _make_execution_log_entry(
|
||||||
'node_id': current_node_id,
|
node_map,
|
||||||
'node_type': node_type,
|
current_node_id,
|
||||||
'status': 'waiting',
|
node_type,
|
||||||
'output': result.output,
|
status='waiting',
|
||||||
'elapsed_time': elapsed,
|
output=result.output,
|
||||||
'metadata': _node_result_metadata(result),
|
elapsed_time=elapsed,
|
||||||
}
|
metadata=_node_result_metadata(result),
|
||||||
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|
||||||
run.status = 'waiting'
|
run.status = 'waiting'
|
||||||
@@ -2226,12 +2348,12 @@ class AIWorkflowService:
|
|||||||
run.waiting_config['_loop_state'] = loop_state
|
run.waiting_config['_loop_state'] = loop_state
|
||||||
# 注意:save由调用方处理
|
# 注意:save由调用方处理
|
||||||
|
|
||||||
waiting_event = {
|
waiting_event = _merge_event_collaboration({
|
||||||
'type': 'waiting_input',
|
'type': 'waiting_input',
|
||||||
'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,
|
||||||
}
|
}, node_map, current_node_id, node_type, result)
|
||||||
# 如果在循环中,添加迭代信息
|
# 如果在循环中,添加迭代信息
|
||||||
if loop_state:
|
if loop_state:
|
||||||
waiting_event['loop_iteration'] = loop_state.get('iteration', 0)
|
waiting_event['loop_iteration'] = loop_state.get('iteration', 0)
|
||||||
@@ -2239,16 +2361,17 @@ class AIWorkflowService:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# 记录日志
|
# 记录日志
|
||||||
log_entry = {
|
log_entry = _make_execution_log_entry(
|
||||||
'node_id': current_node_id,
|
node_map,
|
||||||
'node_type': node_type,
|
current_node_id,
|
||||||
'status': 'completed' if result.success else 'failed',
|
node_type,
|
||||||
'output': result.output,
|
status='completed' if result.success else 'failed',
|
||||||
'error': result.error,
|
output=result.output,
|
||||||
'elapsed_time': elapsed,
|
error=result.error,
|
||||||
'tokens_used': result.tokens_used,
|
elapsed_time=elapsed,
|
||||||
'metadata': _node_result_metadata(result),
|
tokens_used=result.tokens_used,
|
||||||
}
|
metadata=_node_result_metadata(result),
|
||||||
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|
||||||
total_tokens += result.tokens_used
|
total_tokens += result.tokens_used
|
||||||
@@ -2266,7 +2389,7 @@ class AIWorkflowService:
|
|||||||
# 注意:save由调用方处理
|
# 注意:save由调用方处理
|
||||||
|
|
||||||
# 发送节点完成事件
|
# 发送节点完成事件
|
||||||
node_complete_event = _apply_node_result_warnings({
|
node_complete_event = _merge_event_collaboration(_apply_node_result_warnings({
|
||||||
'type': 'node_complete',
|
'type': 'node_complete',
|
||||||
'node_id': current_node_id,
|
'node_id': current_node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
@@ -2278,7 +2401,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),
|
||||||
},
|
},
|
||||||
}, result)
|
}, result), node_map, current_node_id, node_type, result)
|
||||||
# 如果在循环中,添加迭代信息
|
# 如果在循环中,添加迭代信息
|
||||||
if loop_state:
|
if loop_state:
|
||||||
node_complete_event['loop_iteration'] = loop_state.get('iteration', 0)
|
node_complete_event['loop_iteration'] = loop_state.get('iteration', 0)
|
||||||
|
|||||||
@@ -192,6 +192,39 @@ const getStepColor = (step: ReasoningStep) => {
|
|||||||
return colors[step.type] || 'text-gray-500';
|
return colors[step.type] || 'text-gray-500';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStepMetaItems = (step: ReasoningStep) => {
|
||||||
|
const items: string[] = [];
|
||||||
|
if (step.agent_name || step.agent_code) {
|
||||||
|
items.push(`智能体: ${step.agent_name || step.agent_code}`);
|
||||||
|
}
|
||||||
|
if (step.branch_label || step.branch_id) {
|
||||||
|
items.push(`分支: ${step.branch_label || step.branch_id}`);
|
||||||
|
}
|
||||||
|
if (step.subflow_name) {
|
||||||
|
items.push(`子流程: ${step.subflow_name}`);
|
||||||
|
}
|
||||||
|
if (step.model || step.model_id) {
|
||||||
|
items.push(`模型: ${step.model || step.model_id}`);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageMetaItems = computed(() => {
|
||||||
|
if (isUser.value) return [];
|
||||||
|
|
||||||
|
const items: string[] = [];
|
||||||
|
if (props.message.agent_name || props.message.agent_code) {
|
||||||
|
items.push(`智能体: ${props.message.agent_name || props.message.agent_code}`);
|
||||||
|
}
|
||||||
|
if (props.message.model_name || props.message.model_id) {
|
||||||
|
items.push(`模型: ${props.message.model_name || props.message.model_id}`);
|
||||||
|
}
|
||||||
|
if (props.message.collaboration_mode) {
|
||||||
|
items.push(`模式: ${props.message.collaboration_mode}`);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
// 格式化文件大小
|
// 格式化文件大小
|
||||||
const formatFileSize = (bytes?: number) => {
|
const formatFileSize = (bytes?: number) => {
|
||||||
if (!bytes) return '';
|
if (!bytes) return '';
|
||||||
@@ -293,6 +326,16 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 消息气泡 -->
|
<!-- 消息气泡 -->
|
||||||
|
<div v-if="messageMetaItems.length" class="chat-message-meta">
|
||||||
|
<span
|
||||||
|
v-for="item in messageMetaItems"
|
||||||
|
:key="item"
|
||||||
|
class="chat-message-meta-item"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="chat-bubble" :class="{ 'is-error': isFailed }">
|
<div class="chat-bubble" :class="{ 'is-error': isFailed }">
|
||||||
<!-- 执行步骤(执行中展开显示,完成后折叠) -->
|
<!-- 执行步骤(执行中展开显示,完成后折叠) -->
|
||||||
<div v-if="message.reasoning_steps?.length" class="bubble-steps">
|
<div v-if="message.reasoning_steps?.length" class="bubble-steps">
|
||||||
@@ -320,6 +363,15 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
<span class="step-text" :title="step.content">
|
<span class="step-text" :title="step.content">
|
||||||
{{ step.content }}
|
{{ step.content }}
|
||||||
</span>
|
</span>
|
||||||
|
<div v-if="getStepMetaItems(step).length" class="step-meta">
|
||||||
|
<span
|
||||||
|
v-for="item in getStepMetaItems(step)"
|
||||||
|
:key="item"
|
||||||
|
class="step-meta-item"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -608,6 +660,27 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 推理步骤 */
|
/* 推理步骤 */
|
||||||
|
.chat-message-meta {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message-meta-item {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-reasoning {
|
.chat-reasoning {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -1257,9 +1330,11 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bubble-step {
|
.bubble-step {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
column-gap: 8px;
|
||||||
|
row-gap: 4px;
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
@@ -1285,4 +1360,24 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.step-meta {
|
||||||
|
grid-column: 2;
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-meta-item {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ export interface ReasoningStep {
|
|||||||
result?: any;
|
result?: any;
|
||||||
node_id?: string;
|
node_id?: string;
|
||||||
node_type?: string;
|
node_type?: string;
|
||||||
|
branch_id?: string;
|
||||||
|
branch_label?: string;
|
||||||
|
agent_code?: string;
|
||||||
|
agent_name?: string;
|
||||||
|
model?: string;
|
||||||
|
model_id?: string;
|
||||||
|
subflow_name?: string;
|
||||||
|
from_subflow?: boolean;
|
||||||
|
collaboration_role?: string;
|
||||||
|
collaboration_mode?: string;
|
||||||
output?: any;
|
output?: any;
|
||||||
/** 步骤状态:running 执行中,completed 已完成 */
|
/** 步骤状态:running 执行中,completed 已完成 */
|
||||||
status?: 'completed' | 'running';
|
status?: 'completed' | 'running';
|
||||||
@@ -137,6 +147,10 @@ export interface ChatMessage {
|
|||||||
error_message?: string;
|
error_message?: string;
|
||||||
feedback?: 'dislike' | 'like' | null;
|
feedback?: 'dislike' | 'like' | null;
|
||||||
model_name?: string;
|
model_name?: string;
|
||||||
|
agent_code?: string;
|
||||||
|
agent_name?: string;
|
||||||
|
collaboration_mode?: string;
|
||||||
|
model_id?: string;
|
||||||
/** 语音消息 */
|
/** 语音消息 */
|
||||||
voice?: VoiceMessage;
|
voice?: VoiceMessage;
|
||||||
/** 对话流交互配置(等待用户输入时) */
|
/** 对话流交互配置(等待用户输入时) */
|
||||||
|
|||||||
@@ -170,6 +170,48 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildReasoningMeta = (event: StreamEvent): Partial<ReasoningStep> => {
|
||||||
|
const collaboration = event.collaboration || {};
|
||||||
|
return {
|
||||||
|
branch_id: event.branch_id,
|
||||||
|
branch_label: event.branch_label,
|
||||||
|
agent_code: collaboration.agent_code || event.agent_code,
|
||||||
|
agent_name: collaboration.agent_name || event.agent_name,
|
||||||
|
model: collaboration.model || event.model,
|
||||||
|
model_id: collaboration.model_id || event.model_id,
|
||||||
|
subflow_name:
|
||||||
|
event.subflow_name || collaboration.subflow_name || event.event?.subflow_name,
|
||||||
|
from_subflow: Boolean(
|
||||||
|
event.from_subflow || collaboration.from_subflow || event.event?.from_subflow,
|
||||||
|
),
|
||||||
|
collaboration_role:
|
||||||
|
collaboration.collaboration_role || collaboration.role || event.collaboration_role,
|
||||||
|
collaboration_mode: event.collaboration_mode || collaboration.collaboration_mode,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMessageMeta = (event: StreamEvent): Partial<ChatMessage> => {
|
||||||
|
const collaboration = event.collaboration || {};
|
||||||
|
const modelName =
|
||||||
|
event.model ||
|
||||||
|
event.model_name ||
|
||||||
|
collaboration.model ||
|
||||||
|
collaboration.model_id;
|
||||||
|
return {
|
||||||
|
...(modelName ? { model_name: modelName } : {}),
|
||||||
|
...(collaboration.model_id ? { model_id: collaboration.model_id } : {}),
|
||||||
|
...(collaboration.agent_code
|
||||||
|
? { agent_code: collaboration.agent_code }
|
||||||
|
: {}),
|
||||||
|
...(collaboration.agent_name
|
||||||
|
? { agent_name: collaboration.agent_name }
|
||||||
|
: {}),
|
||||||
|
...(collaboration.collaboration_mode
|
||||||
|
? { collaboration_mode: collaboration.collaboration_mode }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/** 处理流式事件 */
|
/** 处理流式事件 */
|
||||||
const handleStreamEvent = (event: StreamEvent, msgId: string) => {
|
const handleStreamEvent = (event: StreamEvent, msgId: string) => {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@@ -194,6 +236,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
updateAssistantMessage(msgId, {
|
updateAssistantMessage(msgId, {
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
content: event.content || '',
|
content: event.content || '',
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const answerMsg: ChatMessage = {
|
const answerMsg: ChatMessage = {
|
||||||
@@ -202,6 +245,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: event.content || '',
|
content: event.content || '',
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
|
...buildMessageMeta(event),
|
||||||
};
|
};
|
||||||
messages.value.push(answerMsg);
|
messages.value.push(answerMsg);
|
||||||
}
|
}
|
||||||
@@ -228,6 +272,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: endOutput,
|
content: endOutput,
|
||||||
elapsed_time: event.elapsed_time,
|
elapsed_time: event.elapsed_time,
|
||||||
tokens_used: event.tokens_used || event.total_tokens,
|
tokens_used: event.tokens_used || event.total_tokens,
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const endMsg: ChatMessage = {
|
const endMsg: ChatMessage = {
|
||||||
@@ -238,6 +283,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
status: 'completed',
|
status: 'completed',
|
||||||
elapsed_time: event.elapsed_time,
|
elapsed_time: event.elapsed_time,
|
||||||
tokens_used: event.tokens_used || event.total_tokens,
|
tokens_used: event.tokens_used || event.total_tokens,
|
||||||
|
...buildMessageMeta(event),
|
||||||
};
|
};
|
||||||
messages.value.push(endMsg);
|
messages.value.push(endMsg);
|
||||||
}
|
}
|
||||||
@@ -259,6 +305,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
elapsed_time: event.elapsed_time,
|
elapsed_time: event.elapsed_time,
|
||||||
tokens_used: event.tokens_used || event.total_tokens,
|
tokens_used: event.tokens_used || event.total_tokens,
|
||||||
content: '工作流执行完成',
|
content: '工作流执行完成',
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (initialMsg) {
|
} else if (initialMsg) {
|
||||||
@@ -267,6 +314,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
status: 'completed',
|
status: 'completed',
|
||||||
elapsed_time: event.elapsed_time,
|
elapsed_time: event.elapsed_time,
|
||||||
tokens_used: event.tokens_used || event.total_tokens,
|
tokens_used: event.tokens_used || event.total_tokens,
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +331,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: event.message || event.content || '执行失败',
|
content: event.message || event.content || '执行失败',
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: event.node_type,
|
node_type: event.node_type,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -302,6 +351,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
updateAssistantMessage(msgId, {
|
updateAssistantMessage(msgId, {
|
||||||
content: event.accumulated_content || event.content || '',
|
content: event.accumulated_content || event.content || '',
|
||||||
status: 'streaming',
|
status: 'streaming',
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 如果消息不存在,创建新消息
|
// 如果消息不存在,创建新消息
|
||||||
@@ -311,6 +361,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: event.accumulated_content || event.content || '',
|
content: event.accumulated_content || event.content || '',
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
status: 'streaming',
|
status: 'streaming',
|
||||||
|
...buildMessageMeta(event),
|
||||||
};
|
};
|
||||||
messages.value.push(streamMsg);
|
messages.value.push(streamMsg);
|
||||||
}
|
}
|
||||||
@@ -342,6 +393,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: '循环执行完成',
|
content: '循环执行完成',
|
||||||
node_id: loopEvent.node_id,
|
node_id: loopEvent.node_id,
|
||||||
node_type: 'loop',
|
node_type: 'loop',
|
||||||
|
...buildReasoningMeta(event),
|
||||||
output: outputs,
|
output: outputs,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -369,6 +421,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: `循环第 ${event.iteration || '-'} 次完成`,
|
content: `循环第 ${event.iteration || '-'} 次完成`,
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: 'loop',
|
node_type: 'loop',
|
||||||
|
...buildReasoningMeta(event),
|
||||||
output: event.output,
|
output: event.output,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -395,6 +448,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: `循环第 ${event.iteration || '-'} 次失败:${event.error || '执行失败'}`,
|
content: `循环第 ${event.iteration || '-'} 次失败:${event.error || '执行失败'}`,
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: 'loop',
|
node_type: 'loop',
|
||||||
|
...buildReasoningMeta(event),
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -420,6 +474,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: `循环第 ${event.iteration || '-'} 次开始`,
|
content: `循环第 ${event.iteration || '-'} 次开始`,
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: 'loop',
|
node_type: 'loop',
|
||||||
|
...buildReasoningMeta(event),
|
||||||
params: { item: event.item, total: event.total },
|
params: { item: event.item, total: event.total },
|
||||||
status: 'running',
|
status: 'running',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -455,6 +510,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: completeLabel,
|
content: completeLabel,
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: event.node_type,
|
node_type: event.node_type,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
output: event.outputs,
|
output: event.outputs,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -464,6 +520,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
...currentSteps.value[existingIndex]!,
|
...currentSteps.value[existingIndex]!,
|
||||||
type: 'node_complete',
|
type: 'node_complete',
|
||||||
content: completeLabel,
|
content: completeLabel,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
output: event.outputs,
|
output: event.outputs,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
};
|
};
|
||||||
@@ -501,6 +558,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content,
|
content,
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: 'parallel',
|
node_type: 'parallel',
|
||||||
|
...buildReasoningMeta(event),
|
||||||
output: {
|
output: {
|
||||||
branch_results: branchResults,
|
branch_results: branchResults,
|
||||||
total_tokens: event.total_tokens,
|
total_tokens: event.total_tokens,
|
||||||
@@ -513,6 +571,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
...currentSteps.value[existingIndex]!,
|
...currentSteps.value[existingIndex]!,
|
||||||
type: 'parallel_complete',
|
type: 'parallel_complete',
|
||||||
content,
|
content,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
output: {
|
output: {
|
||||||
branch_results: branchResults,
|
branch_results: branchResults,
|
||||||
total_tokens: event.total_tokens,
|
total_tokens: event.total_tokens,
|
||||||
@@ -533,7 +592,8 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: `并行分支开始:${getItemCount(branches)} 个分支`,
|
content: `并行分支开始:${getItemCount(branches)} 个分支`,
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: 'parallel',
|
node_type: 'parallel',
|
||||||
params: { branches },
|
...buildReasoningMeta(event),
|
||||||
|
params: { branches, branch_labels: event.branch_labels },
|
||||||
status: 'running',
|
status: 'running',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -551,6 +611,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
updateAssistantMessage(msgId, {
|
updateAssistantMessage(msgId, {
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
content: nodeEvent.content || '',
|
content: nodeEvent.content || '',
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const newMsg: ChatMessage = {
|
const newMsg: ChatMessage = {
|
||||||
@@ -559,6 +620,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
content: nodeEvent.content || '',
|
content: nodeEvent.content || '',
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
|
...buildMessageMeta(event),
|
||||||
};
|
};
|
||||||
messages.value.push(newMsg);
|
messages.value.push(newMsg);
|
||||||
}
|
}
|
||||||
@@ -581,6 +643,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
),
|
),
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: event.node_type,
|
node_type: event.node_type,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
status: 'running',
|
status: 'running',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -622,6 +685,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
'等待用户输入',
|
'等待用户输入',
|
||||||
node_id: event.node_id,
|
node_id: event.node_id,
|
||||||
node_type: event.node_type,
|
node_type: event.node_type,
|
||||||
|
...buildReasoningMeta(event),
|
||||||
params: configData,
|
params: configData,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
|
|||||||
@@ -91,13 +91,14 @@ const agentSummaries = computed(() => {
|
|||||||
|
|
||||||
for (const log of executionLogs.value) {
|
for (const log of executionLogs.value) {
|
||||||
const meta = getLogMeta(log);
|
const meta = getLogMeta(log);
|
||||||
const code = meta.agent_code;
|
const collaboration = meta.collaboration || {};
|
||||||
|
const code = meta.agent_code || collaboration.agent_code;
|
||||||
if (!code) continue;
|
if (!code) continue;
|
||||||
|
|
||||||
const current = agents.get(code) || {
|
const current = agents.get(code) || {
|
||||||
code,
|
code,
|
||||||
model: meta.model || meta.model_id || '-',
|
model: meta.model || collaboration.model || meta.model_id || collaboration.model_id || '-',
|
||||||
name: meta.agent_name || code,
|
name: meta.agent_name || collaboration.agent_name || code,
|
||||||
steps: 0,
|
steps: 0,
|
||||||
tokens: 0,
|
tokens: 0,
|
||||||
};
|
};
|
||||||
@@ -112,6 +113,43 @@ const agentSummaries = computed(() => {
|
|||||||
|
|
||||||
return [...agents.values()];
|
return [...agents.values()];
|
||||||
});
|
});
|
||||||
|
const collaborationTimeline = computed(() => {
|
||||||
|
return executionLogs.value
|
||||||
|
.map((log, index) => {
|
||||||
|
const meta = getLogMeta(log);
|
||||||
|
const collaboration = meta.collaboration || {};
|
||||||
|
const agentCode = meta.agent_code || collaboration.agent_code;
|
||||||
|
const agentName = meta.agent_name || collaboration.agent_name || agentCode;
|
||||||
|
const branch = log.branch_label || log.branch || collaboration.branch_label;
|
||||||
|
const subflowName = collaboration.subflow_name || meta.subflow_name;
|
||||||
|
const model = meta.model || collaboration.model || meta.model_id || collaboration.model_id;
|
||||||
|
const title =
|
||||||
|
agentName ||
|
||||||
|
subflowName ||
|
||||||
|
branch ||
|
||||||
|
log.node_label ||
|
||||||
|
log.node_id ||
|
||||||
|
`#${index + 1}`;
|
||||||
|
const summaryParts = [
|
||||||
|
branch ? `分支:${branch}` : '',
|
||||||
|
subflowName ? `子流程:${subflowName}` : '',
|
||||||
|
model ? `模型:${model}` : '',
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: `${log.node_id || 'node'}-${index}`,
|
||||||
|
nodeId: log.node_id,
|
||||||
|
title,
|
||||||
|
subtitle: log.node_label || log.node_id,
|
||||||
|
status: log.status,
|
||||||
|
summary: summaryParts.join(' · '),
|
||||||
|
elapsed: log.elapsed_time,
|
||||||
|
tokens: log.tokens_used || 0,
|
||||||
|
hasSignal: Boolean(agentName || branch || subflowName || model),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item) => item.hasSignal);
|
||||||
|
});
|
||||||
|
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
if (!runId.value) return;
|
if (!runId.value) return;
|
||||||
@@ -341,6 +379,40 @@ defineExpose({ open });
|
|||||||
<div v-else class="text-muted-foreground text-sm">
|
<div v-else class="text-muted-foreground text-sm">
|
||||||
{{ $t('ai-platform.workflowRuns.detail.noAgentLogs') }}
|
{{ $t('ai-platform.workflowRuns.detail.noAgentLogs') }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-3 border-t pt-3">
|
||||||
|
<div class="mb-2 flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium">协作通信</span>
|
||||||
|
<ElTag size="small">{{ collaborationTimeline.length }}</ElTag>
|
||||||
|
</div>
|
||||||
|
<div v-if="collaborationTimeline.length" class="collaboration-timeline">
|
||||||
|
<button
|
||||||
|
v-for="item in collaborationTimeline"
|
||||||
|
:key="item.key"
|
||||||
|
class="collaboration-item"
|
||||||
|
type="button"
|
||||||
|
@click="selectLog(item.nodeId)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="collaboration-dot"
|
||||||
|
:class="`is-${getLogStatusType(item.status)}`"
|
||||||
|
></span>
|
||||||
|
<span class="min-w-0">
|
||||||
|
<span class="block truncate text-xs font-medium">
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
<span class="text-muted-foreground block truncate text-xs">
|
||||||
|
{{ item.summary || item.subtitle }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-muted-foreground shrink-0 text-xs">
|
||||||
|
{{ formatDuration(item.elapsed) }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-muted-foreground text-sm">
|
||||||
|
暂无协作通信记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -509,6 +581,54 @@ defineExpose({ open });
|
|||||||
max-height: 140px;
|
max-height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-timeline {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 8px minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-item:hover {
|
||||||
|
border-color: var(--el-color-primary-light-5);
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--el-text-color-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-dot.is-success {
|
||||||
|
background: var(--el-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-dot.is-danger {
|
||||||
|
background: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-dot.is-warning {
|
||||||
|
background: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-run-detail-dialog .collaboration-dot.is-primary {
|
||||||
|
background: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.workflow-run-detail-dialog .run-report-grid,
|
.workflow-run-detail-dialog .run-report-grid,
|
||||||
.workflow-run-detail-dialog .run-detail-main {
|
.workflow-run-detail-dialog .run-detail-main {
|
||||||
|
|||||||
Reference in New Issue
Block a user