refactor: replace skill code with skill ID across the application

- Updated skill handling to use skill IDs instead of skill codes in various components, services, and models.
- Modified tests to reflect changes in skill identification.
- Removed references to skill codes in favor of skill IDs for consistency and clarity.
- Updated localization files to remove skill code references and adjust error messages accordingly.
This commit is contained in:
mlogclub
2026-06-20 20:42:07 +08:00
parent 39c648f3c1
commit 541e4c5874
49 changed files with 258 additions and 341 deletions
@@ -13,7 +13,7 @@ func toSummary(summary *executor.RunResult) *Summary {
RunID: summary.RunID,
Status: summary.Status,
ReplyText: summary.ReplyText,
PlannedSkillCode: strings.TrimSpace(summary.SelectedSkillCode),
PlannedSkillID: summary.SelectedSkillID,
PlannedSkillName: strings.TrimSpace(summary.SelectedSkillName),
PlanReason: strings.TrimSpace(summary.SkillRouteReason),
SkillRouteTrace: strings.TrimSpace(summary.SkillRouteTrace),
+1 -1
View File
@@ -33,7 +33,7 @@ type Summary struct {
RunID string
Status string
ReplyText string
PlannedSkillCode string
PlannedSkillID int64
PlannedSkillName string
PlanReason string
SkillRouteTrace string
+14 -7
View File
@@ -2,6 +2,7 @@ package runtime
import (
"context"
"fmt"
"strings"
applicationruntime "agent-desk/internal/ai/application/runtime"
@@ -28,6 +29,12 @@ func DebugRunSkill(ctx context.Context, req request.SkillDebugRunRequest) (*resp
if aiConfig == nil {
return nil, errorsx.InvalidParamI18n("error.e0008")
}
skill := svc.SkillDefinitionService.Get(req.SkillDefinitionID)
if skill == nil || skill.Status != enums.StatusOk {
return nil, errorsx.InvalidParamI18n("error.e0054")
}
debugAgent := *aiAgent
debugAgent.SkillIDs = fmt.Sprintf("%d", skill.ID)
var conversation *models.Conversation
if req.ConversationID > 0 {
if conversation = svc.ConversationService.Get(req.ConversationID); conversation == nil {
@@ -45,13 +52,13 @@ func DebugRunSkill(ctx context.Context, req request.SkillDebugRunRequest) (*resp
summary, err := Service.Run(ctx, applicationruntime.Request{
Conversation: *conversation,
UserMessage: message,
AIAgent: *aiAgent,
AIAgent: debugAgent,
AIConfig: *aiConfig,
})
if err != nil {
return buildSkillDebugRunResponse(req, summary, nil), err
return buildSkillDebugRunResponse(req, summary, skill), err
}
return buildSkillDebugRunResponse(req, summary, nil), nil
return buildSkillDebugRunResponse(req, summary, skill), nil
}
func DebugResumeSkill(ctx context.Context, req request.SkillDebugResumeRequest) (*response.SkillDebugRunResponse, error) {
@@ -125,14 +132,14 @@ func buildSkillDebugRunResponse(req request.SkillDebugRunRequest, summary *appli
AIAgentID: req.AIAgentID,
}
if skill != nil {
resp.SkillCode = skill.Code
resp.SkillDefinitionID = skill.ID
resp.SkillName = skill.Name
}
if summary == nil {
return resp
}
if resp.SkillCode == "" {
resp.SkillCode = strings.TrimSpace(summary.PlannedSkillCode)
if resp.SkillDefinitionID <= 0 {
resp.SkillDefinitionID = summary.PlannedSkillID
}
resp.ReplyText = summary.ReplyText
resp.PlanReason = summary.PlanReason
@@ -159,7 +166,7 @@ func buildSkillDebugResumeResponse(req request.SkillDebugResumeRequest, summary
if summary == nil {
return resp
}
resp.SkillCode = strings.TrimSpace(summary.PlannedSkillCode)
resp.SkillDefinitionID = summary.PlannedSkillID
resp.SkillName = strings.TrimSpace(summary.PlannedSkillName)
resp.ReplyText = summary.ReplyText
resp.PlanReason = summary.PlanReason
+1 -1
View File
@@ -213,7 +213,7 @@ func syncSkillSummaryFromCollector(summary *RunResult, collector *callbacks.Runt
return
}
trace := collector.Data.Skill
summary.SelectedSkillCode = strings.TrimSpace(trace.Code)
summary.SelectedSkillID = trace.ID
summary.SelectedSkillName = strings.TrimSpace(trace.Name)
summary.SkillRouteReason = strings.TrimSpace(trace.RouteReason)
summary.SkillRouteTrace = strings.TrimSpace(trace.RouteTrace)
+1 -1
View File
@@ -33,7 +33,7 @@ type RunResult struct {
RunID string
Status string
ReplyText string
SelectedSkillCode string
SelectedSkillID int64
SelectedSkillName string
SkillRouteReason string
SkillRouteTrace string
+2 -2
View File
@@ -16,7 +16,7 @@ func BuildSelectedSkillActivationInstruction(skill *models.SkillDefinition) stri
}
lines := []string{
"当前命中的专项技能:",
fmt.Sprintf("- code: %s", strings.TrimSpace(skill.Code)),
fmt.Sprintf("- id: %d", skill.ID),
fmt.Sprintf("- name: %s", strings.TrimSpace(skill.Name)),
}
if desc := strings.TrimSpace(skill.Description); desc != "" {
@@ -36,7 +36,7 @@ func BuildSkillDocument(skill *models.SkillDefinition, toolDefinitions []runtime
}
lines := []string{
"当前命中的专项技能:",
fmt.Sprintf("- code: %s", strings.TrimSpace(skill.Code)),
fmt.Sprintf("- id: %d", skill.ID),
fmt.Sprintf("- name: %s", strings.TrimSpace(skill.Name)),
}
if desc := strings.TrimSpace(skill.Description); desc != "" {
@@ -3,6 +3,7 @@ package callbacks
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
@@ -142,17 +143,19 @@ func (h *RuntimeTraceHandler) tryActivateSkill(argumentsInJSON string) {
if err := json.Unmarshal([]byte(strings.TrimSpace(argumentsInJSON)), &args); err != nil {
return
}
code := strings.TrimSpace(args.Skill)
if code == "" {
skillKey := strings.TrimSpace(args.Skill)
if skillKey == "" {
return
}
meta, ok := h.skillMetadataBy[code]
meta, ok := h.skillMetadataBy[skillKey]
if !ok {
meta = SkillMetadata{Code: code}
if id, err := strconv.ParseInt(skillKey, 10, 64); err == nil {
meta = SkillMetadata{ID: id}
}
}
buf, err := json.Marshal(map[string]any{
"source": "eino_skill_tool",
"skill": code,
"source": "eino_skill_tool",
"skillId": skillKey,
})
routeTrace := ""
if err == nil {
@@ -42,18 +42,18 @@ func TestTryActivateSkill(t *testing.T) {
handler := &RuntimeTraceHandler{
collector: collector,
skillMetadataBy: map[string]SkillMetadata{
"after_sales_escalation_skill": {
Code: "after_sales_escalation_skill",
"44": {
ID: 44,
Name: "售后升级",
AllowedToolCodes: []string{"graph/handoff_to_human"},
},
},
}
handler.tryActivateSkill(`{"skill":"after_sales_escalation_skill"}`)
handler.tryActivateSkill(`{"skill":"44"}`)
if collector.Data.Skill.Code != "after_sales_escalation_skill" {
t.Fatalf("unexpected skill code: %#v", collector.Data.Skill)
if collector.Data.Skill.ID != 44 {
t.Fatalf("unexpected skill id: %#v", collector.Data.Skill)
}
if collector.Data.Skill.Name != "售后升级" {
t.Fatalf("unexpected skill name: %#v", collector.Data.Skill)
@@ -52,7 +52,7 @@ func (c *RuntimeTraceCollector) SetSkillMiddleware(enabled bool, toolName string
}
type SkillMetadata struct {
Code string
ID int64
Name string
Description string
AllowedToolCodes []string
@@ -64,20 +64,20 @@ func (c *RuntimeTraceCollector) SetVisibleSkills(skills map[string]SkillMetadata
}
c.mu.Lock()
defer c.mu.Unlock()
codes := make([]string, 0, len(skills))
for code := range skills {
if code == "" {
ids := make([]int64, 0, len(skills))
for _, skill := range skills {
if skill.ID <= 0 {
continue
}
codes = append(codes, code)
ids = append(ids, skill.ID)
}
c.Data.Skill.VisibleCodes = append([]string(nil), codes...)
c.Data.Skill.VisibleIDs = append([]int64(nil), ids...)
}
func (c *RuntimeTraceCollector) ActivateSkill(skill SkillMetadata, routeReason string, routeTrace string) {
c.mu.Lock()
defer c.mu.Unlock()
c.Data.Skill.Code = skill.Code
c.Data.Skill.ID = skill.ID
c.Data.Skill.Name = skill.Name
c.Data.Skill.Description = skill.Description
c.Data.Skill.AllowedToolCodes = append([]string(nil), skill.AllowedToolCodes...)
@@ -152,7 +152,7 @@ type RuntimeTraceData struct {
}
type SkillTraceData struct {
Code string `json:"code,omitempty"`
ID int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
RouteReason string `json:"routeReason,omitempty"`
@@ -161,7 +161,7 @@ type SkillTraceData struct {
FilteredToolCodes []string `json:"filteredToolCodes,omitempty"`
MiddlewareEnabled bool `json:"middlewareEnabled,omitempty"`
MiddlewareToolName string `json:"middlewareToolName,omitempty"`
VisibleCodes []string `json:"visibleCodes,omitempty"`
VisibleIDs []int64 `json:"visibleIds,omitempty"`
}
type InterruptTraceContext struct {
@@ -38,19 +38,19 @@ func NewAgentHandlerService(skillMiddleware *SkillMiddlewareService) *AgentHandl
func (s *AgentHandlerService) Build(ctx context.Context, input BuildAgentHandlersInput) ([]adk.ChatModelAgentMiddleware, error) {
handlers := make([]adk.ChatModelAgentMiddleware, 0, 4)
skillMetadataByCode := buildRuntimeSkillMetadataMap(input.AIAgent)
toolMetadataBy := buildRuntimeTraceToolMetadata(input.DynamicToolDefinitions, input.StaticToolMetadata, len(skillMetadataByCode) > 0)
traceSkillMetadata := make(map[string]einocallbacks.SkillMetadata, len(skillMetadataByCode))
for code, item := range skillMetadataByCode {
traceSkillMetadata[code] = einocallbacks.SkillMetadata{
Code: item.Code,
skillMetadataByID := buildRuntimeSkillMetadataMap(input.AIAgent)
toolMetadataBy := buildRuntimeTraceToolMetadata(input.DynamicToolDefinitions, input.StaticToolMetadata, len(skillMetadataByID) > 0)
traceSkillMetadata := make(map[string]einocallbacks.SkillMetadata, len(skillMetadataByID))
for id, item := range skillMetadataByID {
traceSkillMetadata[id] = einocallbacks.SkillMetadata{
ID: item.ID,
Name: item.Name,
Description: item.Description,
AllowedToolCodes: append([]string(nil), item.AllowedToolCodes...),
}
}
if input.Collector != nil {
if len(skillMetadataByCode) > 0 {
if len(skillMetadataByID) > 0 {
input.Collector.SetSkillMiddleware(true, toolx.BuiltinSkill.Name)
}
input.Collector.SetVisibleSkills(traceSkillMetadata)
@@ -66,7 +66,7 @@ func (s *AgentHandlerService) Build(ctx context.Context, input BuildAgentHandler
}
handlers = append(handlers, toolSearchHandler)
}
if len(skillMetadataByCode) > 0 {
if len(skillMetadataByID) > 0 {
skillHandler, err := s.skillMiddleware.Build(ctx, input.AIAgent, input.InstructionToolDefinitions)
if err != nil {
return nil, err
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
runtimeinstruction "agent-desk/internal/ai/runtime/instruction"
@@ -17,7 +18,7 @@ import (
)
type runtimeSkillMetadata struct {
Code string
ID int64
Name string
Description string
AllowedToolCodes []string
@@ -25,7 +26,7 @@ type runtimeSkillMetadata struct {
type databaseSkillBackend struct {
toolDefinitions []runtimetooling.MCPToolDefinition
skillsByCode map[string]models.SkillDefinition
skillsByID map[string]models.SkillDefinition
order []string
}
@@ -36,18 +37,18 @@ func newDatabaseSkillBackend(aiAgent models.AIAgent, toolDefinitions []runtimeto
}
ret := &databaseSkillBackend{
toolDefinitions: append([]runtimetooling.MCPToolDefinition(nil), toolDefinitions...),
skillsByCode: make(map[string]models.SkillDefinition, len(visibleSkills)),
skillsByID: make(map[string]models.SkillDefinition, len(visibleSkills)),
order: make([]string, 0, len(visibleSkills)),
}
for _, item := range visibleSkills {
code := strings.TrimSpace(item.Code)
if code == "" {
id := strconv.FormatInt(item.ID, 10)
if id == "" {
continue
}
ret.skillsByCode[code] = item
ret.order = append(ret.order, code)
ret.skillsByID[id] = item
ret.order = append(ret.order, id)
}
if len(ret.skillsByCode) == 0 {
if len(ret.skillsByID) == 0 {
return nil, fmt.Errorf("no visible skills available")
}
return ret, nil
@@ -58,13 +59,13 @@ func (b *databaseSkillBackend) List(_ context.Context) ([]einoskill.FrontMatter,
return nil, nil
}
ret := make([]einoskill.FrontMatter, 0, len(b.order))
for _, code := range b.order {
item, ok := b.skillsByCode[code]
for _, id := range b.order {
item, ok := b.skillsByID[id]
if !ok {
continue
}
ret = append(ret, einoskill.FrontMatter{
Name: strings.TrimSpace(item.Code),
Name: strconv.FormatInt(item.ID, 10),
Description: skillListDescription(item),
})
}
@@ -79,13 +80,13 @@ func (b *databaseSkillBackend) Get(_ context.Context, name string) (einoskill.Sk
if name == "" {
return einoskill.Skill{}, fmt.Errorf("skill name is empty")
}
item, ok := b.skillsByCode[name]
item, ok := b.skillsByID[name]
if !ok {
return einoskill.Skill{}, fmt.Errorf("skill %q not found", name)
}
return einoskill.Skill{
FrontMatter: einoskill.FrontMatter{
Name: strings.TrimSpace(item.Code),
Name: strconv.FormatInt(item.ID, 10),
Description: skillListDescription(item),
},
Content: runtimeinstruction.BuildSkillDocument(&item, filterSkillToolDefinitions(b.toolDefinitions, &item)),
@@ -105,7 +106,7 @@ func loadVisibleSkills(aiAgent models.AIAgent) []models.SkillDefinition {
ret := make([]models.SkillDefinition, 0, len(ids))
for _, id := range ids {
item, ok := byID[id]
if !ok || item.Status != enums.StatusOk || strings.TrimSpace(item.Code) == "" {
if !ok || item.Status != enums.StatusOk || item.ID <= 0 {
continue
}
ret = append(ret, item)
@@ -120,12 +121,12 @@ func buildRuntimeSkillMetadataMap(aiAgent models.AIAgent) map[string]runtimeSkil
}
ret := make(map[string]runtimeSkillMetadata, len(visibleSkills))
for _, item := range visibleSkills {
code := strings.TrimSpace(item.Code)
if code == "" {
if item.ID <= 0 {
continue
}
ret[code] = runtimeSkillMetadata{
Code: code,
id := strconv.FormatInt(item.ID, 10)
ret[id] = runtimeSkillMetadata{
ID: item.ID,
Name: strings.TrimSpace(item.Name),
Description: skillListDescription(item),
AllowedToolCodes: parseSkillToolWhitelist(item.ToolWhitelist),
@@ -145,7 +146,7 @@ func skillListDescription(item models.SkillDefinition) string {
if name := strings.TrimSpace(item.Name); name != "" {
return name
}
return strings.TrimSpace(item.Code)
return fmt.Sprintf("Skill %d", item.ID)
}
func parseSkillToolWhitelist(raw string) []string {
@@ -19,7 +19,6 @@ func TestDatabaseSkillBackendListAndGet(t *testing.T) {
setupSkillBackendTestDB(t)
createSkillDefinitionForTest(t, models.SkillDefinition{
ID: 1,
Code: "after_sales_escalation_skill",
Name: "售后升级",
Description: "处理转人工和升级诉求",
Instruction: "请优先判断是否需要转人工。",
@@ -28,7 +27,6 @@ func TestDatabaseSkillBackendListAndGet(t *testing.T) {
})
createSkillDefinitionForTest(t, models.SkillDefinition{
ID: 2,
Code: "disabled_skill",
Name: "禁用技能",
Description: "不会被暴露",
Instruction: "noop",
@@ -47,15 +45,15 @@ func TestDatabaseSkillBackendListAndGet(t *testing.T) {
if err != nil {
t.Fatalf("List returned error: %v", err)
}
if len(matters) != 1 || matters[0].Name != "after_sales_escalation_skill" {
if len(matters) != 1 || matters[0].Name != "1" {
t.Fatalf("unexpected matters: %#v", matters)
}
skill, err := backend.Get(context.Background(), "after_sales_escalation_skill")
skill, err := backend.Get(context.Background(), "1")
if err != nil {
t.Fatalf("Get returned error: %v", err)
}
if skill.Name != "after_sales_escalation_skill" {
if skill.Name != "1" {
t.Fatalf("unexpected skill name: %#v", skill)
}
if skill.Content == "" || !containsAll(skill.Content, "处理转人工和升级诉求", "graph/handoff_to_human") {
@@ -67,7 +65,6 @@ func TestHasVisibleSkills(t *testing.T) {
setupSkillBackendTestDB(t)
createSkillDefinitionForTest(t, models.SkillDefinition{
ID: 3,
Code: "enabled_skill",
Name: "启用技能",
Description: "可见",
Instruction: "noop",
@@ -75,7 +72,6 @@ func TestHasVisibleSkills(t *testing.T) {
})
createSkillDefinitionForTest(t, models.SkillDefinition{
ID: 4,
Code: "deleted_skill",
Name: "删除技能",
Description: "不可见",
Instruction: "noop",
@@ -15,7 +15,7 @@ import (
"github.com/cloudwego/eino/schema"
)
const activeSkillRunLocalKey = "runtime_active_skill_code"
const activeSkillRunLocalKey = "runtime_active_skill_id"
type RuntimeToolFilterMiddleware struct {
*adk.BaseChatModelAgentMiddleware
@@ -72,7 +72,7 @@ func (m *RuntimeToolFilterMiddleware) WrapInvokableToolCall(_ context.Context, e
return result, err
}
if strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinSkill.Code {
_ = m.setActiveSkill(ctx, skillCodeFromArguments(argumentsInJSON))
_ = m.setActiveSkill(ctx, skillIDFromArguments(argumentsInJSON))
return result, nil
}
if strings.TrimSpace(metadata.ToolCode) == toolx.BuiltinToolSearch.Code {
@@ -90,7 +90,7 @@ func (m *RuntimeToolFilterMiddleware) WrapInvokableToolCall(_ context.Context, e
}
func (m *RuntimeToolFilterMiddleware) blockToolCall(metadata einocallbacks.ToolMetadata, argumentsInJSON string, activeSkill einocallbacks.SkillMetadata) error {
err := fmt.Errorf("tool %s is not allowed for active skill %s", strings.TrimSpace(metadata.ToolCode), strings.TrimSpace(activeSkill.Code))
err := fmt.Errorf("tool %s is not allowed for active skill %d", strings.TrimSpace(metadata.ToolCode), activeSkill.ID)
if m.collector != nil {
m.collector.AddToolItem(einocallbacks.ToolTraceItem{
ToolCode: strings.TrimSpace(metadata.ToolCode),
@@ -106,12 +106,12 @@ func (m *RuntimeToolFilterMiddleware) blockToolCall(metadata einocallbacks.ToolM
return err
}
func (m *RuntimeToolFilterMiddleware) setActiveSkill(ctx context.Context, skillCode string) error {
skillCode = strings.TrimSpace(skillCode)
if skillCode == "" {
func (m *RuntimeToolFilterMiddleware) setActiveSkill(ctx context.Context, skillID string) error {
skillID = strings.TrimSpace(skillID)
if skillID == "" {
return nil
}
return adk.SetRunLocalValue(ctx, activeSkillRunLocalKey, skillCode)
return adk.SetRunLocalValue(ctx, activeSkillRunLocalKey, skillID)
}
func (m *RuntimeToolFilterMiddleware) resolveActiveSkill(ctx context.Context) (einocallbacks.SkillMetadata, bool) {
@@ -122,15 +122,15 @@ func (m *RuntimeToolFilterMiddleware) resolveActiveSkill(ctx context.Context) (e
if err != nil || !found {
return einocallbacks.SkillMetadata{}, false
}
code, ok := value.(string)
skillID, ok := value.(string)
if !ok {
return einocallbacks.SkillMetadata{}, false
}
code = strings.TrimSpace(code)
if code == "" {
skillID = strings.TrimSpace(skillID)
if skillID == "" {
return einocallbacks.SkillMetadata{}, false
}
skill, ok := m.skillMetadataBy[code]
skill, ok := m.skillMetadataBy[skillID]
if !ok {
return einocallbacks.SkillMetadata{}, false
}
@@ -179,12 +179,12 @@ func resolveActiveSkillMetadata(ctx context.Context, skills map[string]einocallb
if err != nil || !found {
return einocallbacks.SkillMetadata{}, false
}
code, ok := value.(string)
skillID, ok := value.(string)
if !ok {
return einocallbacks.SkillMetadata{}, false
}
code = strings.TrimSpace(code)
skill, ok := skills[code]
skillID = strings.TrimSpace(skillID)
skill, ok := skills[skillID]
if !ok || len(skill.AllowedToolCodes) == 0 {
return skill, false
}
@@ -330,7 +330,7 @@ func resolveRuntimeToolMetadata(toolName string, toolMetadataByName map[string]e
return metadata, ok
}
func skillCodeFromArguments(argumentsInJSON string) string {
func skillIDFromArguments(argumentsInJSON string) string {
var args struct {
Skill string `json:"skill"`
}
+1 -1
View File
@@ -26,7 +26,7 @@ func TestSummaryPrimaryToolCodePrefersToolSearchTarget(t *testing.T) {
}
func TestToRunLogFinalAction(t *testing.T) {
if got := toRunLogFinalAction(&applicationruntime.Summary{PlannedSkillCode: "refund", ReplyText: "ok"}); got != "skill" {
if got := toRunLogFinalAction(&applicationruntime.Summary{PlannedSkillID: 44, ReplyText: "ok"}); got != "skill" {
t.Fatalf("expected skill final action, got %q", got)
}
+6 -6
View File
@@ -46,7 +46,7 @@ func (s *replyRunLogService) Write(input replyRunLogInput) {
AIConfigID: input.AIAgent.AIConfigID,
UserMessage: strings.TrimSpace(input.Question),
PlannedAction: plannedAction,
PlannedSkillCode: strings.TrimSpace(summaryPlannedSkillCode(input.Summary)),
PlannedSkillID: summaryPlannedSkillID(input.Summary),
PlannedSkillName: strings.TrimSpace(summaryPlannedSkillName(input.Summary)),
SkillRouteTrace: strings.TrimSpace(summarySkillRouteTrace(input.Summary)),
ToolSearchTrace: extractToolSearchTrace(input.Summary),
@@ -90,7 +90,7 @@ func buildRunLogPlan(summary *applicationruntime.Summary) (plannedAction, planne
if summary == nil {
return "", "", ""
}
if skillCode := strings.TrimSpace(summaryPlannedSkillCode(summary)); skillCode != "" {
if summaryPlannedSkillID(summary) > 0 {
reason := strings.TrimSpace(summary.PlanReason)
if reason == "" {
reason = "skill_selected"
@@ -138,7 +138,7 @@ func toRunLogFinalAction(summary *applicationruntime.Summary) string {
if summary == nil {
return ""
}
if skillCode := strings.TrimSpace(summaryPlannedSkillCode(summary)); skillCode != "" && strings.TrimSpace(summary.ReplyText) != "" {
if summaryPlannedSkillID(summary) > 0 && strings.TrimSpace(summary.ReplyText) != "" {
return "skill"
}
if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" && strings.TrimSpace(summary.ReplyText) != "" {
@@ -167,11 +167,11 @@ func buildRunLogReplyText(summary *applicationruntime.Summary) string {
return strings.TrimSpace(summary.ReplyText)
}
func summaryPlannedSkillCode(summary *applicationruntime.Summary) string {
func summaryPlannedSkillID(summary *applicationruntime.Summary) int64 {
if summary == nil {
return ""
return 0
}
return strings.TrimSpace(summary.PlannedSkillCode)
return summary.PlannedSkillID
}
func summaryPlannedSkillName(summary *applicationruntime.Summary) string {
+2 -2
View File
@@ -72,8 +72,8 @@ func TestResolveReplyTimeout(t *testing.T) {
func TestBuildRunLogPlan(t *testing.T) {
summary := &applicationruntime.Summary{
PlannedSkillCode: "faq_router",
PlanReason: "manual",
PlannedSkillID: 44,
PlanReason: "manual",
}
action, toolCode, reason := buildRunLogPlan(summary)
if action != "skill" || toolCode != "" || reason != "manual" {
+5 -2
View File
@@ -16,8 +16,11 @@ var newCandidateLoader = func() *candidateLoader {
type candidateLoader struct {
}
func (l *candidateLoader) findManualSkillDefinition(skillCode string) *models.SkillDefinition {
return repositories.SkillDefinitionRepository.GetByCode(sqls.DB(), skillCode)
func (l *candidateLoader) findManualSkillDefinition(skillDefinitionID int64) *models.SkillDefinition {
if skillDefinitionID <= 0 {
return nil
}
return repositories.SkillDefinitionRepository.Get(sqls.DB(), skillDefinitionID)
}
func (l *candidateLoader) loadCandidateSkills(aiAgent models.AIAgent) []models.SkillDefinition {
+7 -11
View File
@@ -11,12 +11,11 @@ import (
func TestBuildRunLogMatchedPlan(t *testing.T) {
log := BuildRunLog(
RuntimeContext{
AIAgent: models.AIAgent{ID: 22},
AIConfig: models.AIConfig{ID: 33},
ConversationID: 11,
ManualSkillCode: "manual_refund",
IntentCode: "refund",
UserMessage: "我要退款",
AIAgent: models.AIAgent{ID: 22},
AIConfig: models.AIConfig{ID: 33},
ConversationID: 11,
ManualSkillDefinitionID: 44,
UserMessage: "我要退款",
},
&ExecutionPlan{
AIAgent: models.AIAgent{ID: 22},
@@ -25,10 +24,7 @@ func TestBuildRunLogMatchedPlan(t *testing.T) {
ModelName: "gpt-test",
Provider: enums.AIProviderOpenAI,
},
Skill: &models.SkillDefinition{
ID: 44,
Code: "refund_skill",
},
Skill: &models.SkillDefinition{ID: 44},
MatchReason: "llm_route",
},
&ExecutionTrace{Status: "ok"},
@@ -41,7 +37,7 @@ func TestBuildRunLogMatchedPlan(t *testing.T) {
if log.ConversationID != 11 || log.AIAgentID != 22 || log.AIConfigID != 33 {
t.Fatalf("unexpected ids in run log: %#v", log)
}
if !log.Matched || !log.FinalSelected || log.SkillCode != "refund_skill" {
if !log.Matched || !log.FinalSelected || log.SkillDefinitionID != 44 {
t.Fatalf("expected matched skill log, got %#v", log)
}
if log.MatchReason != "llm_route" {
+9 -23
View File
@@ -2,13 +2,10 @@ package skills
import (
"context"
"strings"
"agent-desk/internal/models"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/pkg/errorsx"
"github.com/mlogclub/simple/common/strs"
)
type intentTriggerConfig struct {
@@ -18,45 +15,34 @@ type intentTriggerConfig struct {
// MatchSkill 对单个 SkillDefinition 执行命中判断。
func MatchSkill(execCtx context.Context, ctx RuntimeContext) (*models.SkillDefinition, string, *RouteTrace, error) {
loader := newCandidateLoader()
if strs.IsNotBlank(ctx.ManualSkillCode) {
skill := loader.findManualSkillDefinition(ctx.ManualSkillCode)
if ctx.ManualSkillDefinitionID > 0 {
skill := loader.findManualSkillDefinition(ctx.ManualSkillDefinitionID)
if skill == nil || skill.Status != enums.StatusOk {
return nil, "", nil, errorsx.InvalidParamI18n("error.e0054")
}
return skill, "manual_skill_code", &RouteTrace{
Status: "manual_selected",
SelectedSkillCode: skill.Code,
return skill, "manual_skill_id", &RouteTrace{
Status: "manual_selected",
SelectedSkillID: skill.ID,
}, nil
}
candidates := loader.loadCandidateSkills(ctx.AIAgent)
trace := &RouteTrace{
Status: "started",
CandidateSkillCodes: make([]string, 0, len(candidates)),
Status: "started",
CandidateSkillIDs: make([]int64, 0, len(candidates)),
}
for _, item := range candidates {
trace.CandidateSkillCodes = append(trace.CandidateSkillCodes, item.Code)
trace.CandidateSkillIDs = append(trace.CandidateSkillIDs, item.ID)
}
if len(candidates) == 0 {
trace.Status = "no_candidate"
return nil, "no_enabled_skill_bound", trace, nil
}
intentCode := strings.TrimSpace(ctx.IntentCode)
if intentCode != "" {
for _, item := range candidates {
if strings.EqualFold(strings.TrimSpace(item.Code), intentCode) {
trace.Status = "intent_selected"
trace.SelectedSkillCode = item.Code
return &item, "intent_code", trace, nil
}
}
}
selected, routeTrace, err := routeSkillWithLLM(execCtx, ctx, candidates)
if routeTrace != nil {
trace.Status = routeTrace.Status
trace.SelectedSkillCode = routeTrace.SelectedSkillCode
trace.SelectedSkillID = routeTrace.SelectedSkillID
trace.RawDecision = routeTrace.RawDecision
trace.LatencyMs = routeTrace.LatencyMs
trace.Error = routeTrace.Error
+13 -6
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
@@ -13,10 +14,10 @@ import (
"github.com/mlogclub/simple/common/strs"
)
const routeSkillSystemPrompt = `你是客服技能路由器。你只能在候选 Skill 中选择一个最合适的 skillCode,或者返回 NONE。
const routeSkillSystemPrompt = `你是客服技能路由器。你只能在候选 Skill 中选择一个最合适的 skillId,或者返回 NONE。
只有当用户问题与 Skill 的职责边界明确匹配时才选择;
如果不明确、信息不足、多个 Skill 都不够确定,就返回 NONE。
输出只能是 skillCode 或 NONE,不能输出其他内容。`
输出只能是 skillId 或 NONE,不能输出其他内容。`
func routeSkillWithLLM(ctx context.Context, runtimeCtx RuntimeContext, candidates []models.SkillDefinition) (*models.SkillDefinition, *RouteTrace, error) {
trace := &RouteTrace{Status: "started"}
@@ -43,10 +44,16 @@ func routeSkillWithLLM(ctx context.Context, runtimeCtx RuntimeContext, candidate
trace.Status = "not_matched"
return nil, trace, nil
}
selectedID, parseErr := strconv.ParseInt(decision, 10, 64)
if parseErr != nil || selectedID <= 0 {
trace.Status = "invalid_decision"
trace.Error = fmt.Sprintf("invalid route decision: %s", decision)
return nil, trace, nil
}
for _, item := range candidates {
if strings.EqualFold(item.Code, decision) {
if item.ID == selectedID {
trace.Status = "llm_selected"
trace.SelectedSkillCode = item.Code
trace.SelectedSkillID = item.ID
return &item, trace, nil
}
}
@@ -62,14 +69,14 @@ func buildSkillRoutePrompt(userMessage string, candidates []models.SkillDefiniti
lines = append(lines, "")
lines = append(lines, "候选 Skills")
for _, item := range candidates {
line := fmt.Sprintf("- skillCode=%s; name=%s; description=%s", strings.TrimSpace(item.Code), strings.TrimSpace(item.Name), strings.TrimSpace(item.Description))
line := fmt.Sprintf("- skillId=%d; name=%s; description=%s", item.ID, strings.TrimSpace(item.Name), strings.TrimSpace(item.Description))
if examples := parseSkillExamples(item.Examples); len(examples) > 0 {
line += "; examples=" + strings.Join(examples, " | ")
}
lines = append(lines, line)
}
lines = append(lines, "")
lines = append(lines, "请只输出一个 skillCode 或 NONE。")
lines = append(lines, "请只输出一个 skillId 或 NONE。")
return strings.Join(lines, "\n")
}
+5 -5
View File
@@ -18,7 +18,7 @@ func TestParseSkillExamples(t *testing.T) {
}
func TestNormalizeRouteDecision(t *testing.T) {
if got := normalizeRouteDecision("```refund_skill```\n补充说明"); got != "refund_skill" {
if got := normalizeRouteDecision("```44```\n补充说明"); got != "44" {
t.Fatalf("unexpected normalized decision: %q", got)
}
if got := normalizeRouteDecision(" none "); got != "NONE" {
@@ -29,20 +29,20 @@ func TestNormalizeRouteDecision(t *testing.T) {
func TestBuildSkillRoutePrompt(t *testing.T) {
prompt := buildSkillRoutePrompt("我要申请退款", []models.SkillDefinition{
{
Code: "refund_skill",
ID: 44,
Name: "退款处理",
Description: "负责退款和退货相关问题",
Examples: `["退款进度","退货运费"]`,
},
})
if !strings.Contains(prompt, "skillCode=refund_skill") {
t.Fatalf("expected prompt to include skill code, got %q", prompt)
if !strings.Contains(prompt, "skillId=44") {
t.Fatalf("expected prompt to include skill id, got %q", prompt)
}
if !strings.Contains(prompt, "examples=退款进度 | 退货运费") {
t.Fatalf("expected prompt to include examples, got %q", prompt)
}
if !strings.Contains(prompt, "请只输出一个 skillCode 或 NONE。") {
if !strings.Contains(prompt, "请只输出一个 skillId 或 NONE。") {
t.Fatalf("expected prompt to include output constraint, got %q", prompt)
}
}
+6 -8
View File
@@ -19,13 +19,12 @@ type RunLogService struct{}
// Build 根据执行计划与运行结果构建 Skill 运行日志。
func (s *RunLogService) Build(ctx RuntimeContext, plan *ExecutionPlan, trace *ExecutionTrace, err error) *models.SkillRunLog {
log := &models.SkillRunLog{
ConversationID: ctx.ConversationID,
AIAgentID: ctx.AIAgent.ID,
ManualSkillCode: ctx.ManualSkillCode,
IntentCode: ctx.IntentCode,
UserMessage: ctx.UserMessage,
TraceData: s.buildTraceData(trace),
CreatedAt: time.Now(),
ConversationID: ctx.ConversationID,
AIAgentID: ctx.AIAgent.ID,
ManualSkillID: ctx.ManualSkillDefinitionID,
UserMessage: ctx.UserMessage,
TraceData: s.buildTraceData(trace),
CreatedAt: time.Now(),
}
if plan != nil {
log.AIConfigID = plan.AIConfig.ID
@@ -34,7 +33,6 @@ func (s *RunLogService) Build(ctx RuntimeContext, plan *ExecutionPlan, trace *Ex
if plan.Skill != nil {
log.SkillDefinitionID = plan.Skill.ID
log.SkillCode = plan.Skill.Code
log.Matched = true
log.FinalSelected = true
log.MatchReason = plan.MatchReason
+11 -12
View File
@@ -4,12 +4,11 @@ import "agent-desk/internal/models"
// RuntimeContext 表示一次 Skill 运行的输入上下文。
type RuntimeContext struct {
AIAgent models.AIAgent // AIAgent 为当前请求所属的 AI Agent,必填。
AIConfig models.AIConfig // AIConfig 为当前请求实际使用的模型配置,必填。
UserMessage string // UserMessage 为当前用户输入。
ConversationID int64 // ConversationID 为当前会话 ID,无会话上下文时为 0。
ManualSkillCode string // ManualSkillCode 为显式指定的 Skill 编码
IntentCode string // IntentCode 为上游识别出的意图编码。
AIAgent models.AIAgent // AIAgent 为当前请求所属的 AI Agent,必填。
AIConfig models.AIConfig // AIConfig 为当前请求实际使用的模型配置,必填。
UserMessage string // UserMessage 为当前用户输入。
ConversationID int64 // ConversationID 为当前会话 ID,无会话上下文时为 0。
ManualSkillDefinitionID int64 // ManualSkillDefinitionID 为显式指定的 Skill 定义ID
}
// ExecutionPlan 表示 Skill Runtime 计算出的最终路由结果。
@@ -35,12 +34,12 @@ type ExecutionTrace struct {
}
type RouteTrace struct {
Status string `json:"status"`
CandidateSkillCodes []string `json:"candidateSkillCodes,omitempty"`
SelectedSkillCode string `json:"selectedSkillCode,omitempty"`
RawDecision string `json:"rawDecision,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
Error string `json:"error,omitempty"`
Status string `json:"status"`
CandidateSkillIDs []int64 `json:"candidateSkillIds,omitempty"`
SelectedSkillID int64 `json:"selectedSkillId,omitempty"`
RawDecision string `json:"rawDecision,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
Error string `json:"error,omitempty"`
}
type PromptTrace struct {
+1 -1
View File
@@ -22,7 +22,7 @@ func BuildAgentRunLog(item *models.AgentRunLog) response.AgentRunLogResponse {
AIConfigID: item.AIConfigID,
UserMessage: item.UserMessage,
PlannedAction: item.PlannedAction,
PlannedSkillCode: item.PlannedSkillCode,
PlannedSkillID: item.PlannedSkillID,
PlannedSkillName: item.PlannedSkillName,
SkillRouteTrace: item.SkillRouteTrace,
ToolSearchTrace: item.ToolSearchTrace,
-1
View File
@@ -19,7 +19,6 @@ func BuildSkillDefinitionResponse(item *models.SkillDefinition) response.SkillDe
}
return response.SkillDefinitionResponse{
ID: item.ID,
Code: item.Code,
Name: item.Name,
Description: item.Description,
Instruction: item.Instruction,
@@ -25,7 +25,7 @@ func AgentRunLogAnyList(ctx *gin.Context) {
params.QueryFilter{ParamName: "requestId"},
params.QueryFilter{ParamName: "aiAgentId"},
params.QueryFilter{ParamName: "plannedAction"},
params.QueryFilter{ParamName: "plannedSkillCode", Op: params.Like},
params.QueryFilter{ParamName: "plannedSkillId"},
params.QueryFilter{ParamName: "graphToolCode"},
params.QueryFilter{ParamName: "interruptType"},
params.QueryFilter{ParamName: "resumeSource"},
@@ -215,7 +215,6 @@ func buildAIAgentResponseWithLocale(item *models.AIAgent, locale string) respons
if skill := services.SkillDefinitionService.Get(id); skill != nil {
ret.Skills = append(ret.Skills, response.AIAgentSkillResponse{
ID: skill.ID,
Code: skill.Code,
Name: skill.Name,
})
}
@@ -28,7 +28,6 @@ func SkillDefinitionAnyList(ctx *gin.Context) {
cnd := params.NewPagedSqlCnd(ctx,
params.QueryFilter{ParamName: "status"},
params.QueryFilter{ParamName: "name", Op: params.Like},
params.QueryFilter{ParamName: "code", Op: params.Like},
).Desc("id")
if _, ok := params.Get(ctx, "status"); !ok {
cnd.Where("status <> ?", enums.StatusDeleted)
+24 -27
View File
@@ -818,37 +818,34 @@ type KnowledgeFeedback struct {
// SkillDefinition 表示可由后台配置并参与运行时路由的 Skill 定义。
type SkillDefinition struct {
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为 Skill 主键。
Code string `gorm:"type:varchar(100);not null;default:'';uniqueIndex"` // Code 为 Skill 的稳定唯一编码,供程序内部引用和路由判断使用,例如 refund_skill
Name string `gorm:"type:varchar(100);not null;default:'';index"` // Name 为 Skill 的展示名称,用于后台列表、配置页和人工选择场景
Description string `gorm:"type:varchar(255);not null;default:''"` // Description 为 Skill 的简要说明,用于描述该 Skill 的适用场景和职责边界
Instruction string `gorm:"type:longtext"` // Instruction 为 Skill 的主体说明文档存储字段,使用 Markdown 编写,供 Agent 理解任务目标、步骤和工具使用要求
Examples string `gorm:"type:text"` // Examples 为示例问法 JSON 数组字符串。
ToolWhitelist string `gorm:"type:text"` // ToolWhitelist 为允许使用的工具编码 JSON 数组字符串
Status enums.Status `gorm:"type:int;not null;default:0;index"` // Status 为 Skill 当前状态,使用全局通用状态:0启用 1禁用 2删除
Remark string `gorm:"type:text"` // Remark 为后台备注,用于记录配置说明、维护信息或内部协作信息。
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为 Skill 主键。
Name string `gorm:"type:varchar(100);not null;default:'';index"` // Name 为 Skill 的展示名称,用于后台列表、配置页和人工选择场景
Description string `gorm:"type:varchar(255);not null;default:''"` // Description 为 Skill 的简要说明,用于描述该 Skill 的适用场景和职责边界
Instruction string `gorm:"type:longtext"` // Instruction 为 Skill 的主体说明文档存储字段,使用 Markdown 编写,供 Agent 理解任务目标、步骤和工具使用要求
Examples string `gorm:"type:text"` // Examples 为示例问法 JSON 数组字符串
ToolWhitelist string `gorm:"type:text"` // ToolWhitelist 为允许使用的工具编码 JSON 数组字符串。
Status enums.Status `gorm:"type:int;not null;default:0;index"` // Status 为 Skill 当前状态,使用全局通用状态:0启用 1禁用 2删除
Remark string `gorm:"type:text"` // Remark 为后台备注,用于记录配置说明、维护信息或内部协作信息
AuditFields
}
// SkillRunLog 表示一次 Skill 运行过程的审计日志。
type SkillRunLog struct {
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为 Skill 运行日志主键。
ConversationID int64 `gorm:"type:bigint;not null;default:0;index"` // ConversationID 为关联会话ID,无会话上下文时为0。
AIAgentID int64 `gorm:"type:bigint;not null;default:0;index"` // AIAgentID 为本次运行所属的 AI Agent ID。
AIConfigID int64 `gorm:"type:bigint;not null;default:0;index"` // AIConfigID 为本次运行实际使用的 AI 配置ID。
SkillDefinitionID int64 `gorm:"type:bigint;not null;default:0;index"` // SkillDefinitionID 为最终命中的 Skill 定义ID,未命中时为0。
SkillCode string `gorm:"type:varchar(100);not null;default:'';index"` // SkillCode 为最终命中的 Skill 编码,未命中时为空
ManualSkillCode string `gorm:"type:varchar(100);not null;default:'';index"` // ManualSkillCode 为本次请求显式指定的 Skill 编码
IntentCode string `gorm:"type:varchar(100);not null;default:'';index"` // IntentCode 为上游传入的意图编码
UserMessage string `gorm:"type:longtext"` // UserMessage 为本次请求的用户输入内容
Matched bool `gorm:"not null;default:false;index"` // Matched 表示本次请求是否命中了 Skill。
MatchReason string `gorm:"type:varchar(500);not null;default:''"` // MatchReason 为命中或未命中的原因说明
FinalSelected bool `gorm:"not null;default:false;index"` // FinalSelected 表示该日志记录的 Skill 是否为最终选中的执行 Skill
UsedModel string `gorm:"type:varchar(100);not null;default:''"` // UsedModel 为本次实际调用的模型名称
UsedProvider enums.AIProvider `gorm:"type:varchar(50);not null;default:''"` // UsedProvider 为本次实际调用的模型供应商
ErrorMessage string `gorm:"type:text"` // ErrorMessage 为运行过程中的错误信息
TraceData string `gorm:"type:text"` // TraceData 为 Skill 执行链路追踪数据JSON。
CreatedAt time.Time `gorm:"type:datetime;not null;index"` // CreatedAt 为运行日志创建时间。
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为 Skill 运行日志主键。
ConversationID int64 `gorm:"type:bigint;not null;default:0;index"` // ConversationID 为关联会话ID,无会话上下文时为0。
AIAgentID int64 `gorm:"type:bigint;not null;default:0;index"` // AIAgentID 为本次运行所属的 AI Agent ID。
AIConfigID int64 `gorm:"type:bigint;not null;default:0;index"` // AIConfigID 为本次运行实际使用的 AI 配置ID。
SkillDefinitionID int64 `gorm:"type:bigint;not null;default:0;index"` // SkillDefinitionID 为最终命中的 Skill 定义ID,未命中时为0。
ManualSkillID int64 `gorm:"type:bigint;not null;default:0;index"` // ManualSkillID 为本次请求显式指定的 Skill 定义ID
UserMessage string `gorm:"type:longtext"` // UserMessage 为本次请求的用户输入内容
Matched bool `gorm:"not null;default:false;index"` // Matched 表示本次请求是否命中了 Skill
MatchReason string `gorm:"type:varchar(500);not null;default:''"` // MatchReason 为命中或未命中的原因说明
FinalSelected bool `gorm:"not null;default:false;index"` // FinalSelected 表示该日志记录的 Skill 是否为最终选中的执行 Skill。
UsedModel string `gorm:"type:varchar(100);not null;default:''"` // UsedModel 为本次实际调用的模型名称
UsedProvider enums.AIProvider `gorm:"type:varchar(50);not null;default:''"` // UsedProvider 为本次实际调用的模型供应商
ErrorMessage string `gorm:"type:text"` // ErrorMessage 为运行过程中的错误信息
TraceData string `gorm:"type:text"` // TraceData 为 Skill 执行链路追踪数据JSON
CreatedAt time.Time `gorm:"type:datetime;not null;index"` // CreatedAt 为运行日志创建时间
}
// AgentRunLog 表示一次客服 Agent 自动运行的总链路日志。
@@ -861,7 +858,7 @@ type AgentRunLog struct {
AIConfigID int64 `gorm:"type:bigint;not null;default:0;index"`
UserMessage string `gorm:"type:longtext"`
PlannedAction string `gorm:"type:varchar(30);not null;default:'';index"`
PlannedSkillCode string `gorm:"type:varchar(100);not null;default:'';index"`
PlannedSkillID int64 `gorm:"type:bigint;not null;default:0;index"`
PlannedSkillName string `gorm:"type:varchar(100);not null;default:''"`
SkillRouteTrace string `gorm:"type:text"`
ToolSearchTrace string `gorm:"type:text"`
+4 -6
View File
@@ -2,12 +2,10 @@ package request
type SkillDefinitionListRequest struct {
Name string `json:"name"`
Code string `json:"code"`
Status int `json:"status"`
}
type CreateSkillDefinitionRequest struct {
Code string `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
Instruction string `json:"instruction"`
@@ -35,10 +33,10 @@ type UpdateSkillDefinitionStatusRequest struct {
}
type SkillDebugRunRequest struct {
AIAgentID int64 `json:"aiAgentId"`
ConversationID int64 `json:"conversationId"`
SkillCode string `json:"skillCode"`
UserMessage string `json:"userMessage"`
AIAgentID int64 `json:"aiAgentId"`
ConversationID int64 `json:"conversationId"`
SkillDefinitionID int64 `json:"skillDefinitionId"`
UserMessage string `json:"userMessage"`
}
type SkillDebugResumeRequest struct {
-1
View File
@@ -12,7 +12,6 @@ type AIAgentTeamResponse struct {
type AIAgentSkillResponse struct {
ID int64 `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
}
+19 -20
View File
@@ -4,7 +4,6 @@ import "time"
type SkillDefinitionResponse struct {
ID int64 `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
Instruction string `json:"instruction"`
@@ -20,24 +19,24 @@ type SkillDefinitionResponse struct {
}
type SkillDebugRunResponse struct {
SkillCode string `json:"skillCode"`
SkillName string `json:"skillName"`
ReplyText string `json:"replyText"`
PlanReason string `json:"planReason"`
SkillRouteTrace string `json:"skillRouteTrace"`
ToolWhitelist []string `json:"toolWhitelist"`
ExposedToolCodes []string `json:"exposedToolCodes"`
InvokedToolCodes []string `json:"invokedToolCodes"`
ToolSearchTrace string `json:"toolSearchTrace"`
GraphToolTrace string `json:"graphToolTrace"`
GraphToolCode string `json:"graphToolCode"`
InterruptType string `json:"interruptType"`
CheckPointID string `json:"checkPointId"`
Interrupted bool `json:"interrupted"`
TraceData string `json:"traceData"`
ErrorMessage string `json:"errorMessage"`
ConversationID int64 `json:"conversationId"`
AIAgentID int64 `json:"aiAgentId"`
SkillDefinitionID int64 `json:"skillDefinitionId"`
SkillName string `json:"skillName"`
ReplyText string `json:"replyText"`
PlanReason string `json:"planReason"`
SkillRouteTrace string `json:"skillRouteTrace"`
ToolWhitelist []string `json:"toolWhitelist"`
ExposedToolCodes []string `json:"exposedToolCodes"`
InvokedToolCodes []string `json:"invokedToolCodes"`
ToolSearchTrace string `json:"toolSearchTrace"`
GraphToolTrace string `json:"graphToolTrace"`
GraphToolCode string `json:"graphToolCode"`
InterruptType string `json:"interruptType"`
CheckPointID string `json:"checkPointId"`
Interrupted bool `json:"interrupted"`
TraceData string `json:"traceData"`
ErrorMessage string `json:"errorMessage"`
ConversationID int64 `json:"conversationId"`
AIAgentID int64 `json:"aiAgentId"`
}
type AgentRunLogResponse struct {
@@ -49,7 +48,7 @@ type AgentRunLogResponse struct {
AIConfigID int64 `json:"aiConfigId"`
UserMessage string `json:"userMessage"`
PlannedAction string `json:"plannedAction"`
PlannedSkillCode string `json:"plannedSkillCode"`
PlannedSkillID int64 `json:"plannedSkillId"`
PlannedSkillName string `json:"plannedSkillName"`
SkillRouteTrace string `json:"skillRouteTrace"`
ToolSearchTrace string `json:"toolSearchTrace"`
+3 -3
View File
@@ -54,8 +54,8 @@ error.e0053: "Skill not found."
error.e0054: "Skill not found or not enabled."
error.e0055: "Enter a Skill name."
error.e0056: "Skill is not enabled."
error.e0057: "Enter a Skill code."
error.e0058: "This Skill code is already in use."
error.e0057: "Skill is required."
error.e0058: "This Skill already exists."
error.e0059: "Web channel position must be left or right."
error.e0060: "Invalid web channel configuration."
error.e0061: "AI Agent ID is required."
@@ -68,7 +68,7 @@ error.e0067: "Invalid index status."
error.e0068: "openKfID is required."
error.e0069: "This openKfId is already used by another channel."
error.e0070: "Server code is required."
error.e0071: "Skill code is required."
error.e0071: "Skill definition ID is required."
error.e0072: "Ticket is required."
error.e0073: "The MCP server bound to this tool code does not exist or is not enabled."
error.e0074: "Tool code is required."
+3 -3
View File
@@ -54,8 +54,8 @@ error.e0053: "Skill 不存在"
error.e0054: "Skill 不存在或未启用"
error.e0055: "Skill 名称不能为空"
error.e0056: "Skill 未启用"
error.e0057: "Skill 编码不能为空"
error.e0058: "Skill 编码已存在"
error.e0057: "Skill 参数不能为空"
error.e0058: "Skill 已存在"
error.e0059: "Web渠道配置 position 只能为 left 或 right"
error.e0060: "Web渠道配置不合法"
error.e0061: "aiAgentId不能为空"
@@ -68,7 +68,7 @@ error.e0067: "indexStatus参数不合法"
error.e0068: "openKfID不能为空"
error.e0069: "openKfId 已被其他渠道使用"
error.e0070: "serverCode不能为空"
error.e0071: "skillCode不能为空"
error.e0071: "skillDefinitionId不能为空"
error.e0072: "ticket 不能为空"
error.e0073: "toolCode 绑定的 MCP 服务不存在或未启用"
error.e0074: "toolCode不能为空"
@@ -101,10 +101,6 @@ func (r *skillDefinitionRepository) Delete(db *gorm.DB, id int64) {
db.Delete(&models.SkillDefinition{}, "id = ?", id)
}
func (r *skillDefinitionRepository) GetByCode(db *gorm.DB, code string) *models.SkillDefinition {
return r.FindOne(db, sqls.NewCnd().Where("code = ?", code))
}
func (r *skillDefinitionRepository) GetByIDs(db *gorm.DB, ids []int64) map[int64]models.SkillDefinition {
if len(ids) == 0 {
return nil
@@ -76,10 +76,6 @@ func (s *skillDefinitionService) Delete(id int64) {
repositories.SkillDefinitionRepository.Delete(sqls.DB(), id)
}
func (s *skillDefinitionService) GetByCode(code string) *models.SkillDefinition {
return repositories.SkillDefinitionRepository.GetByCode(sqls.DB(), code)
}
func (s *skillDefinitionService) GetByIDs(ids []int64) map[int64]models.SkillDefinition {
return repositories.SkillDefinitionRepository.GetByIDs(sqls.DB(), ids)
}
@@ -92,11 +88,7 @@ func (s *skillDefinitionService) CreateSkillDefinition(req request.CreateSkillDe
if err != nil {
return nil, err
}
if s.Take("code = ?", normalized.Code) != nil {
return nil, errorsx.InvalidParamI18n("error.e0058")
}
item := &models.SkillDefinition{
Code: normalized.Code,
Name: normalized.Name,
Description: normalized.Description,
Instruction: normalized.Instruction,
@@ -127,11 +119,7 @@ func (s *skillDefinitionService) UpdateSkillDefinition(req request.UpdateSkillDe
if err != nil {
return err
}
if exists := s.Take("code = ? AND id <> ?", normalized.Code, req.ID); exists != nil {
return errorsx.InvalidParamI18n("error.e0058")
}
return repositories.SkillDefinitionRepository.Updates(sqls.DB(), req.ID, map[string]any{
"code": normalized.Code,
"name": normalized.Name,
"description": normalized.Description,
"instruction": normalized.Instruction,
@@ -146,15 +134,11 @@ func (s *skillDefinitionService) UpdateSkillDefinition(req request.UpdateSkillDe
func (s *skillDefinitionService) normalizeSkillDefinitionRequest(req request.CreateSkillDefinitionRequest) (*request.CreateSkillDefinitionRequest, error) {
normalized := &request.CreateSkillDefinitionRequest{
Code: strings.TrimSpace(req.Code),
Name: strings.TrimSpace(req.Name),
Description: strings.TrimSpace(req.Description),
Instruction: strings.TrimSpace(req.Instruction),
Remark: strings.TrimSpace(req.Remark),
}
if normalized.Code == "" {
return nil, errorsx.InvalidParamI18n("error.e0057")
}
if normalized.Name == "" {
return nil, errorsx.InvalidParamI18n("error.e0055")
}
+1 -1
View File
@@ -24,7 +24,7 @@ func (s *skillRuntimeService) DebugRun(ctx context.Context, req request.SkillDeb
if req.AIAgentID <= 0 {
return nil, errorsx.InvalidParamI18n("error.e0061")
}
if strings.TrimSpace(req.SkillCode) == "" {
if req.SkillDefinitionID <= 0 {
return nil, errorsx.InvalidParamI18n("error.e0071")
}
if strings.TrimSpace(req.UserMessage) == "" {