feat: improve agent runtime observability
This commit is contained in:
@@ -43,6 +43,16 @@ class AgentService:
|
|||||||
def _attach_agent_collaboration(self, event: Dict[str, Any], agent: Agent) -> Dict[str, Any]:
|
def _attach_agent_collaboration(self, event: Dict[str, Any], agent: Agent) -> Dict[str, Any]:
|
||||||
enriched = dict(event)
|
enriched = dict(event)
|
||||||
collaboration = dict(enriched.get("collaboration") or {})
|
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():
|
for key, value in self._agent_collaboration_metadata(agent).items():
|
||||||
collaboration.setdefault(key, value)
|
collaboration.setdefault(key, value)
|
||||||
enriched["collaboration"] = collaboration
|
enriched["collaboration"] = collaboration
|
||||||
@@ -55,6 +65,10 @@ class AgentService:
|
|||||||
"collaboration_role",
|
"collaboration_role",
|
||||||
"collaboration_mode",
|
"collaboration_mode",
|
||||||
"model_id",
|
"model_id",
|
||||||
|
"model",
|
||||||
|
"model_name",
|
||||||
|
"provider_name",
|
||||||
|
"provider_type",
|
||||||
):
|
):
|
||||||
if collaboration.get(key):
|
if collaboration.get(key):
|
||||||
actor.setdefault(key, collaboration[key])
|
actor.setdefault(key, collaboration[key])
|
||||||
@@ -248,6 +262,34 @@ class AgentService:
|
|||||||
)
|
)
|
||||||
return str(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(
|
async def _chat_autonomous_function_calling(
|
||||||
self,
|
self,
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
@@ -264,6 +306,8 @@ class AgentService:
|
|||||||
from ai_platform.providers.base import LLMMessage
|
from ai_platform.providers.base import LLMMessage
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
model_id: Optional[str] = None
|
||||||
|
model_meta: Dict[str, Any] = {}
|
||||||
|
|
||||||
if not self._db:
|
if not self._db:
|
||||||
yield {'type': 'error', 'content': '数据库会话未初始化'}
|
yield {'type': 'error', 'content': '数据库会话未初始化'}
|
||||||
@@ -315,11 +359,13 @@ class AgentService:
|
|||||||
model_id = await self._resolve_agent_model_id(agent)
|
model_id = await self._resolve_agent_model_id(agent)
|
||||||
if not model_id:
|
if not model_id:
|
||||||
raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型')
|
raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型')
|
||||||
|
model_meta = await self._get_model_runtime_meta(model_id)
|
||||||
|
|
||||||
event = self._attach_agent_collaboration({
|
event = self._attach_agent_collaboration({
|
||||||
'type': 'thought',
|
'type': 'thought',
|
||||||
'content': '智能体开始分析用户需求并调用默认 chat 模型',
|
'content': '智能体开始分析用户需求并调用默认 chat 模型',
|
||||||
'model_id': model_id,
|
'model_id': model_id,
|
||||||
|
**model_meta,
|
||||||
}, agent)
|
}, agent)
|
||||||
step = self._event_to_reasoning_step(event)
|
step = self._event_to_reasoning_step(event)
|
||||||
if step:
|
if step:
|
||||||
@@ -390,6 +436,7 @@ class AgentService:
|
|||||||
model_id = await self._resolve_agent_model_id(agent)
|
model_id = await self._resolve_agent_model_id(agent)
|
||||||
if not model_id:
|
if not model_id:
|
||||||
raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型')
|
raise ValueError('未找到可用的 chat 模型,请先在模型配置中启用一个模型')
|
||||||
|
model_meta = await self._get_model_runtime_meta(model_id)
|
||||||
|
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
final_answer = ''
|
final_answer = ''
|
||||||
@@ -410,6 +457,7 @@ class AgentService:
|
|||||||
'content': chunk.content,
|
'content': chunk.content,
|
||||||
'accumulated_content': accumulated_content,
|
'accumulated_content': accumulated_content,
|
||||||
'model_id': model_id,
|
'model_id': model_id,
|
||||||
|
**model_meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunk.is_finished:
|
if chunk.is_finished:
|
||||||
@@ -431,6 +479,7 @@ class AgentService:
|
|||||||
'type': 'answer',
|
'type': 'answer',
|
||||||
'content': final_answer,
|
'content': final_answer,
|
||||||
'model_id': model_id,
|
'model_id': model_id,
|
||||||
|
**model_meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 更新助手消息
|
# 更新助手消息
|
||||||
@@ -461,21 +510,20 @@ class AgentService:
|
|||||||
'conversation_id': str(conversation.id),
|
'conversation_id': str(conversation.id),
|
||||||
'tokens_used': total_tokens,
|
'tokens_used': total_tokens,
|
||||||
'elapsed_time': elapsed_time,
|
'elapsed_time': elapsed_time,
|
||||||
|
**model_meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
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)})
|
error_event = {'type': 'error', 'content': str(e), **model_meta}
|
||||||
|
error_step = self._event_to_reasoning_step(error_event)
|
||||||
if error_step:
|
if error_step:
|
||||||
reasoning_steps.append(error_step)
|
reasoning_steps.append(error_step)
|
||||||
assistant_msg.reasoning_steps = reasoning_steps.copy()
|
assistant_msg.reasoning_steps = reasoning_steps.copy()
|
||||||
await self._db.commit()
|
await self._db.commit()
|
||||||
yield {
|
yield error_event
|
||||||
'type': 'error',
|
|
||||||
'content': str(e),
|
|
||||||
}
|
|
||||||
|
|
||||||
async def _chat_autonomous_react(
|
async def _chat_autonomous_react(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -134,48 +134,89 @@ const handleCopy = async () => {
|
|||||||
|
|
||||||
// 获取步骤类型标签
|
// 获取步骤类型标签
|
||||||
const getStepLabel = (step: ReasoningStep) => {
|
const getStepLabel = (step: ReasoningStep) => {
|
||||||
|
if (step.status === 'failed') {
|
||||||
|
return '失败';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.status === 'running') {
|
||||||
|
return '执行中';
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是节点类型,根据 status 显示不同的标签
|
// 如果是节点类型,根据 status 显示不同的标签
|
||||||
if (step.type === 'node_start' || step.type === 'node_complete') {
|
if (step.type === 'node_start' || step.type === 'node_complete') {
|
||||||
if (step.status === 'running') {
|
|
||||||
return '执行中';
|
|
||||||
}
|
|
||||||
return '已完成';
|
return '已完成';
|
||||||
}
|
}
|
||||||
|
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
thought: '思考',
|
|
||||||
action: '执行',
|
action: '执行',
|
||||||
|
annotation_reply: '标注回复',
|
||||||
|
error: '错误',
|
||||||
|
knowledge_retrieval: '知识库检索',
|
||||||
|
llm_chunk: '模型输出',
|
||||||
|
loop_complete: '循环完成',
|
||||||
|
loop_iteration_complete: '单次完成',
|
||||||
|
loop_iteration_error: '单次失败',
|
||||||
|
loop_iteration_start: '循环开始',
|
||||||
observation: '观察',
|
observation: '观察',
|
||||||
|
parallel_complete: '并行完成',
|
||||||
|
parallel_start: '并行开始',
|
||||||
|
thought: '思考',
|
||||||
tool_call: '调用工具',
|
tool_call: '调用工具',
|
||||||
tool_result: '工具结果',
|
tool_result: '工具结果',
|
||||||
knowledge_retrieval: '知识库检索',
|
waiting_input: '等待输入',
|
||||||
annotation_reply: '标注回复',
|
|
||||||
};
|
};
|
||||||
return labels[step.type] || step.type;
|
return labels[step.type] || step.type;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取步骤类型颜色
|
// 获取步骤类型颜色
|
||||||
const getStepColor = (step: ReasoningStep) => {
|
const getStepColor = (step: ReasoningStep) => {
|
||||||
|
if (step.status === 'failed' || step.type === 'error') {
|
||||||
|
return 'text-red-500';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.status === 'running') {
|
||||||
|
return 'text-blue-500';
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是节点类型,根据 status 显示不同的颜色
|
// 如果是节点类型,根据 status 显示不同的颜色
|
||||||
if (step.type === 'node_start' || step.type === 'node_complete') {
|
if (step.type === 'node_start' || step.type === 'node_complete') {
|
||||||
if (step.status === 'running') {
|
|
||||||
return 'text-blue-500';
|
|
||||||
}
|
|
||||||
return 'text-emerald-500';
|
return 'text-emerald-500';
|
||||||
}
|
}
|
||||||
|
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
thought: 'text-blue-500',
|
|
||||||
action: 'text-orange-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',
|
observation: 'text-green-500',
|
||||||
|
parallel_complete: 'text-emerald-500',
|
||||||
|
parallel_start: 'text-blue-500',
|
||||||
|
thought: 'text-blue-500',
|
||||||
tool_call: 'text-purple-500',
|
tool_call: 'text-purple-500',
|
||||||
tool_result: 'text-teal-500',
|
tool_result: 'text-teal-500',
|
||||||
knowledge_retrieval: 'text-cyan-500',
|
waiting_input: 'text-amber-500',
|
||||||
annotation_reply: 'text-emerald-500',
|
|
||||||
};
|
};
|
||||||
return colors[step.type] || 'text-gray-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) => {
|
const formatFileSize = (bytes?: number) => {
|
||||||
if (!bytes) return '';
|
if (!bytes) return '';
|
||||||
@@ -302,6 +343,9 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
{{ getStepLabel(step) }}
|
{{ getStepLabel(step) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="step-text">{{ step.content }}</span>
|
<span class="step-text">{{ step.content }}</span>
|
||||||
|
<span v-if="getStepMeta(step)" class="step-meta">
|
||||||
|
{{ getStepMeta(step) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -495,6 +539,9 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
class="chat-actions"
|
class="chat-actions"
|
||||||
>
|
>
|
||||||
<div class="action-info">
|
<div class="action-info">
|
||||||
|
<span v-if="message.provider_name || message.model_name">
|
||||||
|
{{ [message.provider_name, message.model_name].filter(Boolean).join(' / ') }}
|
||||||
|
</span>
|
||||||
<span v-if="message.elapsed_time">{{ message.elapsed_time }}ms</span>
|
<span v-if="message.elapsed_time">{{ message.elapsed_time }}ms</span>
|
||||||
<span v-if="message.tokens_used">{{ message.tokens_used }} tokens</span>
|
<span v-if="message.tokens_used">{{ message.tokens_used }} tokens</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1263,8 +1310,18 @@ const formatVoiceDuration = (seconds: number) => {
|
|||||||
|
|
||||||
.step-text {
|
.step-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -41,13 +41,22 @@ export interface ReasoningStep {
|
|||||||
type:
|
type:
|
||||||
| 'action'
|
| 'action'
|
||||||
| 'annotation_reply'
|
| 'annotation_reply'
|
||||||
|
| 'error'
|
||||||
| 'knowledge_retrieval'
|
| 'knowledge_retrieval'
|
||||||
|
| 'llm_chunk'
|
||||||
|
| 'loop_complete'
|
||||||
|
| 'loop_iteration_complete'
|
||||||
|
| 'loop_iteration_error'
|
||||||
|
| 'loop_iteration_start'
|
||||||
| 'node_complete'
|
| 'node_complete'
|
||||||
| 'node_start'
|
| 'node_start'
|
||||||
| 'observation'
|
| 'observation'
|
||||||
|
| 'parallel_complete'
|
||||||
|
| 'parallel_start'
|
||||||
| 'thought'
|
| 'thought'
|
||||||
| 'tool_call'
|
| 'tool_call'
|
||||||
| 'tool_result';
|
| 'tool_result'
|
||||||
|
| 'waiting_input';
|
||||||
content: string;
|
content: string;
|
||||||
tool?: string;
|
tool?: string;
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
@@ -55,8 +64,21 @@ export interface ReasoningStep {
|
|||||||
node_id?: string;
|
node_id?: string;
|
||||||
node_type?: string;
|
node_type?: string;
|
||||||
output?: any;
|
output?: any;
|
||||||
/** 步骤状态:running 执行中,completed 已完成 */
|
branch_id?: string;
|
||||||
status?: 'completed' | 'running';
|
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<string, any>;
|
||||||
|
/** 步骤状态:running 执行中,completed 已完成,failed 失败 */
|
||||||
|
status?: 'completed' | 'failed' | 'running';
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +148,12 @@ export interface ChatMessage {
|
|||||||
tokens_used?: number;
|
tokens_used?: number;
|
||||||
error_message?: string;
|
error_message?: string;
|
||||||
feedback?: 'dislike' | 'like' | null;
|
feedback?: 'dislike' | 'like' | null;
|
||||||
|
model_id?: string;
|
||||||
model_name?: string;
|
model_name?: string;
|
||||||
|
provider_name?: string;
|
||||||
|
agent_code?: string;
|
||||||
|
agent_name?: string;
|
||||||
|
collaboration_mode?: string;
|
||||||
/** 语音消息 */
|
/** 语音消息 */
|
||||||
voice?: VoiceMessage;
|
voice?: VoiceMessage;
|
||||||
/** 对话流交互配置(等待用户输入时) */
|
/** 对话流交互配置(等待用户输入时) */
|
||||||
|
|||||||
@@ -238,6 +238,19 @@ export function useChatApi(props: AiChatPanelProps): {
|
|||||||
output: step.output,
|
output: step.output,
|
||||||
status: step.status,
|
status: step.status,
|
||||||
timestamp: step.timestamp,
|
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,
|
interaction: msg.interaction,
|
||||||
voice: msg.voice,
|
voice: msg.voice,
|
||||||
|
|||||||
@@ -178,8 +178,14 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
branch_label: event.branch_label,
|
branch_label: event.branch_label,
|
||||||
agent_code: collaboration.agent_code || event.agent_code,
|
agent_code: collaboration.agent_code || event.agent_code,
|
||||||
agent_name: collaboration.agent_name || event.agent_name,
|
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,
|
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:
|
subflow_name:
|
||||||
event.subflow_name || collaboration.subflow_name || event.event?.subflow_name,
|
event.subflow_name || collaboration.subflow_name || event.event?.subflow_name,
|
||||||
from_subflow: Boolean(
|
from_subflow: Boolean(
|
||||||
@@ -198,10 +204,16 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
event.model ||
|
event.model ||
|
||||||
event.model_name ||
|
event.model_name ||
|
||||||
collaboration.model ||
|
collaboration.model ||
|
||||||
|
collaboration.model_name ||
|
||||||
collaboration.model_id;
|
collaboration.model_id;
|
||||||
return {
|
return {
|
||||||
...(modelName ? { model_name: modelName } : {}),
|
...(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
|
...(collaboration.agent_code
|
||||||
? { agent_code: collaboration.agent_code }
|
? { agent_code: collaboration.agent_code }
|
||||||
: {}),
|
: {}),
|
||||||
@@ -426,6 +438,7 @@ export function useEventHandler(config: EventHandlerConfig) {
|
|||||||
status: 'failed',
|
status: 'failed',
|
||||||
error_message: errorMessage,
|
error_message: errorMessage,
|
||||||
reasoning_steps: [...currentSteps.value],
|
reasoning_steps: [...currentSteps.value],
|
||||||
|
...buildMessageMeta(event),
|
||||||
});
|
});
|
||||||
running.value = false;
|
running.value = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user