diff --git a/internal/ai/runtime/internal/impl/callbacks/runlog_callback.go b/internal/ai/runtime/internal/impl/callbacks/runlog_callback.go
index e67e71e..d0ac9f4 100644
--- a/internal/ai/runtime/internal/impl/callbacks/runlog_callback.go
+++ b/internal/ai/runtime/internal/impl/callbacks/runlog_callback.go
@@ -49,3 +49,13 @@ func (c *RuntimeTraceCollector) AddToolSearchItem(item ToolSearchTraceItem) {
c.Data.ToolSearch.Count++
c.Data.ToolSearch.Items = append(c.Data.ToolSearch.Items, item)
}
+
+func (c *RuntimeTraceCollector) AddGraphToolItem(item GraphToolTraceItem) {
+ if c == nil {
+ return
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.Data.GraphTools.Count++
+ c.Data.GraphTools.Items = append(c.Data.GraphTools.Items, item)
+}
diff --git a/internal/ai/runtime/internal/impl/callbacks/trace_callback.go b/internal/ai/runtime/internal/impl/callbacks/trace_callback.go
index 71eea7a..1727e0a 100644
--- a/internal/ai/runtime/internal/impl/callbacks/trace_callback.go
+++ b/internal/ai/runtime/internal/impl/callbacks/trace_callback.go
@@ -22,6 +22,16 @@ type ToolSearchTraceItem struct {
ErrorMessage string `json:"errorMessage,omitempty"`
}
+type GraphToolTraceItem struct {
+ ToolCode string `json:"toolCode"`
+ ToolName string `json:"toolName"`
+ Arguments map[string]any `json:"arguments,omitempty"`
+ ResultPreview string `json:"resultPreview,omitempty"`
+ LatencyMs int64 `json:"latencyMs,omitempty"`
+ Status string `json:"status,omitempty"`
+ ErrorMessage string `json:"errorMessage,omitempty"`
+}
+
type RetrieverTraceItem struct {
Query string `json:"query,omitempty"`
KnowledgeBaseID int64 `json:"knowledgeBaseId,omitempty"`
@@ -62,6 +72,10 @@ type RuntimeTraceData struct {
Count int `json:"count,omitempty"`
Items []ToolSearchTraceItem `json:"items,omitempty"`
} `json:"toolSearch"`
+ GraphTools struct {
+ Count int `json:"count,omitempty"`
+ Items []GraphToolTraceItem `json:"items,omitempty"`
+ } `json:"graphTools"`
Output struct {
ReplyText string `json:"replyText,omitempty"`
FinishReason string `json:"finishReason,omitempty"`
diff --git a/internal/ai/runtime/internal/impl/factory/agent_factory.go b/internal/ai/runtime/internal/impl/factory/agent_factory.go
index 5308095..721821f 100644
--- a/internal/ai/runtime/internal/impl/factory/agent_factory.go
+++ b/internal/ai/runtime/internal/impl/factory/agent_factory.go
@@ -67,6 +67,7 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
ToolCode: item.ToolCode,
ServerCode: item.ServerCode,
ToolName: item.ToolName,
+ SourceType: "mcp",
}
}
for modelName, toolCode := range extraToolCodes {
@@ -87,6 +88,7 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
ToolCode: toolCode,
ServerCode: serverCode,
ToolName: toolName,
+ SourceType: resolveToolSourceType(toolCode),
}
}
handlers = append(handlers, einocallbacks.NewRuntimeTraceHandler(collector, toolMetadataBy))
@@ -109,6 +111,20 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
return &einoagents.CustomerServiceAgent{Inner: inner}, nil
}
+func resolveToolSourceType(toolCode string) string {
+ toolCode = strings.TrimSpace(toolCode)
+ switch {
+ case toolCode == toolx.BuiltinToolSearchToolCode:
+ return toolx.BuiltinToolCatalogServerCode
+ case strings.HasPrefix(toolCode, toolx.GraphToolCatalogServerCode+"/"):
+ return toolx.GraphToolCatalogServerCode
+ case strings.HasPrefix(toolCode, toolx.BuiltinToolCatalogServerCode+"/"):
+ return toolx.BuiltinToolCatalogServerCode
+ default:
+ return "mcp"
+ }
+}
+
func buildAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) string {
baseInstruction := ""
if aiAgent != nil {
diff --git a/internal/ai/runtime/reply_service.go b/internal/ai/runtime/reply_service.go
index 9362f4e..67d6931 100644
--- a/internal/ai/runtime/reply_service.go
+++ b/internal/ai/runtime/reply_service.go
@@ -333,6 +333,7 @@ func (s *aiReplyService) writeRunLog(startedAt time.Time, message models.Message
PlannedSkillName: strings.TrimSpace(summaryPlannedSkillName(summary)),
SkillRouteTrace: strings.TrimSpace(summarySkillRouteTrace(summary)),
ToolSearchTrace: extractToolSearchTrace(summary),
+ GraphToolTrace: extractGraphToolTrace(summary),
PlannedToolCode: plannedToolCode,
PlanReason: planReason,
InterruptType: firstInterruptType(summary),
@@ -380,9 +381,15 @@ func buildRunLogPlan(summary *Summary) (plannedAction, plannedToolCode, planReas
return "interrupt", "", "pending interrupt checkpoint expired"
}
if summary.Interrupted {
+ if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" {
+ return "graph", graphToolCode, "graph tool interrupted and is waiting for user confirmation"
+ }
return "tool", summaryPrimaryToolCode(summary), "agent interrupted and is waiting for user confirmation"
}
if len(summary.InvokedToolCodes) > 0 {
+ if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" {
+ return "graph", graphToolCode, "agent invoked graph tool"
+ }
toolCode := summaryPrimaryToolCode(summary)
reason := "agent invoked MCP tool"
if toolCode != "" && toolCode != firstInvokedToolCode(summary) {
@@ -406,6 +413,9 @@ func toRunLogFinalAction(summary *Summary) string {
if skillCode := strings.TrimSpace(summaryPlannedSkillCode(summary)); skillCode != "" && strings.TrimSpace(summary.ReplyText) != "" {
return "skill"
}
+ if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" && strings.TrimSpace(summary.ReplyText) != "" {
+ return "graph"
+ }
switch strings.TrimSpace(summary.Status) {
case "completed":
return "reply"
@@ -573,6 +583,17 @@ func extractToolSearchTrace(summary *Summary) string {
return string(trace.ToolSearch.Raw)
}
+func extractGraphToolTrace(summary *Summary) string {
+ if summary == nil {
+ return ""
+ }
+ trace := parseRuntimeTraceData(summary.TraceData)
+ if len(trace.GraphTools.Items) == 0 || len(trace.GraphTools.Raw) == 0 {
+ return ""
+ }
+ return string(trace.GraphTools.Raw)
+}
+
func firstToolSearchTargetToolCode(summary *Summary) string {
trace := parseRuntimeTraceData(summary.TraceData)
for _, item := range trace.ToolSearch.Items {
@@ -590,6 +611,17 @@ func firstToolSearchTargetToolCode(summary *Summary) string {
return ""
}
+func firstGraphToolCode(summary *Summary) string {
+ trace := parseRuntimeTraceData(summary.TraceData)
+ for _, item := range trace.GraphTools.Items {
+ toolCode := strings.TrimSpace(item.ToolCode)
+ if toolCode != "" {
+ return toolCode
+ }
+ }
+ return ""
+}
+
type runtimeTraceProjection struct {
ToolSearch struct {
Raw json.RawMessage `json:"-"`
@@ -598,6 +630,12 @@ type runtimeTraceProjection struct {
CandidateToolCodes []string `json:"candidateToolCodes"`
} `json:"items"`
} `json:"toolSearch"`
+ GraphTools struct {
+ Raw json.RawMessage `json:"-"`
+ Items []struct {
+ ToolCode string `json:"toolCode"`
+ } `json:"items"`
+ } `json:"graphTools"`
}
func parseRuntimeTraceData(raw string) runtimeTraceProjection {
@@ -614,6 +652,10 @@ func parseRuntimeTraceData(raw string) runtimeTraceProjection {
trace.ToolSearch.Raw = append(json.RawMessage(nil), toolSearchRaw...)
_ = json.Unmarshal(toolSearchRaw, &trace.ToolSearch)
}
+ if graphToolsRaw, ok := payload["graphTools"]; ok && len(graphToolsRaw) > 0 {
+ trace.GraphTools.Raw = append(json.RawMessage(nil), graphToolsRaw...)
+ _ = json.Unmarshal(graphToolsRaw, &trace.GraphTools)
+ }
return trace
}
diff --git a/internal/builders/agent_run_log_builder.go b/internal/builders/agent_run_log_builder.go
index 6daa240..ed5ec4e 100644
--- a/internal/builders/agent_run_log_builder.go
+++ b/internal/builders/agent_run_log_builder.go
@@ -21,6 +21,7 @@ func BuildAgentRunLog(item *models.AgentRunLog) response.AgentRunLogResponse {
PlannedSkillName: item.PlannedSkillName,
SkillRouteTrace: item.SkillRouteTrace,
ToolSearchTrace: item.ToolSearchTrace,
+ GraphToolTrace: item.GraphToolTrace,
PlannedToolCode: item.PlannedToolCode,
PlanReason: item.PlanReason,
InterruptType: item.InterruptType,
diff --git a/internal/models/models.go b/internal/models/models.go
index d59d053..78211ef 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -953,6 +953,7 @@ type AgentRunLog struct {
PlannedSkillName string `gorm:"type:varchar(100);not null;default:''"`
SkillRouteTrace string `gorm:"type:text"`
ToolSearchTrace string `gorm:"type:text"`
+ GraphToolTrace string `gorm:"type:text"`
PlannedToolCode string `gorm:"type:varchar(200);not null;default:'';index"`
PlanReason string `gorm:"type:varchar(500);not null;default:''"`
InterruptType string `gorm:"type:varchar(50);not null;default:'';index"`
diff --git a/internal/pkg/dto/response/skill_response.go b/internal/pkg/dto/response/skill_response.go
index 8c93f42..456fa0f 100644
--- a/internal/pkg/dto/response/skill_response.go
+++ b/internal/pkg/dto/response/skill_response.go
@@ -49,6 +49,7 @@ type AgentRunLogResponse struct {
PlannedSkillName string `json:"plannedSkillName"`
SkillRouteTrace string `json:"skillRouteTrace"`
ToolSearchTrace string `json:"toolSearchTrace"`
+ GraphToolTrace string `json:"graphToolTrace"`
PlannedToolCode string `json:"plannedToolCode"`
PlanReason string `json:"planReason"`
InterruptType string `json:"interruptType"`
diff --git a/web/app/(console)/agent-run-logs/page.tsx b/web/app/(console)/agent-run-logs/page.tsx
index c981428..935ab39 100644
--- a/web/app/(console)/agent-run-logs/page.tsx
+++ b/web/app/(console)/agent-run-logs/page.tsx
@@ -45,6 +45,7 @@ const actionOptions = [
{ value: "rag", label: "RAG" },
{ value: "skill", label: "Skill" },
{ value: "tool", label: "Tool" },
+ { value: "graph", label: "Graph" },
{ value: "handoff", label: "转人工" },
{ value: "reply", label: "回复" },
{ value: "fallback", label: "兜底" },
@@ -58,6 +59,8 @@ function actionBadgeVariant(action: string) {
return "default" as const
case "tool":
return "default" as const
+ case "graph":
+ return "default" as const
case "rag":
return "secondary" as const
case "fallback":
@@ -95,6 +98,10 @@ export default function DashboardAgentRunLogsPage() {
() => safeParseJSON(activeLog?.toolSearchTrace ?? ""),
[activeLog?.toolSearchTrace]
)
+ const activeGraphToolTrace = useMemo(
+ () => safeParseJSON(activeLog?.graphToolTrace ?? ""),
+ [activeLog?.graphToolTrace]
+ )
const aiAgentOptions = useMemo(
() => [
@@ -384,6 +391,14 @@ export default function DashboardAgentRunLogsPage() {
: activeLog.toolSearchTrace
}
/>
+
}
title="用户问题"
diff --git a/web/lib/api/admin.ts b/web/lib/api/admin.ts
index d79233c..06c2dfe 100644
--- a/web/lib/api/admin.ts
+++ b/web/lib/api/admin.ts
@@ -367,6 +367,7 @@ export type AgentRunLog = {
plannedSkillName: string
skillRouteTrace: string
toolSearchTrace: string
+ graphToolTrace: string
plannedToolCode: string
planReason: string
interruptType: string