diff --git a/backend/platform-bridge/cmd/main.go b/backend/platform-bridge/cmd/main.go index 00d7916..552a5ef 100644 --- a/backend/platform-bridge/cmd/main.go +++ b/backend/platform-bridge/cmd/main.go @@ -696,7 +696,7 @@ func forwardToAICore(cfg *config.Config, msg *bridge.UnifiedMessage, mode, userI req.Header.Set("X-Internal-Token", cfg.InternalToken) } - client := &http.Client{Timeout: 120 * time.Second} + client := &http.Client{Timeout: 180 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("forward to ai-core failed: %w", err) diff --git a/ethend/src/index.js b/ethend/src/index.js index c647a64..da46223 100644 --- a/ethend/src/index.js +++ b/ethend/src/index.js @@ -1275,87 +1275,129 @@ function parseLogTimestamp(line) { // GET /api/trace/recent — 最近的全链路追踪数据 app.get('/api/trace/recent', async (req, res) => { - const limit = Math.min(parseInt(req.query.limit) || 30, 100); + const limit = Math.min(parseInt(req.query.limit) || 50, 200); try { - const [llmResult, toolResult, sessionsResult] = await Promise.all([ + const [llmResult, toolResult, sessionsResult, bridgeLogsResult, aiCoreLogsResult] = await Promise.all([ proxyToAICore(`/api/v1/llm-calls?limit=${limit}`).catch(() => ({ status: 502, body: [] })), proxyToAICore(`/api/v1/tools/calls?limit=${limit}`).catch(() => ({ status: 502, body: { calls: [] } })), proxyToGateway('/api/v1/admin/sessions/active').catch(() => ({ status: 502, body: { users: {} } })), + // 拉 platform-bridge 最近的消息日志 + Promise.all((['obv11', 'qq']).map(platform => + proxyToPlatformBridge(`/api/v1/logs/${platform}?limit=50`).catch(() => ({ status: 502, body: { entries: [] } })) + )), + // 拉 ai-core 最近的日志(解析处理事件) + proxyToAICore('/api/v1/llm-calls/stream?limit=0').catch(() => ({ status: 502, body: [] })), ]); const traces = []; - // LLM 调用 → 追踪节点 + // === 消息收发 (platform-bridge 日志) === + for (const logResult of bridgeLogsResult) { + const entries = logResult.body?.logs || logResult.body?.entries || logResult.body || []; + for (const entry of entries) { + const ts = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now(); + const isIncoming = entry.direction === 'incoming'; + const hop = isIncoming ? 'msg_received' : 'msg_sent'; + const icon = isIncoming ? '📥' : '📤'; + const sender = entry.sender_name || entry.sender_id || '?'; + const channel = entry.channel_id || '?'; + const groupName = entry.group_name ? ` (${entry.group_name})` : ''; + traces.push({ + id: `msg-${ts}-${Math.random().toString(36).slice(2, 6)}`, + timestamp: new Date(ts).toISOString(), ts, + service: 'platform-bridge', + hop, + label: `${icon} ${isIncoming ? '收到' : '发送'}消息 · ${entry.platform || '?'}${groupName}`, + status: entry.success ? 'success' : 'error', + durationMs: 0, + detail: `${isIncoming ? '来自' : '发给'} ${sender} | 频道 ${channel} | ${(entry.content || '').substring(0, 80)}`, + data: { platform: entry.platform, sender, channel, content: entry.content, direction: entry.direction, contentType: entry.content_type }, + }); + } + } + + // === LLM 调用 === const llmCalls = Array.isArray(llmResult.body) ? llmResult.body : (llmResult.body?.calls || []); for (const call of llmCalls) { const ts = call.time ? new Date(call.time).getTime() : Date.now(); traces.push({ id: `llm-${ts}-${Math.random().toString(36).slice(2, 6)}`, - timestamp: new Date(ts).toISOString(), - ts, - service: 'ai-core', - hop: 'llm_call', - label: `LLM 调用: ${call.model || 'unknown'}`, + timestamp: new Date(ts).toISOString(), ts, + service: 'ai-core', hop: 'llm_call', + label: `🧠 LLM: ${call.model || 'unknown'}`, status: call.success ? 'success' : 'error', durationMs: call.duration_ms || (call.Duration ? Math.round(call.Duration / 1e6) : 0), - detail: call.error || `${call.prompt_tokens || 0}+${call.completion_tokens || 0} tokens`, + detail: call.error || `${call.prompt_tokens || 0}→${call.completion_tokens || 0} tokens`, data: call, }); } - // 工具调用 + // === 工具调用 === const toolCalls = toolResult.body?.calls || (Array.isArray(toolResult.body) ? toolResult.body : []); for (const tc of toolCalls) { const ts = tc.time || tc.timestamp || tc.created_at; const tsNum = ts ? new Date(ts).getTime() : Date.now(); traces.push({ id: `tool-${tsNum}-${Math.random().toString(36).slice(2, 6)}`, - timestamp: new Date(tsNum).toISOString(), - ts: tsNum, - service: 'ai-core', - hop: 'tool_call', - label: `工具调用: ${tc.tool_name || tc.name || 'unknown'}`, + timestamp: new Date(tsNum).toISOString(), ts: tsNum, + service: 'ai-core', hop: 'tool_call', + label: `🔧 ${tc.tool_name || tc.name || 'unknown'}`, status: tc.error ? 'error' : 'success', durationMs: tc.duration_ms || (tc.Duration ? Math.round(tc.Duration / 1e6) : 0), - detail: tc.error || tc.result?.substring?.(0, 100) || '', + detail: tc.error || tc.result?.substring?.(0, 100) || tc.output?.substring?.(0, 100) || '', data: tc, }); } - // 活跃会话 + // === 活跃会话 === const users = sessionsResult.body?.users || {}; for (const [userID, sessions] of Object.entries(users)) { for (const s of sessions) { const ts = s.last_activity ? new Date(s.last_activity).getTime() : Date.now(); traces.push({ id: `session-${s.session_id || ''}`, - timestamp: new Date(ts).toISOString(), - ts, - service: 'gateway', - hop: 'session_active', - label: `会话活跃: ${userID}`, - status: 'success', + timestamp: new Date(ts).toISOString(), ts, + service: 'gateway', hop: 'session', + label: `👤 会话: ${userID}`, + status: s.state === 'streaming' ? 'running' : 'success', durationMs: 0, - detail: `Session: ${(s.session_id || '').substring(0, 16)}... State: ${s.state || 'idle'}`, + detail: `${(s.session_id || '').substring(0, 20)}... [${s.state || 'idle'}]`, data: { userID, sessionId: s.session_id, state: s.state }, }); } } - // 按时间倒序排列 + // 按时间倒序,按 hop 分组 traces.sort((a, b) => b.ts - a.ts); const recent = traces.slice(0, limit); - // 统计摘要 + // 按消息分组:将相近时间的 msg_received + llm_call + tool_call + msg_sent 聚合成消息管线 + const pipelines = []; + let currentPipeline = null; + const sorted = [...recent].sort((a, b) => a.ts - b.ts); + for (const t of sorted) { + if (t.hop === 'msg_received') { + if (currentPipeline) pipelines.push(currentPipeline); + currentPipeline = { id: t.id, ts: t.ts, timestamp: t.timestamp, sender: t.data?.sender, channel: t.data?.channel, platform: t.data?.platform, content: t.data?.content, steps: [t] }; + } else if (currentPipeline) { + // 只聚合时间相近的事件 (60s 内) + if (t.ts - currentPipeline.ts < 60000) { + currentPipeline.steps.push(t); + } + } else { + currentPipeline = { id: t.id, ts: t.ts, timestamp: t.timestamp, steps: [t] }; + } + } + if (currentPipeline) pipelines.push(currentPipeline); + const services = [...new Set(recent.map(t => t.service))]; const errors = recent.filter(t => t.status === 'error').length; - const totalDuration = recent.reduce((sum, t) => sum + (t.durationMs || 0), 0); res.json({ timestamp: Date.now(), - total: traces.length, - shown: recent.length, - stats: { services, errors, totalDurationMs: Math.round(totalDuration) }, + total: traces.length, shown: recent.length, + pipelines: pipelines.slice(-limit), + stats: { services, errors, pipelineCount: pipelines.length }, traces: recent, }); } catch (err) {