feat: add Graph Tool tracing and logging support in agent run logs
This commit is contained in:
@@ -49,3 +49,13 @@ func (c *RuntimeTraceCollector) AddToolSearchItem(item ToolSearchTraceItem) {
|
|||||||
c.Data.ToolSearch.Count++
|
c.Data.ToolSearch.Count++
|
||||||
c.Data.ToolSearch.Items = append(c.Data.ToolSearch.Items, item)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ type ToolSearchTraceItem struct {
|
|||||||
ErrorMessage string `json:"errorMessage,omitempty"`
|
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 {
|
type RetrieverTraceItem struct {
|
||||||
Query string `json:"query,omitempty"`
|
Query string `json:"query,omitempty"`
|
||||||
KnowledgeBaseID int64 `json:"knowledgeBaseId,omitempty"`
|
KnowledgeBaseID int64 `json:"knowledgeBaseId,omitempty"`
|
||||||
@@ -62,6 +72,10 @@ type RuntimeTraceData struct {
|
|||||||
Count int `json:"count,omitempty"`
|
Count int `json:"count,omitempty"`
|
||||||
Items []ToolSearchTraceItem `json:"items,omitempty"`
|
Items []ToolSearchTraceItem `json:"items,omitempty"`
|
||||||
} `json:"toolSearch"`
|
} `json:"toolSearch"`
|
||||||
|
GraphTools struct {
|
||||||
|
Count int `json:"count,omitempty"`
|
||||||
|
Items []GraphToolTraceItem `json:"items,omitempty"`
|
||||||
|
} `json:"graphTools"`
|
||||||
Output struct {
|
Output struct {
|
||||||
ReplyText string `json:"replyText,omitempty"`
|
ReplyText string `json:"replyText,omitempty"`
|
||||||
FinishReason string `json:"finishReason,omitempty"`
|
FinishReason string `json:"finishReason,omitempty"`
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
|
|||||||
ToolCode: item.ToolCode,
|
ToolCode: item.ToolCode,
|
||||||
ServerCode: item.ServerCode,
|
ServerCode: item.ServerCode,
|
||||||
ToolName: item.ToolName,
|
ToolName: item.ToolName,
|
||||||
|
SourceType: "mcp",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for modelName, toolCode := range extraToolCodes {
|
for modelName, toolCode := range extraToolCodes {
|
||||||
@@ -87,6 +88,7 @@ func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, aiAgent *m
|
|||||||
ToolCode: toolCode,
|
ToolCode: toolCode,
|
||||||
ServerCode: serverCode,
|
ServerCode: serverCode,
|
||||||
ToolName: toolName,
|
ToolName: toolName,
|
||||||
|
SourceType: resolveToolSourceType(toolCode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handlers = append(handlers, einocallbacks.NewRuntimeTraceHandler(collector, toolMetadataBy))
|
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
|
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 {
|
func buildAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition, extraToolCodes map[string]string) string {
|
||||||
baseInstruction := ""
|
baseInstruction := ""
|
||||||
if aiAgent != nil {
|
if aiAgent != nil {
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ func (s *aiReplyService) writeRunLog(startedAt time.Time, message models.Message
|
|||||||
PlannedSkillName: strings.TrimSpace(summaryPlannedSkillName(summary)),
|
PlannedSkillName: strings.TrimSpace(summaryPlannedSkillName(summary)),
|
||||||
SkillRouteTrace: strings.TrimSpace(summarySkillRouteTrace(summary)),
|
SkillRouteTrace: strings.TrimSpace(summarySkillRouteTrace(summary)),
|
||||||
ToolSearchTrace: extractToolSearchTrace(summary),
|
ToolSearchTrace: extractToolSearchTrace(summary),
|
||||||
|
GraphToolTrace: extractGraphToolTrace(summary),
|
||||||
PlannedToolCode: plannedToolCode,
|
PlannedToolCode: plannedToolCode,
|
||||||
PlanReason: planReason,
|
PlanReason: planReason,
|
||||||
InterruptType: firstInterruptType(summary),
|
InterruptType: firstInterruptType(summary),
|
||||||
@@ -380,9 +381,15 @@ func buildRunLogPlan(summary *Summary) (plannedAction, plannedToolCode, planReas
|
|||||||
return "interrupt", "", "pending interrupt checkpoint expired"
|
return "interrupt", "", "pending interrupt checkpoint expired"
|
||||||
}
|
}
|
||||||
if summary.Interrupted {
|
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"
|
return "tool", summaryPrimaryToolCode(summary), "agent interrupted and is waiting for user confirmation"
|
||||||
}
|
}
|
||||||
if len(summary.InvokedToolCodes) > 0 {
|
if len(summary.InvokedToolCodes) > 0 {
|
||||||
|
if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" {
|
||||||
|
return "graph", graphToolCode, "agent invoked graph tool"
|
||||||
|
}
|
||||||
toolCode := summaryPrimaryToolCode(summary)
|
toolCode := summaryPrimaryToolCode(summary)
|
||||||
reason := "agent invoked MCP tool"
|
reason := "agent invoked MCP tool"
|
||||||
if toolCode != "" && toolCode != firstInvokedToolCode(summary) {
|
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) != "" {
|
if skillCode := strings.TrimSpace(summaryPlannedSkillCode(summary)); skillCode != "" && strings.TrimSpace(summary.ReplyText) != "" {
|
||||||
return "skill"
|
return "skill"
|
||||||
}
|
}
|
||||||
|
if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" && strings.TrimSpace(summary.ReplyText) != "" {
|
||||||
|
return "graph"
|
||||||
|
}
|
||||||
switch strings.TrimSpace(summary.Status) {
|
switch strings.TrimSpace(summary.Status) {
|
||||||
case "completed":
|
case "completed":
|
||||||
return "reply"
|
return "reply"
|
||||||
@@ -573,6 +583,17 @@ func extractToolSearchTrace(summary *Summary) string {
|
|||||||
return string(trace.ToolSearch.Raw)
|
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 {
|
func firstToolSearchTargetToolCode(summary *Summary) string {
|
||||||
trace := parseRuntimeTraceData(summary.TraceData)
|
trace := parseRuntimeTraceData(summary.TraceData)
|
||||||
for _, item := range trace.ToolSearch.Items {
|
for _, item := range trace.ToolSearch.Items {
|
||||||
@@ -590,6 +611,17 @@ func firstToolSearchTargetToolCode(summary *Summary) string {
|
|||||||
return ""
|
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 {
|
type runtimeTraceProjection struct {
|
||||||
ToolSearch struct {
|
ToolSearch struct {
|
||||||
Raw json.RawMessage `json:"-"`
|
Raw json.RawMessage `json:"-"`
|
||||||
@@ -598,6 +630,12 @@ type runtimeTraceProjection struct {
|
|||||||
CandidateToolCodes []string `json:"candidateToolCodes"`
|
CandidateToolCodes []string `json:"candidateToolCodes"`
|
||||||
} `json:"items"`
|
} `json:"items"`
|
||||||
} `json:"toolSearch"`
|
} `json:"toolSearch"`
|
||||||
|
GraphTools struct {
|
||||||
|
Raw json.RawMessage `json:"-"`
|
||||||
|
Items []struct {
|
||||||
|
ToolCode string `json:"toolCode"`
|
||||||
|
} `json:"items"`
|
||||||
|
} `json:"graphTools"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRuntimeTraceData(raw string) runtimeTraceProjection {
|
func parseRuntimeTraceData(raw string) runtimeTraceProjection {
|
||||||
@@ -614,6 +652,10 @@ func parseRuntimeTraceData(raw string) runtimeTraceProjection {
|
|||||||
trace.ToolSearch.Raw = append(json.RawMessage(nil), toolSearchRaw...)
|
trace.ToolSearch.Raw = append(json.RawMessage(nil), toolSearchRaw...)
|
||||||
_ = json.Unmarshal(toolSearchRaw, &trace.ToolSearch)
|
_ = 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
|
return trace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func BuildAgentRunLog(item *models.AgentRunLog) response.AgentRunLogResponse {
|
|||||||
PlannedSkillName: item.PlannedSkillName,
|
PlannedSkillName: item.PlannedSkillName,
|
||||||
SkillRouteTrace: item.SkillRouteTrace,
|
SkillRouteTrace: item.SkillRouteTrace,
|
||||||
ToolSearchTrace: item.ToolSearchTrace,
|
ToolSearchTrace: item.ToolSearchTrace,
|
||||||
|
GraphToolTrace: item.GraphToolTrace,
|
||||||
PlannedToolCode: item.PlannedToolCode,
|
PlannedToolCode: item.PlannedToolCode,
|
||||||
PlanReason: item.PlanReason,
|
PlanReason: item.PlanReason,
|
||||||
InterruptType: item.InterruptType,
|
InterruptType: item.InterruptType,
|
||||||
|
|||||||
@@ -953,6 +953,7 @@ type AgentRunLog struct {
|
|||||||
PlannedSkillName string `gorm:"type:varchar(100);not null;default:''"`
|
PlannedSkillName string `gorm:"type:varchar(100);not null;default:''"`
|
||||||
SkillRouteTrace string `gorm:"type:text"`
|
SkillRouteTrace string `gorm:"type:text"`
|
||||||
ToolSearchTrace string `gorm:"type:text"`
|
ToolSearchTrace string `gorm:"type:text"`
|
||||||
|
GraphToolTrace string `gorm:"type:text"`
|
||||||
PlannedToolCode string `gorm:"type:varchar(200);not null;default:'';index"`
|
PlannedToolCode string `gorm:"type:varchar(200);not null;default:'';index"`
|
||||||
PlanReason string `gorm:"type:varchar(500);not null;default:''"`
|
PlanReason string `gorm:"type:varchar(500);not null;default:''"`
|
||||||
InterruptType string `gorm:"type:varchar(50);not null;default:'';index"`
|
InterruptType string `gorm:"type:varchar(50);not null;default:'';index"`
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type AgentRunLogResponse struct {
|
|||||||
PlannedSkillName string `json:"plannedSkillName"`
|
PlannedSkillName string `json:"plannedSkillName"`
|
||||||
SkillRouteTrace string `json:"skillRouteTrace"`
|
SkillRouteTrace string `json:"skillRouteTrace"`
|
||||||
ToolSearchTrace string `json:"toolSearchTrace"`
|
ToolSearchTrace string `json:"toolSearchTrace"`
|
||||||
|
GraphToolTrace string `json:"graphToolTrace"`
|
||||||
PlannedToolCode string `json:"plannedToolCode"`
|
PlannedToolCode string `json:"plannedToolCode"`
|
||||||
PlanReason string `json:"planReason"`
|
PlanReason string `json:"planReason"`
|
||||||
InterruptType string `json:"interruptType"`
|
InterruptType string `json:"interruptType"`
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const actionOptions = [
|
|||||||
{ value: "rag", label: "RAG" },
|
{ value: "rag", label: "RAG" },
|
||||||
{ value: "skill", label: "Skill" },
|
{ value: "skill", label: "Skill" },
|
||||||
{ value: "tool", label: "Tool" },
|
{ value: "tool", label: "Tool" },
|
||||||
|
{ value: "graph", label: "Graph" },
|
||||||
{ value: "handoff", label: "转人工" },
|
{ value: "handoff", label: "转人工" },
|
||||||
{ value: "reply", label: "回复" },
|
{ value: "reply", label: "回复" },
|
||||||
{ value: "fallback", label: "兜底" },
|
{ value: "fallback", label: "兜底" },
|
||||||
@@ -58,6 +59,8 @@ function actionBadgeVariant(action: string) {
|
|||||||
return "default" as const
|
return "default" as const
|
||||||
case "tool":
|
case "tool":
|
||||||
return "default" as const
|
return "default" as const
|
||||||
|
case "graph":
|
||||||
|
return "default" as const
|
||||||
case "rag":
|
case "rag":
|
||||||
return "secondary" as const
|
return "secondary" as const
|
||||||
case "fallback":
|
case "fallback":
|
||||||
@@ -95,6 +98,10 @@ export default function DashboardAgentRunLogsPage() {
|
|||||||
() => safeParseJSON(activeLog?.toolSearchTrace ?? ""),
|
() => safeParseJSON(activeLog?.toolSearchTrace ?? ""),
|
||||||
[activeLog?.toolSearchTrace]
|
[activeLog?.toolSearchTrace]
|
||||||
)
|
)
|
||||||
|
const activeGraphToolTrace = useMemo(
|
||||||
|
() => safeParseJSON(activeLog?.graphToolTrace ?? ""),
|
||||||
|
[activeLog?.graphToolTrace]
|
||||||
|
)
|
||||||
|
|
||||||
const aiAgentOptions = useMemo(
|
const aiAgentOptions = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -384,6 +391,14 @@ export default function DashboardAgentRunLogsPage() {
|
|||||||
: activeLog.toolSearchTrace
|
: activeLog.toolSearchTrace
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<TextBlock
|
||||||
|
title="Graph Tool 调用"
|
||||||
|
value={
|
||||||
|
activeGraphToolTrace
|
||||||
|
? JSON.stringify(activeGraphToolTrace, null, 2)
|
||||||
|
: activeLog.graphToolTrace
|
||||||
|
}
|
||||||
|
/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
icon={<BotMessageSquareIcon className="size-4" />}
|
icon={<BotMessageSquareIcon className="size-4" />}
|
||||||
title="用户问题"
|
title="用户问题"
|
||||||
|
|||||||
@@ -367,6 +367,7 @@ export type AgentRunLog = {
|
|||||||
plannedSkillName: string
|
plannedSkillName: string
|
||||||
skillRouteTrace: string
|
skillRouteTrace: string
|
||||||
toolSearchTrace: string
|
toolSearchTrace: string
|
||||||
|
graphToolTrace: string
|
||||||
plannedToolCode: string
|
plannedToolCode: string
|
||||||
planReason: string
|
planReason: string
|
||||||
interruptType: string
|
interruptType: string
|
||||||
|
|||||||
Reference in New Issue
Block a user