diff --git a/backend-fastapi/ai_platform/services/agent_service.py b/backend-fastapi/ai_platform/services/agent_service.py index 512ea06..93c3e12 100644 --- a/backend-fastapi/ai_platform/services/agent_service.py +++ b/backend-fastapi/ai_platform/services/agent_service.py @@ -43,6 +43,16 @@ class AgentService: 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 in ( + "model_id", + "model", + "model_name", + "provider_id", + "provider_name", + "provider_type", + ): + if enriched.get(key): + collaboration.setdefault(key, enriched[key]) for key, value in self._agent_collaboration_metadata(agent).items(): collaboration.setdefault(key, value) enriched["collaboration"] = collaboration @@ -55,6 +65,10 @@ class AgentService: "collaboration_role", "collaboration_mode", "model_id", + "model", + "model_name", + "provider_name", + "provider_type", ): if collaboration.get(key): actor.setdefault(key, collaboration[key]) @@ -247,6 +261,34 @@ class AgentService: model.id, ) return str(model.id) + + async def _get_model_runtime_meta(self, model_id: Optional[str]) -> Dict[str, Any]: + if not self._db or not model_id: + return {} + + result = await self._db.execute( + select(LLMModel, LLMProvider) + .join(LLMProvider, LLMProvider.id == LLMModel.provider_id) + .where( + LLMModel.id == model_id, + LLMModel.is_deleted == False, + LLMProvider.is_deleted == False, + ) + ) + row = result.first() + if not row: + return {"model_id": str(model_id)} + + model, provider = row[0], row[1] + display_model = model.display_name or model.model_name or str(model.id) + return { + "model_id": str(model.id), + "model": display_model, + "model_name": model.model_name or display_model, + "provider_id": str(provider.id), + "provider_name": provider.name or provider.provider_type, + "provider_type": provider.provider_type, + } async def _chat_autonomous_function_calling( self, @@ -264,6 +306,8 @@ class AgentService: from ai_platform.providers.base import LLMMessage start_time = time.time() + model_id: Optional[str] = None + model_meta: Dict[str, Any] = {} if not self._db: yield {'type': 'error', 'content': '数据库会话未初始化'} @@ -315,11 +359,13 @@ class AgentService: model_id = await self._resolve_agent_model_id(agent) if not model_id: raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型') + model_meta = await self._get_model_runtime_meta(model_id) event = self._attach_agent_collaboration({ 'type': 'thought', 'content': '智能体开始分析用户需求并调用默认 chat 模型', 'model_id': model_id, + **model_meta, }, agent) step = self._event_to_reasoning_step(event) if step: @@ -390,6 +436,7 @@ class AgentService: model_id = await self._resolve_agent_model_id(agent) if not model_id: raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型') + model_meta = await self._get_model_runtime_meta(model_id) total_tokens = 0 final_answer = '' @@ -410,6 +457,7 @@ class AgentService: 'content': chunk.content, 'accumulated_content': accumulated_content, 'model_id': model_id, + **model_meta, } if chunk.is_finished: @@ -431,6 +479,7 @@ class AgentService: 'type': 'answer', 'content': final_answer, 'model_id': model_id, + **model_meta, } # 更新助手消息 @@ -461,21 +510,20 @@ class AgentService: 'conversation_id': str(conversation.id), 'tokens_used': total_tokens, 'elapsed_time': elapsed_time, + **model_meta, } except Exception as e: logger.exception(f'Agent chat error: {e}') assistant_msg.status = 'failed' assistant_msg.error_message = str(e) - error_step = self._event_to_reasoning_step({'type': 'error', 'content': str(e)}) + error_event = {'type': 'error', 'content': str(e), **model_meta} + error_step = self._event_to_reasoning_step(error_event) if error_step: reasoning_steps.append(error_step) assistant_msg.reasoning_steps = reasoning_steps.copy() await self._db.commit() - yield { - 'type': 'error', - 'content': str(e), - } + yield error_event async def _chat_autonomous_react( self, diff --git a/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue b/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue index b0cc209..2fb4492 100644 --- a/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue +++ b/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue @@ -134,48 +134,89 @@ const handleCopy = async () => { // 获取步骤类型标签 const getStepLabel = (step: ReasoningStep) => { + if (step.status === 'failed') { + return '失败'; + } + + if (step.status === 'running') { + return '执行中'; + } + // 如果是节点类型,根据 status 显示不同的标签 if (step.type === 'node_start' || step.type === 'node_complete') { - if (step.status === 'running') { - return '执行中'; - } return '已完成'; } const labels: Record = { - thought: '思考', action: '执行', + annotation_reply: '标注回复', + error: '错误', + knowledge_retrieval: '知识库检索', + llm_chunk: '模型输出', + loop_complete: '循环完成', + loop_iteration_complete: '单次完成', + loop_iteration_error: '单次失败', + loop_iteration_start: '循环开始', observation: '观察', + parallel_complete: '并行完成', + parallel_start: '并行开始', + thought: '思考', tool_call: '调用工具', tool_result: '工具结果', - knowledge_retrieval: '知识库检索', - annotation_reply: '标注回复', + waiting_input: '等待输入', }; return labels[step.type] || step.type; }; // 获取步骤类型颜色 const getStepColor = (step: ReasoningStep) => { + if (step.status === 'failed' || step.type === 'error') { + return 'text-red-500'; + } + + if (step.status === 'running') { + return 'text-blue-500'; + } + // 如果是节点类型,根据 status 显示不同的颜色 if (step.type === 'node_start' || step.type === 'node_complete') { - if (step.status === 'running') { - return 'text-blue-500'; - } return 'text-emerald-500'; } const colors: Record = { - thought: 'text-blue-500', action: 'text-orange-500', + annotation_reply: 'text-emerald-500', + knowledge_retrieval: 'text-cyan-500', + llm_chunk: 'text-blue-500', + loop_complete: 'text-emerald-500', + loop_iteration_complete: 'text-emerald-500', + loop_iteration_error: 'text-red-500', + loop_iteration_start: 'text-blue-500', observation: 'text-green-500', + parallel_complete: 'text-emerald-500', + parallel_start: 'text-blue-500', + thought: 'text-blue-500', tool_call: 'text-purple-500', tool_result: 'text-teal-500', - knowledge_retrieval: 'text-cyan-500', - annotation_reply: 'text-emerald-500', + waiting_input: 'text-amber-500', }; return colors[step.type] || 'text-gray-500'; }; +const getStepMeta = (step: ReasoningStep) => { + const items: string[] = []; + if (step.agent_name) items.push(step.agent_name); + if (step.branch_label) items.push(`分支 ${step.branch_label}`); + if (step.subflow_name) items.push(`子流程 ${step.subflow_name}`); + + const modelText = [step.provider_name, step.model || step.model_id] + .filter(Boolean) + .join(' / '); + if (modelText) items.push(modelText); + + return items.join(' · '); +}; + // 格式化文件大小 const formatFileSize = (bytes?: number) => { if (!bytes) return ''; @@ -302,6 +343,9 @@ const formatVoiceDuration = (seconds: number) => { {{ getStepLabel(step) }} {{ step.content }} + + {{ getStepMeta(step) }} + @@ -495,6 +539,9 @@ const formatVoiceDuration = (seconds: number) => { class="chat-actions" >
+ + {{ [message.provider_name, message.model_name].filter(Boolean).join(' / ') }} + {{ message.elapsed_time }}ms {{ message.tokens_used }} tokens
@@ -1263,8 +1310,18 @@ const formatVoiceDuration = (seconds: number) => { .step-text { flex: 1; + min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +.step-meta { + max-width: 220px; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--el-text-color-placeholder); +} diff --git a/web/apps/web-ele/src/components/ChatBox/types.ts b/web/apps/web-ele/src/components/ChatBox/types.ts index 62a877e..cf6583b 100644 --- a/web/apps/web-ele/src/components/ChatBox/types.ts +++ b/web/apps/web-ele/src/components/ChatBox/types.ts @@ -41,13 +41,22 @@ export interface ReasoningStep { type: | 'action' | 'annotation_reply' + | 'error' | 'knowledge_retrieval' + | 'llm_chunk' + | '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'; + | 'tool_result' + | 'waiting_input'; content: string; tool?: string; params?: Record; @@ -55,8 +64,21 @@ export interface ReasoningStep { node_id?: string; node_type?: string; output?: any; - /** 步骤状态:running 执行中,completed 已完成 */ - status?: 'completed' | 'running'; + branch_id?: string; + branch_label?: string; + agent_code?: string; + agent_name?: string; + model?: string; + model_id?: string; + provider_name?: string; + provider_type?: string; + subflow_name?: string; + from_subflow?: boolean; + collaboration_role?: string; + collaboration_mode?: string; + communication?: Record; + /** 步骤状态:running 执行中,completed 已完成,failed 失败 */ + status?: 'completed' | 'failed' | 'running'; timestamp?: string; } @@ -126,7 +148,12 @@ export interface ChatMessage { tokens_used?: number; error_message?: string; feedback?: 'dislike' | 'like' | null; + model_id?: string; model_name?: string; + provider_name?: string; + agent_code?: string; + agent_name?: string; + collaboration_mode?: string; /** 语音消息 */ voice?: VoiceMessage; /** 对话流交互配置(等待用户输入时) */ diff --git a/web/apps/web-ele/src/components/ai-chat-panel/composables/useChatApi.ts b/web/apps/web-ele/src/components/ai-chat-panel/composables/useChatApi.ts index a421958..99cc9a2 100644 --- a/web/apps/web-ele/src/components/ai-chat-panel/composables/useChatApi.ts +++ b/web/apps/web-ele/src/components/ai-chat-panel/composables/useChatApi.ts @@ -238,6 +238,19 @@ export function useChatApi(props: AiChatPanelProps): { output: step.output, status: step.status, timestamp: step.timestamp, + 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, + provider_name: step.provider_name, + provider_type: step.provider_type, + subflow_name: step.subflow_name, + from_subflow: step.from_subflow, + collaboration_role: step.collaboration_role, + collaboration_mode: step.collaboration_mode, + communication: step.communication, })), interaction: msg.interaction, voice: msg.voice, diff --git a/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts b/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts index cbe74e2..00a112c 100644 --- a/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts +++ b/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts @@ -178,8 +178,14 @@ export function useEventHandler(config: EventHandlerConfig) { 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: + collaboration.model || + collaboration.model_name || + event.model || + event.model_name, model_id: collaboration.model_id || event.model_id, + provider_name: collaboration.provider_name || event.provider_name, + provider_type: collaboration.provider_type || event.provider_type, subflow_name: event.subflow_name || collaboration.subflow_name || event.event?.subflow_name, from_subflow: Boolean( @@ -198,10 +204,16 @@ export function useEventHandler(config: EventHandlerConfig) { event.model || event.model_name || collaboration.model || + collaboration.model_name || collaboration.model_id; return { ...(modelName ? { model_name: modelName } : {}), - ...(collaboration.model_id ? { model_id: collaboration.model_id } : {}), + ...(event.model_id || collaboration.model_id + ? { model_id: event.model_id || collaboration.model_id } + : {}), + ...(event.provider_name || collaboration.provider_name + ? { provider_name: event.provider_name || collaboration.provider_name } + : {}), ...(collaboration.agent_code ? { agent_code: collaboration.agent_code } : {}), @@ -426,6 +438,7 @@ export function useEventHandler(config: EventHandlerConfig) { status: 'failed', error_message: errorMessage, reasoning_steps: [...currentSteps.value], + ...buildMessageMeta(event), }); running.value = false; break;