Refactor skill handling and improve runtime trace capabilities

- Removed the selectSkill method from prepareService and adjusted related logic in the Run method of Service.
- Updated tool catalog to parse agent allowed tool codes directly.
- Simplified Request and RunInput structures by removing unnecessary fields.
- Enhanced the RuntimeTraceCollector to manage skill activation and visibility.
- Introduced a new databaseSkillBackend to manage skill definitions and their metadata.
- Added tests for skill backend functionalities to ensure correct behavior.
- Updated various factory methods to accommodate changes in skill handling.
- Improved documentation and descriptions for better clarity.
This commit is contained in:
mlogclub
2026-04-19 09:57:34 +08:00
parent ad83d1dfda
commit 42077c1aab
21 changed files with 469 additions and 176 deletions
@@ -24,8 +24,9 @@ type ToolMetadata struct {
type RuntimeTraceHandler struct {
*adk.BaseChatModelAgentMiddleware
collector *RuntimeTraceCollector
toolMetadataBy map[string]ToolMetadata
collector *RuntimeTraceCollector
toolMetadataBy map[string]ToolMetadata
skillMetadataBy map[string]SkillMetadata
}
type graphAnalyzeConversationResult struct {
@@ -65,11 +66,12 @@ type toolSearchSearchResult struct {
Candidates []toolSearchCandidateResult `json:"candidates"`
}
func NewRuntimeTraceHandler(collector *RuntimeTraceCollector, toolMetadataBy map[string]ToolMetadata) *RuntimeTraceHandler {
func NewRuntimeTraceHandler(collector *RuntimeTraceCollector, toolMetadataBy map[string]ToolMetadata, skillMetadataBy map[string]SkillMetadata) *RuntimeTraceHandler {
return &RuntimeTraceHandler{
BaseChatModelAgentMiddleware: &adk.BaseChatModelAgentMiddleware{},
collector: collector,
toolMetadataBy: toolMetadataBy,
skillMetadataBy: skillMetadataBy,
}
}
@@ -123,10 +125,42 @@ func (h *RuntimeTraceHandler) WrapInvokableToolCall(_ context.Context, endpoint
if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinToolSearch.Code {
h.collector.AddToolSearchItem(h.buildToolSearchTraceItem(argumentsInJSON, result, err))
}
if metadata, ok := h.resolveToolMetadata(item.ToolName); ok && strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinSkill.Code {
h.tryActivateSkill(argumentsInJSON)
}
return result, err
}, nil
}
func (h *RuntimeTraceHandler) tryActivateSkill(argumentsInJSON string) {
if h == nil || h.collector == nil {
return
}
var args struct {
Skill string `json:"skill"`
}
if err := json.Unmarshal([]byte(strings.TrimSpace(argumentsInJSON)), &args); err != nil {
return
}
code := strings.TrimSpace(args.Skill)
if code == "" {
return
}
meta, ok := h.skillMetadataBy[code]
if !ok {
meta = SkillMetadata{Code: code}
}
buf, err := json.Marshal(map[string]any{
"source": "eino_skill_tool",
"skill": code,
})
routeTrace := ""
if err == nil {
routeTrace = string(buf)
}
h.collector.ActivateSkill(meta, "eino_skill_tool", routeTrace)
}
func parseGraphToolOutcome(toolCode string, result string) (recommendedAction, riskLevel string, ticketDraftReady bool) {
toolCode = strings.TrimSpace(toolCode)
if toolCode == "" || strings.TrimSpace(result) == "" {
@@ -36,3 +36,32 @@ func TestExtractCandidateToolCodes(t *testing.T) {
t.Fatalf("unexpected candidate codes: %#v", got)
}
}
func TestTryActivateSkill(t *testing.T) {
collector := NewRuntimeTraceCollector()
handler := &RuntimeTraceHandler{
collector: collector,
skillMetadataBy: map[string]SkillMetadata{
"after_sales_escalation_skill": {
Code: "after_sales_escalation_skill",
Name: "售后升级",
AllowedToolCodes: []string{"graph/handoff_to_human"},
},
},
}
handler.tryActivateSkill(`{"skill":"after_sales_escalation_skill"}`)
if collector.Data.Skill.Code != "after_sales_escalation_skill" {
t.Fatalf("unexpected skill code: %#v", collector.Data.Skill)
}
if collector.Data.Skill.Name != "售后升级" {
t.Fatalf("unexpected skill name: %#v", collector.Data.Skill)
}
if collector.Data.Skill.RouteReason != "eino_skill_tool" {
t.Fatalf("unexpected route reason: %#v", collector.Data.Skill)
}
if len(collector.Data.Skill.AllowedToolCodes) != 1 || collector.Data.Skill.AllowedToolCodes[0] != "graph/handoff_to_human" {
t.Fatalf("unexpected allowed tools: %#v", collector.Data.Skill.AllowedToolCodes)
}
}
@@ -63,6 +63,43 @@ func (c *RuntimeTraceCollector) SetSkillMiddleware(enabled bool, toolName string
c.Data.Skill.MiddlewareToolName = toolName
}
type SkillMetadata struct {
Code string
Name string
Description string
AllowedToolCodes []string
}
func (c *RuntimeTraceCollector) SetVisibleSkills(skills map[string]SkillMetadata) {
if c == nil || len(skills) == 0 {
return
}
c.mu.Lock()
defer c.mu.Unlock()
codes := make([]string, 0, len(skills))
for code := range skills {
if code == "" {
continue
}
codes = append(codes, code)
}
c.Data.Skill.VisibleCodes = append([]string(nil), codes...)
}
func (c *RuntimeTraceCollector) ActivateSkill(skill SkillMetadata, routeReason string, routeTrace string) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.Data.Skill.Code = skill.Code
c.Data.Skill.Name = skill.Name
c.Data.Skill.Description = skill.Description
c.Data.Skill.AllowedToolCodes = append([]string(nil), skill.AllowedToolCodes...)
c.Data.Skill.RouteReason = routeReason
c.Data.Skill.RouteTrace = routeTrace
}
func (c *RuntimeTraceCollector) SetRetrieverSummary(summary RetrieverTraceSummary) {
if c == nil {
return
@@ -142,11 +142,13 @@ type RuntimeTraceData struct {
type SkillTraceData struct {
Code string `json:"code,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
RouteReason string `json:"routeReason,omitempty"`
RouteTrace string `json:"routeTrace,omitempty"`
AllowedToolCodes []string `json:"allowedToolCodes,omitempty"`
MiddlewareEnabled bool `json:"middlewareEnabled,omitempty"`
MiddlewareToolName string `json:"middlewareToolName,omitempty"`
VisibleCodes []string `json:"visibleCodes,omitempty"`
}
type InterruptTraceContext struct {