From 976b9defdeb421ce0477ae3356dc740392ae11f2 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Fri, 17 Apr 2026 17:57:01 +0800 Subject: [PATCH] Refactor AI Agent and AI Config handling across multiple files - Updated function signatures to accept AI Agent and AI Config as non-pointer types for better clarity and safety. - Modified instances where AI Agent and AI Config were dereferenced to improve code readability. - Removed unnecessary nil checks for AI Agent and AI Config, simplifying the logic. - Adjusted related tests and services to align with the new function signatures. - Cleaned up code in runtime, skills, and executor packages to ensure consistency in handling AI configurations. --- .../ai/application/runtime/prepare_service.go | 2 +- .../ai/application/runtime/tool_catalog.go | 7 +-- .../application/runtime/tool_catalog_test.go | 4 +- internal/ai/application/runtime/types.go | 8 ++-- internal/ai/embedding.go | 2 +- internal/ai/llm.go | 20 +++------ internal/ai/openai_client.go | 2 +- internal/ai/runtime/debug_run.go | 8 ++-- .../ai/runtime/executor/context_builders.go | 6 +-- .../ai/runtime/executor/knowledge_guard.go | 12 +++-- .../runtime/executor/knowledge_guard_test.go | 6 +-- internal/ai/runtime/executor/service.go | 29 +----------- internal/ai/runtime/executor/types.go | 8 ++-- .../ai/runtime/graphs/create_ticket_graph.go | 6 +-- .../graphs/create_ticket_graph_test.go | 45 ------------------- internal/ai/runtime/graphs/handoff_graph.go | 6 +-- .../ai/runtime/graphs/handoff_graph_test.go | 41 ----------------- internal/ai/runtime/instruction/service.go | 8 +--- .../internal/impl/factory/agent_factory.go | 7 +-- .../impl/factory/chat_model_factory.go | 32 ++++++------- .../internal/impl/factory/tool_factory.go | 7 +-- .../impl/factory/tool_factory_test.go | 2 +- .../impl/retrievers/knowledge_retriever.go | 7 +-- internal/ai/runtime/registry/registry_test.go | 2 +- internal/ai/runtime/registry/types.go | 4 +- internal/ai/runtime/runtime_reply_executor.go | 8 ++-- .../tools/create_ticket_confirm_tool.go | 6 +-- .../ai/runtime/tools/handoff_graph_tool.go | 6 +-- internal/ai/skills/candidate_loader.go | 5 +-- internal/ai/skills/log_test.go | 12 ++--- internal/ai/skills/matcher.go | 6 +-- internal/ai/skills/plan_service.go | 10 +---- internal/ai/skills/router.go | 8 +--- internal/ai/skills/runlog_service.go | 21 +++------ internal/ai/skills/types.go | 16 +++---- internal/services/conversation_service.go | 5 +-- 36 files changed, 102 insertions(+), 282 deletions(-) delete mode 100644 internal/ai/runtime/graphs/create_ticket_graph_test.go delete mode 100644 internal/ai/runtime/graphs/handoff_graph_test.go diff --git a/internal/ai/application/runtime/prepare_service.go b/internal/ai/application/runtime/prepare_service.go index 8a1b9f8..b9938cd 100644 --- a/internal/ai/application/runtime/prepare_service.go +++ b/internal/ai/application/runtime/prepare_service.go @@ -18,7 +18,7 @@ type prepareService struct { } func (s *prepareService) selectSkill(ctx context.Context, req Request) (*models.SkillDefinition, string, string, error) { - if req.AIAgent == nil || req.AIConfig == nil || req.UserMessage == nil || req.Conversation == nil { + if req.UserMessage == nil || req.Conversation == nil { return nil, "", "", nil } result, err := skills.Select(ctx, skills.RuntimeContext{ diff --git a/internal/ai/application/runtime/tool_catalog.go b/internal/ai/application/runtime/tool_catalog.go index c19e530..6f5ff6f 100644 --- a/internal/ai/application/runtime/tool_catalog.go +++ b/internal/ai/application/runtime/tool_catalog.go @@ -72,10 +72,7 @@ func (c *toolCatalog) parseSkillAllowedToolCodes(skill *models.SkillDefinition) return toolx.NormalizeToolCodes(items) } -func (c *toolCatalog) parseAgentAllowedToolCodes(aiAgent *models.AIAgent) []string { - if aiAgent == nil { - return nil - } +func (c *toolCatalog) parseAgentAllowedToolCodes(aiAgent models.AIAgent) []string { ret := make([]string, 0) if raw := strings.TrimSpace(aiAgent.AllowedMCPTools); raw != "" { items, err := toolx.ParseAgentMCPToolsJSON(raw) @@ -94,6 +91,6 @@ func (c *toolCatalog) parseAgentAllowedToolCodes(aiAgent *models.AIAgent) []stri return toolx.NormalizeToolCodes(ret) } -func (c *toolCatalog) resolveAllowedToolCodes(aiAgent *models.AIAgent, skill *models.SkillDefinition) []string { +func (c *toolCatalog) resolveAllowedToolCodes(aiAgent models.AIAgent, skill *models.SkillDefinition) []string { return toolx.IntersectToolCodes(c.parseAgentAllowedToolCodes(aiAgent), c.parseSkillAllowedToolCodes(skill)) } diff --git a/internal/ai/application/runtime/tool_catalog_test.go b/internal/ai/application/runtime/tool_catalog_test.go index d1715b1..5797871 100644 --- a/internal/ai/application/runtime/tool_catalog_test.go +++ b/internal/ai/application/runtime/tool_catalog_test.go @@ -28,7 +28,7 @@ func TestNormalizeAllowedToolCodes(t *testing.T) { func TestToolCatalogResolveAllowedToolCodes(t *testing.T) { catalog := newToolCatalog() - agent := &models.AIAgent{ + agent := models.AIAgent{ AllowedMCPTools: `[{"toolCode":"graph/create_ticket_with_confirmation"},{"toolCode":"graph/handoff_to_human"}]`, } skill := &models.SkillDefinition{ @@ -45,7 +45,7 @@ func TestToolCatalogResolveAllowedToolCodes(t *testing.T) { func TestToolCatalogResolveAllowedToolCodesFallsBackWhenSkillEmpty(t *testing.T) { catalog := newToolCatalog() - agent := &models.AIAgent{ + agent := models.AIAgent{ AllowedMCPTools: `[{"toolCode":"graph/create_ticket_with_confirmation"},{"toolCode":"graph/handoff_to_human"}]`, } ret := catalog.resolveAllowedToolCodes(agent, nil) diff --git a/internal/ai/application/runtime/types.go b/internal/ai/application/runtime/types.go index 7373b59..893a0e6 100644 --- a/internal/ai/application/runtime/types.go +++ b/internal/ai/application/runtime/types.go @@ -8,8 +8,8 @@ import ( type Request struct { Conversation *models.Conversation UserMessage *models.Message - AIAgent *models.AIAgent - AIConfig *models.AIConfig + AIAgent models.AIAgent + AIConfig models.AIConfig ManualSkillCode string SelectedSkill *models.SkillDefinition SkillRouteReason string @@ -20,8 +20,8 @@ type Request struct { type ResumeRequest struct { Conversation *models.Conversation - AIAgent *models.AIAgent - AIConfig *models.AIConfig + AIAgent models.AIAgent + AIConfig models.AIConfig CheckPointID string ResumeData map[string]string ToolSet *registry.ToolSet diff --git a/internal/ai/embedding.go b/internal/ai/embedding.go index 0c1f914..9d31ea3 100644 --- a/internal/ai/embedding.go +++ b/internal/ai/embedding.go @@ -65,7 +65,7 @@ func (s *embedding) callEmbeddingAPI(ctx context.Context, text string) (*Embeddi if err != nil { return nil, err } - client := newOpenAIClient(config) + client := newOpenAIClient(*config) embeddingResp, err := client.Embeddings.New(ctx, openai.EmbeddingNewParams{ Input: openai.EmbeddingNewParamsInputUnion{ OfString: openai.String(text), diff --git a/internal/ai/llm.go b/internal/ai/llm.go index d07cc6b..551379a 100644 --- a/internal/ai/llm.go +++ b/internal/ai/llm.go @@ -30,14 +30,10 @@ func (s *llm) Chat(ctx context.Context, systemPrompt string, userPrompt string) if err != nil { return nil, err } - return s.ChatWithConfig(ctx, config, systemPrompt, userPrompt) + return s.ChatWithConfig(ctx, *config, systemPrompt, userPrompt) } -func (s *llm) ChatWithConfig(ctx context.Context, config *models.AIConfig, systemPrompt string, userPrompt string) (*ChatCompletionResult, error) { - if config == nil { - return nil, fmt.Errorf("ai config is nil") - } - +func (s *llm) ChatWithConfig(ctx context.Context, config models.AIConfig, systemPrompt string, userPrompt string) (*ChatCompletionResult, error) { messages := make([]openai.ChatCompletionMessageParamUnion, 0, 2) if strs.IsNotBlank(systemPrompt) { messages = append(messages, openai.ChatCompletionMessageParamUnion{ @@ -63,7 +59,7 @@ func (s *llm) ChatWithConfig(ctx context.Context, config *models.AIConfig, syste if config.MaxOutputTokens > 0 { params.MaxCompletionTokens = openai.Int(int64(config.MaxOutputTokens)) } - applyProviderSpecificChatParams(¶ms, config) + applyProviderSpecificChatParams(params, config) client := newOpenAIClient(config) chatResp, err := client.Chat.Completions.New(ctx, params) @@ -84,10 +80,7 @@ func (s *llm) ChatWithConfig(ctx context.Context, config *models.AIConfig, syste }, nil } -func applyProviderSpecificChatParams(params *openai.ChatCompletionNewParams, config *models.AIConfig) { - if params == nil || config == nil { - return - } +func applyProviderSpecificChatParams(params openai.ChatCompletionNewParams, config models.AIConfig) { if isDashScopeQwenThinkingModel(config) { params.SetExtraFields(map[string]any{ "enable_thinking": false, @@ -95,10 +88,7 @@ func applyProviderSpecificChatParams(params *openai.ChatCompletionNewParams, con } } -func isDashScopeQwenThinkingModel(config *models.AIConfig) bool { - if config == nil { - return false - } +func isDashScopeQwenThinkingModel(config models.AIConfig) bool { baseURL := strings.ToLower(strings.TrimSpace(config.BaseURL)) modelName := strings.ToLower(strings.TrimSpace(config.ModelName)) return strings.Contains(baseURL, "dashscope.aliyuncs.com") && strings.HasPrefix(modelName, "qwen3") diff --git a/internal/ai/openai_client.go b/internal/ai/openai_client.go index 02c27a7..2dfd7e6 100644 --- a/internal/ai/openai_client.go +++ b/internal/ai/openai_client.go @@ -13,7 +13,7 @@ import ( "cs-agent/internal/repositories" ) -func newOpenAIClient(config *models.AIConfig) openai.Client { +func newOpenAIClient(config models.AIConfig) openai.Client { opts := []option.RequestOption{ option.WithAPIKey(config.APIKey), option.WithBaseURL(config.BaseURL), diff --git a/internal/ai/runtime/debug_run.go b/internal/ai/runtime/debug_run.go index 5110691..d3f64f2 100644 --- a/internal/ai/runtime/debug_run.go +++ b/internal/ai/runtime/debug_run.go @@ -44,8 +44,8 @@ func DebugRunSkill(ctx context.Context, req request.SkillDebugRunRequest) (*resp summary, err := Service.Run(ctx, applicationruntime.Request{ Conversation: conversation, UserMessage: message, - AIAgent: aiAgent, - AIConfig: aiConfig, + AIAgent: *aiAgent, + AIConfig: *aiConfig, ManualSkillCode: strings.TrimSpace(req.SkillCode), }) if err != nil { @@ -91,8 +91,8 @@ func DebugResumeSkill(ctx context.Context, req request.SkillDebugResumeRequest) resumeText := strings.TrimSpace(req.UserMessage) summary, err := Service.Resume(ctx, applicationruntime.ResumeRequest{ Conversation: conversation, - AIAgent: aiAgent, - AIConfig: aiConfig, + AIAgent: *aiAgent, + AIConfig: *aiConfig, CheckPointID: strings.TrimSpace(req.CheckPointID), ResumeData: map[string]string{ strings.TrimSpace(pendingInterrupt.InterruptID): resumeText, diff --git a/internal/ai/runtime/executor/context_builders.go b/internal/ai/runtime/executor/context_builders.go index a194a65..a925584 100644 --- a/internal/ai/runtime/executor/context_builders.go +++ b/internal/ai/runtime/executor/context_builders.go @@ -22,9 +22,7 @@ func buildRunMessages(ctx context.Context, req RunInput, summary *RunResult, col } if collector != nil { collector.Data.Input.HistoryMessageCount = len(history.Messages) - if req.AIAgent != nil { - collector.Data.Input.KnowledgeBaseIDs = utils.SplitInt64s(req.AIAgent.KnowledgeIDs) - } + collector.Data.Input.KnowledgeBaseIDs = utils.SplitInt64s(req.AIAgent.KnowledgeIDs) collector.Data.Input.CurrentUserMessagePreview = preview(req.UserMessage.Content, 120) } messages := make([]*schema.Message, 0, len(history.Messages)+3) @@ -41,7 +39,7 @@ func buildRunMessages(ctx context.Context, req RunInput, summary *RunResult, col } func appendRetrievedContext(ctx context.Context, req RunInput, summary *RunResult, collector *callbacks.RuntimeTraceCollector, messages *[]*schema.Message) knowledgeGuardDecision { - if req.AIAgent == nil || req.UserMessage == nil || messages == nil { + if req.UserMessage == nil || messages == nil { return knowledgeGuardDecision{} } retriever := retrievers.NewKnowledgeRetriever(req.AIAgent) diff --git a/internal/ai/runtime/executor/knowledge_guard.go b/internal/ai/runtime/executor/knowledge_guard.go index 9ecbe74..e79b71c 100644 --- a/internal/ai/runtime/executor/knowledge_guard.go +++ b/internal/ai/runtime/executor/knowledge_guard.go @@ -15,8 +15,8 @@ type knowledgeGuardDecision struct { Instructions []*schema.Message } -func buildKnowledgeGuardDecision(aiAgent *models.AIAgent, retrieveResult *retrievers.KnowledgeRetrieveResult) knowledgeGuardDecision { - if aiAgent == nil || retrieveResult == nil || len(retrieveResult.KnowledgeBaseIDs) == 0 { +func buildKnowledgeGuardDecision(aiAgent models.AIAgent, retrieveResult *retrievers.KnowledgeRetrieveResult) knowledgeGuardDecision { + if retrieveResult == nil || len(retrieveResult.KnowledgeBaseIDs) == 0 { return knowledgeGuardDecision{} } fallbackReply := resolveKnowledgeFallbackReply(aiAgent, retrieveResult.FallbackMode) @@ -32,11 +32,9 @@ func buildKnowledgeGuardDecision(aiAgent *models.AIAgent, retrieveResult *retrie } } -func resolveKnowledgeFallbackReply(aiAgent *models.AIAgent, fallbackMode enums.KnowledgeFallbackMode) string { - if aiAgent != nil { - if reply := strings.TrimSpace(aiAgent.FallbackMessage); reply != "" { - return reply - } +func resolveKnowledgeFallbackReply(aiAgent models.AIAgent, fallbackMode enums.KnowledgeFallbackMode) string { + if reply := strings.TrimSpace(aiAgent.FallbackMessage); reply != "" { + return reply } switch fallbackMode { case enums.KnowledgeFallbackModeSuggestRetry: diff --git a/internal/ai/runtime/executor/knowledge_guard_test.go b/internal/ai/runtime/executor/knowledge_guard_test.go index ffc80ac..71ab01b 100644 --- a/internal/ai/runtime/executor/knowledge_guard_test.go +++ b/internal/ai/runtime/executor/knowledge_guard_test.go @@ -12,7 +12,7 @@ import ( func TestBuildKnowledgeGuardDecisionFallsBackWhenKnowledgeMisses(t *testing.T) { agent := newKnowledgeGuardAgentFixture() - decision := buildKnowledgeGuardDecision(&agent, &retrievers.KnowledgeRetrieveResult{ + decision := buildKnowledgeGuardDecision(agent, &retrievers.KnowledgeRetrieveResult{ KnowledgeBaseIDs: []int64{1}, FallbackMode: enums.KnowledgeFallbackModeSuggestRetry, }) @@ -28,7 +28,7 @@ func TestBuildKnowledgeGuardDecisionFallsBackWhenKnowledgeMisses(t *testing.T) { func TestBuildKnowledgeGuardDecisionUsesAgentFallbackMessage(t *testing.T) { agent := newKnowledgeGuardAgentFixture() agent.FallbackMessage = "请联系人工客服" - decision := buildKnowledgeGuardDecision(&agent, &retrievers.KnowledgeRetrieveResult{ + decision := buildKnowledgeGuardDecision(agent, &retrievers.KnowledgeRetrieveResult{ KnowledgeBaseIDs: []int64{1}, FallbackMode: enums.KnowledgeFallbackModeNoAnswer, }) @@ -40,7 +40,7 @@ func TestBuildKnowledgeGuardDecisionUsesAgentFallbackMessage(t *testing.T) { func TestBuildKnowledgeGuardDecisionInjectsStrictInstructionOnHit(t *testing.T) { agent := newKnowledgeGuardAgentFixture() - decision := buildKnowledgeGuardDecision(&agent, &retrievers.KnowledgeRetrieveResult{ + decision := buildKnowledgeGuardDecision(agent, &retrievers.KnowledgeRetrieveResult{ KnowledgeBaseIDs: []int64{1}, Hits: []rag.RetrieveResult{ {KnowledgeBaseID: 1, Score: 0.88}, diff --git a/internal/ai/runtime/executor/service.go b/internal/ai/runtime/executor/service.go index cf4eb5d..911d5ed 100644 --- a/internal/ai/runtime/executor/service.go +++ b/internal/ai/runtime/executor/service.go @@ -33,7 +33,7 @@ func (s *Service) ExecuteRun(ctx context.Context, req RunInput) (*RunResult, err } collector := callbacks.NewRuntimeTraceCollector() collector.Data.RunID = summary.RunID - if req.AIAgent == nil || req.Conversation == nil || req.UserMessage == nil { + if req.Conversation == nil || req.UserMessage == nil { summary.Status = "error" summary.ErrorMessage = "invalid runtime request" collector.Data.Status = summary.Status @@ -42,15 +42,6 @@ func (s *Service) ExecuteRun(ctx context.Context, req RunInput) (*RunResult, err summary.TraceData = collector.Marshal() return summary, fmt.Errorf("%s", summary.ErrorMessage) } - if req.AIConfig == nil { - summary.Status = "error" - summary.ErrorMessage = "ai config is nil" - collector.Data.Status = summary.Status - collector.Data.Error.Message = summary.ErrorMessage - collector.Data.Error.Stage = "prepare" - summary.TraceData = collector.Marshal() - return summary, fmt.Errorf("%s", summary.ErrorMessage) - } toolDefs, err := factory.NewToolFactory().BuildMCPTools(req.AIAgent) if err != nil { @@ -149,24 +140,6 @@ func (s *Service) ExecuteResume(ctx context.Context, req ResumeInput) (*RunResul collector := callbacks.NewRuntimeTraceCollector() collector.Data.RunID = summary.RunID collector.Data.Interrupt.CheckPointID = summary.CheckPointID - if req.AIAgent == nil { - summary.Status = "error" - summary.ErrorMessage = "ai agent is nil" - collector.Data.Status = summary.Status - collector.Data.Error.Message = summary.ErrorMessage - collector.Data.Error.Stage = "resume_prepare" - summary.TraceData = collector.Marshal() - return summary, fmt.Errorf("%s", summary.ErrorMessage) - } - if req.AIConfig == nil { - summary.Status = "error" - summary.ErrorMessage = "ai config is nil" - collector.Data.Status = summary.Status - collector.Data.Error.Message = summary.ErrorMessage - collector.Data.Error.Stage = "resume_prepare" - summary.TraceData = collector.Marshal() - return summary, fmt.Errorf("%s", summary.ErrorMessage) - } if summary.CheckPointID == "" { summary.Status = "error" summary.ErrorMessage = "checkpoint id is required" diff --git a/internal/ai/runtime/executor/types.go b/internal/ai/runtime/executor/types.go index 572fb9f..f7c79c9 100644 --- a/internal/ai/runtime/executor/types.go +++ b/internal/ai/runtime/executor/types.go @@ -8,8 +8,8 @@ import ( type RunInput struct { Conversation *models.Conversation UserMessage *models.Message - AIAgent *models.AIAgent - AIConfig *models.AIConfig + AIAgent models.AIAgent + AIConfig models.AIConfig SelectedSkill *models.SkillDefinition SkillRouteReason string SkillRouteTrace string @@ -19,8 +19,8 @@ type RunInput struct { type ResumeInput struct { Conversation *models.Conversation - AIAgent *models.AIAgent - AIConfig *models.AIConfig + AIAgent models.AIAgent + AIConfig models.AIConfig CheckPointID string ResumeData map[string]string ToolSet *registry.ToolSet diff --git a/internal/ai/runtime/graphs/create_ticket_graph.go b/internal/ai/runtime/graphs/create_ticket_graph.go index fc902a2..f0d2778 100644 --- a/internal/ai/runtime/graphs/create_ticket_graph.go +++ b/internal/ai/runtime/graphs/create_ticket_graph.go @@ -38,10 +38,10 @@ func init() { type CreateTicketGraph struct { conversation *models.Conversation - aiAgent *models.AIAgent + aiAgent models.AIAgent } -func NewCreateTicketGraph(conversation *models.Conversation, aiAgent *models.AIAgent) *CreateTicketGraph { +func NewCreateTicketGraph(conversation *models.Conversation, aiAgent models.AIAgent) *CreateTicketGraph { return &CreateTicketGraph{ conversation: conversation, aiAgent: aiAgent, @@ -49,7 +49,7 @@ func NewCreateTicketGraph(conversation *models.Conversation, aiAgent *models.AIA } func (g *CreateTicketGraph) Run(ctx context.Context, argumentsInJSON string) (string, error) { - if g == nil || g.conversation == nil || g.aiAgent == nil { + if g == nil || g.conversation == nil { return "", fmt.Errorf("create ticket graph not initialized") } wasInterrupted, hasState, state := componenttool.GetInterruptState[CreateTicketGraphState](ctx) diff --git a/internal/ai/runtime/graphs/create_ticket_graph_test.go b/internal/ai/runtime/graphs/create_ticket_graph_test.go deleted file mode 100644 index ccf47c8..0000000 --- a/internal/ai/runtime/graphs/create_ticket_graph_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package graphs - -import ( - "testing" - - "cs-agent/internal/models" -) - -func TestCreateTicketGraphBuildCreateRequest(t *testing.T) { - graph := NewCreateTicketGraph(&models.Conversation{ - ID: 12, - Subject: "fallback-title", - LastMessageSummary: "fallback-description", - }, &models.AIAgent{Name: "AI"}) - - req, err := graph.buildCreateRequest(`{"title":" test title ","description":" desc ","priority":2,"severity":3}`) - if err != nil { - t.Fatalf("buildCreateRequest returned error: %v", err) - } - if req.Title != "test title" || req.Description != "desc" { - t.Fatalf("unexpected request text fields: %#v", req) - } - if req.Priority != 2 || req.Severity != 3 { - t.Fatalf("unexpected request numeric fields: %#v", req) - } -} - -func TestCreateTicketGraphBuildCreateRequestFallbacks(t *testing.T) { - graph := NewCreateTicketGraph(&models.Conversation{ - ID: 12, - Subject: "fallback-title", - LastMessageSummary: "fallback-description", - }, &models.AIAgent{Name: "AI"}) - - req, err := graph.buildCreateRequest(`{}`) - if err != nil { - t.Fatalf("buildCreateRequest returned error: %v", err) - } - if req.Title != "fallback-title" { - t.Fatalf("unexpected fallback title: %#v", req) - } - if req.Description != "fallback-description" { - t.Fatalf("unexpected fallback description: %#v", req) - } -} diff --git a/internal/ai/runtime/graphs/handoff_graph.go b/internal/ai/runtime/graphs/handoff_graph.go index 8f11acd..62ab5f3 100644 --- a/internal/ai/runtime/graphs/handoff_graph.go +++ b/internal/ai/runtime/graphs/handoff_graph.go @@ -33,10 +33,10 @@ func init() { type HandoffGraph struct { conversation *models.Conversation - aiAgent *models.AIAgent + aiAgent models.AIAgent } -func NewHandoffGraph(conversation *models.Conversation, aiAgent *models.AIAgent) *HandoffGraph { +func NewHandoffGraph(conversation *models.Conversation, aiAgent models.AIAgent) *HandoffGraph { return &HandoffGraph{ conversation: conversation, aiAgent: aiAgent, @@ -44,7 +44,7 @@ func NewHandoffGraph(conversation *models.Conversation, aiAgent *models.AIAgent) } func (g *HandoffGraph) Run(ctx context.Context, argumentsInJSON string) (string, error) { - if g == nil || g.conversation == nil || g.aiAgent == nil { + if g == nil || g.conversation == nil { return "", fmt.Errorf("handoff graph not initialized") } wasInterrupted, hasState, state := componenttool.GetInterruptState[HandoffGraphState](ctx) diff --git a/internal/ai/runtime/graphs/handoff_graph_test.go b/internal/ai/runtime/graphs/handoff_graph_test.go deleted file mode 100644 index 25bf8ee..0000000 --- a/internal/ai/runtime/graphs/handoff_graph_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package graphs - -import ( - "testing" - - "cs-agent/internal/models" -) - -func TestHandoffGraphBuildReason(t *testing.T) { - graph := NewHandoffGraph(&models.Conversation{ID: 1}, &models.AIAgent{Name: "AI"}) - - reason, err := graph.buildReason(`{"reason":" 用户需要人工确认 "}`) - if err != nil { - t.Fatalf("buildReason returned error: %v", err) - } - if reason != "用户需要人工确认" { - t.Fatalf("unexpected reason: %q", reason) - } -} - -func TestHandoffGraphBuildReasonFallback(t *testing.T) { - graph := NewHandoffGraph(&models.Conversation{ID: 1}, &models.AIAgent{Name: "AI"}) - - reason, err := graph.buildReason(`{}`) - if err != nil { - t.Fatalf("buildReason returned error: %v", err) - } - if reason != "用户需要转人工支持" { - t.Fatalf("unexpected fallback reason: %q", reason) - } -} - -func TestHandoffGraphBuildSuccessReply(t *testing.T) { - graph := NewHandoffGraph(&models.Conversation{ID: 1}, &models.AIAgent{Name: "AI"}) - - got := graph.buildSuccessReply() - want := "已为你转接人工客服,请稍候。,请稍候。" - if got != want { - t.Fatalf("unexpected success reply: %q", got) - } -} diff --git a/internal/ai/runtime/instruction/service.go b/internal/ai/runtime/instruction/service.go index 2a285c2..bb03aef 100644 --- a/internal/ai/runtime/instruction/service.go +++ b/internal/ai/runtime/instruction/service.go @@ -47,15 +47,11 @@ func NewService( } func (s *Service) Build( - aiAgent *models.AIAgent, + aiAgent models.AIAgent, selectedSkill *models.SkillDefinition, toolDefinitions []runtimetooling.MCPToolDefinition, extraToolCodes map[string]string, ) AssemblyResult { - baseInstruction := "" - if aiAgent != nil { - baseInstruction = strings.TrimSpace(aiAgent.SystemPrompt) - } projectInstruction := "" governanceInstruction := "" skillInstruction := "" @@ -77,7 +73,7 @@ func (s *Service) Build( assembler = s.assembler } return assembler.Assemble(AssemblerInput{ - AgentInstruction: baseInstruction, + AgentInstruction: strings.TrimSpace(aiAgent.SystemPrompt), GovernanceInstruction: governanceInstruction, SkillInstruction: skillInstruction, ToolAppendices: toolAppendices, diff --git a/internal/ai/runtime/internal/impl/factory/agent_factory.go b/internal/ai/runtime/internal/impl/factory/agent_factory.go index 27c21d3..631d551 100644 --- a/internal/ai/runtime/internal/impl/factory/agent_factory.go +++ b/internal/ai/runtime/internal/impl/factory/agent_factory.go @@ -31,9 +31,9 @@ type AgentFactory struct { // 3. 后续扩展装配项时继续拉长函数签名。 type BuildCustomerServiceAgentInput struct { // AIAgent 为当前运行的业务 Agent 配置,提供名称、描述、系统提示词等基础信息。 - AIAgent *models.AIAgent + AIAgent models.AIAgent // AIConfig 为模型配置,决定底层使用哪个 ChatModel。 - AIConfig *models.AIConfig + AIConfig models.AIConfig // SelectedSkill 为当前命中的技能;为空表示本次运行未命中专项技能。 SelectedSkill *models.SkillDefinition // InstructionToolDefinitions 用于生成 instruction 中的工具说明。 @@ -63,9 +63,6 @@ func NewAgentFactory() *AgentFactory { // BuildCustomerServiceAgent 根据装配输入构建客服 ChatModelAgent。 func (f *AgentFactory) BuildCustomerServiceAgent(ctx context.Context, input BuildCustomerServiceAgentInput) (*einoagents.CustomerServiceAgent, error) { - if input.AIAgent == nil || input.AIConfig == nil { - return nil, nil - } chatModel, err := f.chatModelFactory.Build(ctx, input.AIConfig) if err != nil { return nil, err diff --git a/internal/ai/runtime/internal/impl/factory/chat_model_factory.go b/internal/ai/runtime/internal/impl/factory/chat_model_factory.go index e88b81e..bb260aa 100644 --- a/internal/ai/runtime/internal/impl/factory/chat_model_factory.go +++ b/internal/ai/runtime/internal/impl/factory/chat_model_factory.go @@ -18,27 +18,24 @@ func NewChatModelFactory() *ChatModelFactory { return &ChatModelFactory{} } -func (f *ChatModelFactory) Build(ctx context.Context, item *models.AIConfig) (model.ToolCallingChatModel, error) { - if item == nil { - return nil, nil - } +func (f *ChatModelFactory) Build(ctx context.Context, aiConfig models.AIConfig) (model.ToolCallingChatModel, error) { conf := &openai.ChatModelConfig{ - APIKey: strings.TrimSpace(item.APIKey), - BaseURL: strings.TrimSpace(item.BaseURL), - Model: strings.TrimSpace(item.ModelName), + APIKey: strings.TrimSpace(aiConfig.APIKey), + BaseURL: strings.TrimSpace(aiConfig.BaseURL), + Model: strings.TrimSpace(aiConfig.ModelName), } - if item.TimeoutMS > 0 { - conf.Timeout = time.Duration(item.TimeoutMS) * time.Millisecond + if aiConfig.TimeoutMS > 0 { + conf.Timeout = time.Duration(aiConfig.TimeoutMS) * time.Millisecond } - if item.MaxOutputTokens > 0 { - maxCompletionTokens := item.MaxOutputTokens + if aiConfig.MaxOutputTokens > 0 { + maxCompletionTokens := aiConfig.MaxOutputTokens conf.MaxCompletionTokens = &maxCompletionTokens } - if item.Provider == enums.AIProviderOpenAI && isAzureOpenAIBaseURL(item.BaseURL) { + if aiConfig.Provider == enums.AIProviderOpenAI && isAzureOpenAIBaseURL(aiConfig.BaseURL) { conf.ByAzure = true conf.APIVersion = "2024-06-01" } - if extraFields := providerExtraFields(item); len(extraFields) > 0 { + if extraFields := providerExtraFields(aiConfig); len(extraFields) > 0 { conf.ExtraFields = extraFields } return openai.NewChatModel(ctx, conf) @@ -49,12 +46,9 @@ func isAzureOpenAIBaseURL(baseURL string) bool { return strings.Contains(baseURL, ".openai.azure.com") } -func providerExtraFields(item *models.AIConfig) map[string]any { - if item == nil { - return nil - } - baseURL := strings.ToLower(strings.TrimSpace(item.BaseURL)) - modelName := strings.ToLower(strings.TrimSpace(item.ModelName)) +func providerExtraFields(aiConfig models.AIConfig) map[string]any { + baseURL := strings.ToLower(strings.TrimSpace(aiConfig.BaseURL)) + modelName := strings.ToLower(strings.TrimSpace(aiConfig.ModelName)) if strings.Contains(baseURL, "dashscope.aliyuncs.com") && strings.HasPrefix(modelName, "qwen3") { return map[string]any{ "enable_thinking": false, diff --git a/internal/ai/runtime/internal/impl/factory/tool_factory.go b/internal/ai/runtime/internal/impl/factory/tool_factory.go index 79005ee..31139ea 100644 --- a/internal/ai/runtime/internal/impl/factory/tool_factory.go +++ b/internal/ai/runtime/internal/impl/factory/tool_factory.go @@ -20,10 +20,7 @@ func NewToolFactory() *ToolFactory { return &ToolFactory{} } -func (f *ToolFactory) BuildMCPTools(aiAgent *models.AIAgent) ([]runtimetooling.MCPToolDefinition, error) { - if aiAgent == nil || strings.TrimSpace(aiAgent.AllowedMCPTools) == "" { - return nil, nil - } +func (f *ToolFactory) BuildMCPTools(aiAgent models.AIAgent) ([]runtimetooling.MCPToolDefinition, error) { raw, err := toolx.ParseAgentMCPToolsJSON(aiAgent.AllowedMCPTools) if err != nil { return nil, err @@ -56,7 +53,7 @@ func (f *ToolFactory) BuildMCPTools(aiAgent *models.AIAgent) ([]runtimetooling.M return ret, nil } -func (f *ToolFactory) BuildBaseTools(ctx context.Context, aiAgent *models.AIAgent) ([]einotool.BaseTool, error) { +func (f *ToolFactory) BuildBaseTools(ctx context.Context, aiAgent models.AIAgent) ([]einotool.BaseTool, error) { definitions, err := f.BuildMCPTools(aiAgent) if err != nil { return nil, err diff --git a/internal/ai/runtime/internal/impl/factory/tool_factory_test.go b/internal/ai/runtime/internal/impl/factory/tool_factory_test.go index 2e9417f..2be96dd 100644 --- a/internal/ai/runtime/internal/impl/factory/tool_factory_test.go +++ b/internal/ai/runtime/internal/impl/factory/tool_factory_test.go @@ -7,7 +7,7 @@ import ( ) func TestBuildMCPToolsSkipsGraphAndBuiltinTools(t *testing.T) { - aiAgent := &models.AIAgent{ + aiAgent := models.AIAgent{ AllowedMCPTools: `[ {"toolCode":"graph/create_ticket_with_confirmation","serverCode":"graph","toolName":"create_ticket_with_confirmation"}, {"toolCode":"builtin/tool_search","serverCode":"builtin","toolName":"tool_search"}, diff --git a/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go b/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go index 77eba1d..42995c1 100644 --- a/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go +++ b/internal/ai/runtime/internal/impl/retrievers/knowledge_retriever.go @@ -20,7 +20,7 @@ const defaultRuntimeKnowledgeScoreThreshold = 0.3 const defaultRuntimeKnowledgeMaxContextItems = 5 type KnowledgeRetriever struct { - AIAgent *models.AIAgent + AIAgent models.AIAgent } type KnowledgeRetrieveOptions struct { @@ -53,7 +53,7 @@ type KnowledgeRetrieveResult struct { Policies []KnowledgeBaseRetrievePolicy } -func NewKnowledgeRetriever(aiAgent *models.AIAgent) *KnowledgeRetriever { +func NewKnowledgeRetriever(aiAgent models.AIAgent) *KnowledgeRetriever { return &KnowledgeRetriever{AIAgent: aiAgent} } @@ -65,9 +65,6 @@ func DefaultKnowledgeRetrieveOptions() KnowledgeRetrieveOptions { } func (r *KnowledgeRetriever) KnowledgeBaseIDs() []int64 { - if r == nil || r.AIAgent == nil { - return nil - } return utils.SplitInt64s(r.AIAgent.KnowledgeIDs) } diff --git a/internal/ai/runtime/registry/registry_test.go b/internal/ai/runtime/registry/registry_test.go index e47d6bf..5b26e7e 100644 --- a/internal/ai/runtime/registry/registry_test.go +++ b/internal/ai/runtime/registry/registry_test.go @@ -50,7 +50,7 @@ func TestResolveBuildsStaticToolMetadata(t *testing.T) { }) toolSet, err := r.Resolve(registry.Context{ Conversation: &models.Conversation{ID: 1}, - AIAgent: &models.AIAgent{ID: 1}, + AIAgent: models.AIAgent{ID: 1}, }) if err != nil { t.Fatalf("resolve returned error: %v", err) diff --git a/internal/ai/runtime/registry/types.go b/internal/ai/runtime/registry/types.go index 248f128..c2da414 100644 --- a/internal/ai/runtime/registry/types.go +++ b/internal/ai/runtime/registry/types.go @@ -10,8 +10,8 @@ import ( type Context struct { Conversation *models.Conversation - AIAgent *models.AIAgent - AIConfig *models.AIConfig + AIAgent models.AIAgent + AIConfig models.AIConfig UserMessage *models.Message AllowedToolCodes []string } diff --git a/internal/ai/runtime/runtime_reply_executor.go b/internal/ai/runtime/runtime_reply_executor.go index ba9a5df..d1be8df 100644 --- a/internal/ai/runtime/runtime_reply_executor.go +++ b/internal/ai/runtime/runtime_reply_executor.go @@ -28,8 +28,8 @@ func (e *runtimeReplyExecutor) Run(ctx context.Context, conversation models.Conv summary, err := Service.Run(ctx, applicationruntime.Request{ Conversation: &conversation, UserMessage: &message, - AIAgent: &aiAgent, - AIConfig: aiConfig, + AIAgent: aiAgent, + AIConfig: *aiConfig, }) if trace != nil { trace.RuntimeLatencyMs = time.Since(runtimeStartedAt).Milliseconds() @@ -52,8 +52,8 @@ func (e *runtimeReplyExecutor) ResumePendingInterrupt(ctx context.Context, conve } summary, err := Service.Resume(ctx, applicationruntime.ResumeRequest{ Conversation: &conversation, - AIAgent: &aiAgent, - AIConfig: aiConfig, + AIAgent: aiAgent, + AIConfig: *aiConfig, CheckPointID: strings.TrimSpace(pendingInterrupt.CheckPointID), ResumeData: map[string]string{ strings.TrimSpace(pendingInterrupt.InterruptID): strings.TrimSpace(message.Content), diff --git a/internal/ai/runtime/tools/create_ticket_confirm_tool.go b/internal/ai/runtime/tools/create_ticket_confirm_tool.go index 9d6fce1..c273e67 100644 --- a/internal/ai/runtime/tools/create_ticket_confirm_tool.go +++ b/internal/ai/runtime/tools/create_ticket_confirm_tool.go @@ -17,7 +17,7 @@ import ( type CreateTicketGraphTool struct { conversation *models.Conversation - aiAgent *models.AIAgent + aiAgent models.AIAgent } func NewCreateTicketGraphTool() *CreateTicketGraphTool { @@ -37,7 +37,7 @@ func (t *CreateTicketGraphTool) Code() string { } func (t *CreateTicketGraphTool) Enabled(ctx registry.Context) bool { - return ctx.Conversation != nil && ctx.AIAgent != nil + return true } func (t *CreateTicketGraphTool) Build(ctx registry.Context) (einotool.BaseTool, error) { @@ -100,7 +100,7 @@ func (t *CreateTicketGraphTool) Info(ctx context.Context) (*schema.ToolInfo, err } func (t *CreateTicketGraphTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) { - if t == nil || t.conversation == nil || t.aiAgent == nil { + if t == nil || t.conversation == nil { return "", fmt.Errorf("create ticket graph tool not initialized") } return graphs.NewCreateTicketGraph(t.conversation, t.aiAgent).Run(ctx, argumentsInJSON) diff --git a/internal/ai/runtime/tools/handoff_graph_tool.go b/internal/ai/runtime/tools/handoff_graph_tool.go index 105f8a9..e2df477 100644 --- a/internal/ai/runtime/tools/handoff_graph_tool.go +++ b/internal/ai/runtime/tools/handoff_graph_tool.go @@ -17,7 +17,7 @@ import ( type HandoffGraphTool struct { conversation *models.Conversation - aiAgent *models.AIAgent + aiAgent models.AIAgent } func NewHandoffGraphTool() *HandoffGraphTool { @@ -37,7 +37,7 @@ func (t *HandoffGraphTool) Code() string { } func (t *HandoffGraphTool) Enabled(ctx registry.Context) bool { - return ctx.Conversation != nil && ctx.AIAgent != nil + return true } func (t *HandoffGraphTool) Build(ctx registry.Context) (einotool.BaseTool, error) { @@ -75,7 +75,7 @@ func (t *HandoffGraphTool) Info(ctx context.Context) (*schema.ToolInfo, error) { } func (t *HandoffGraphTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...einotool.Option) (string, error) { - if t == nil || t.conversation == nil || t.aiAgent == nil { + if t == nil || t.conversation == nil { return "", fmt.Errorf("handoff graph tool not initialized") } return graphs.NewHandoffGraph(t.conversation, t.aiAgent).Run(ctx, argumentsInJSON) diff --git a/internal/ai/skills/candidate_loader.go b/internal/ai/skills/candidate_loader.go index a4ad80a..7b4a4f9 100644 --- a/internal/ai/skills/candidate_loader.go +++ b/internal/ai/skills/candidate_loader.go @@ -20,10 +20,7 @@ func (l *candidateLoader) findManualSkillDefinition(skillCode string) *models.Sk return repositories.SkillDefinitionRepository.GetByCode(sqls.DB(), skillCode) } -func (l *candidateLoader) loadCandidateSkills(aiAgent *models.AIAgent) []models.SkillDefinition { - if aiAgent == nil { - return nil - } +func (l *candidateLoader) loadCandidateSkills(aiAgent models.AIAgent) []models.SkillDefinition { skillIDs := utils.SplitInt64s(aiAgent.SkillIDs) skills := repositories.SkillDefinitionRepository.GetByIDs(sqls.DB(), skillIDs) ret := make([]models.SkillDefinition, 0, len(skillIDs)) diff --git a/internal/ai/skills/log_test.go b/internal/ai/skills/log_test.go index 4db9261..7141582 100644 --- a/internal/ai/skills/log_test.go +++ b/internal/ai/skills/log_test.go @@ -11,16 +11,16 @@ import ( func TestBuildRunLogMatchedPlan(t *testing.T) { log := BuildRunLog( RuntimeContext{ - AIAgent: &models.AIAgent{ID: 22}, - AIConfig: &models.AIConfig{ID: 33}, + AIAgent: models.AIAgent{ID: 22}, + AIConfig: models.AIConfig{ID: 33}, ConversationID: 11, ManualSkillCode: "manual_refund", IntentCode: "refund", UserMessage: "我要退款", }, &ExecutionPlan{ - AIAgent: &models.AIAgent{ID: 22}, - AIConfig: &models.AIConfig{ + AIAgent: models.AIAgent{ID: 22}, + AIConfig: models.AIConfig{ ID: 33, ModelName: "gpt-test", Provider: enums.AIProviderOpenAI, @@ -55,7 +55,7 @@ func TestBuildRunLogMatchedPlan(t *testing.T) { func TestBuildRunLogNotMatchedAndError(t *testing.T) { log := BuildRunLog( RuntimeContext{ - AIAgent: &models.AIAgent{ID: 22}, + AIAgent: models.AIAgent{ID: 22}, UserMessage: "随便问问", }, nil, @@ -74,7 +74,7 @@ func TestBuildRunLogNotMatchedAndError(t *testing.T) { } noMatchLog := BuildRunLog( - RuntimeContext{AIAgent: &models.AIAgent{ID: 22}, UserMessage: "随便问问"}, + RuntimeContext{AIAgent: models.AIAgent{ID: 22}, UserMessage: "随便问问"}, &ExecutionPlan{MatchReason: ""}, &ExecutionTrace{Status: "not_matched"}, nil, diff --git a/internal/ai/skills/matcher.go b/internal/ai/skills/matcher.go index 8c1ae58..5bee18e 100644 --- a/internal/ai/skills/matcher.go +++ b/internal/ai/skills/matcher.go @@ -16,7 +16,7 @@ type intentTriggerConfig struct { } // MatchSkill 对单个 SkillDefinition 执行命中判断。 -func MatchSkill(execCtx context.Context, ctx RuntimeContext, aiAgent *models.AIAgent, aiConfig *models.AIConfig) (*models.SkillDefinition, string, *RouteTrace, error) { +func MatchSkill(execCtx context.Context, ctx RuntimeContext) (*models.SkillDefinition, string, *RouteTrace, error) { loader := newCandidateLoader() if strs.IsNotBlank(ctx.ManualSkillCode) { skill := loader.findManualSkillDefinition(ctx.ManualSkillCode) @@ -29,7 +29,7 @@ func MatchSkill(execCtx context.Context, ctx RuntimeContext, aiAgent *models.AIA }, nil } - candidates := loader.loadCandidateSkills(aiAgent) + candidates := loader.loadCandidateSkills(ctx.AIAgent) trace := &RouteTrace{ Status: "started", CandidateSkillCodes: make([]string, 0, len(candidates)), @@ -53,7 +53,7 @@ func MatchSkill(execCtx context.Context, ctx RuntimeContext, aiAgent *models.AIA } } - selected, routeTrace, err := routeSkillWithLLM(execCtx, aiConfig, ctx.UserMessage, candidates) + selected, routeTrace, err := routeSkillWithLLM(execCtx, ctx.AIConfig, ctx.UserMessage, candidates) if routeTrace != nil { trace.Status = routeTrace.Status trace.SelectedSkillCode = routeTrace.SelectedSkillCode diff --git a/internal/ai/skills/plan_service.go b/internal/ai/skills/plan_service.go index 566df21..1cd6c52 100644 --- a/internal/ai/skills/plan_service.go +++ b/internal/ai/skills/plan_service.go @@ -3,8 +3,6 @@ package skills import ( "context" "strings" - - "cs-agent/internal/pkg/errorsx" ) func newPlanService() *planService { @@ -15,13 +13,7 @@ type planService struct{} // BuildExecutionPlan 构建当前请求的 Skill 执行计划。 func (s *planService) BuildExecutionPlan(execCtx context.Context, ctx RuntimeContext) (*ExecutionPlan, error) { - if ctx.AIAgent == nil { - return nil, errorsx.InvalidParam("AIAgent不能为空") - } - if ctx.AIConfig == nil { - return nil, errorsx.InvalidParam("AIConfig不能为空") - } - skill, matchReason, routeTrace, err := MatchSkill(execCtx, ctx, ctx.AIAgent, ctx.AIConfig) + skill, matchReason, routeTrace, err := MatchSkill(execCtx, ctx) if err != nil { return nil, err } diff --git a/internal/ai/skills/router.go b/internal/ai/skills/router.go index a82c9f7..100c239 100644 --- a/internal/ai/skills/router.go +++ b/internal/ai/skills/router.go @@ -9,16 +9,10 @@ import ( "cs-agent/internal/ai" "cs-agent/internal/models" - "cs-agent/internal/pkg/errorsx" ) -func routeSkillWithLLM(ctx context.Context, aiConfig *models.AIConfig, userMessage string, candidates []models.SkillDefinition) (*models.SkillDefinition, *RouteTrace, error) { +func routeSkillWithLLM(ctx context.Context, aiConfig models.AIConfig, userMessage string, candidates []models.SkillDefinition) (*models.SkillDefinition, *RouteTrace, error) { trace := &RouteTrace{Status: "started"} - if aiConfig == nil { - trace.Status = "config_error" - trace.Error = "ai config is nil" - return nil, trace, errorsx.InvalidParam("Skill 路由依赖的 AI 配置不可用") - } if len(candidates) == 0 { trace.Status = "no_candidate" return nil, trace, nil diff --git a/internal/ai/skills/runlog_service.go b/internal/ai/skills/runlog_service.go index ca4077a..a6b58bf 100644 --- a/internal/ai/skills/runlog_service.go +++ b/internal/ai/skills/runlog_service.go @@ -20,7 +20,7 @@ type RunLogService struct{} func (s *RunLogService) Build(ctx RuntimeContext, plan *ExecutionPlan, trace *ExecutionTrace, err error) *models.SkillRunLog { log := &models.SkillRunLog{ ConversationID: ctx.ConversationID, - AIAgentID: resolveRuntimeAIAgentID(ctx, plan), + AIAgentID: ctx.AIAgent.ID, ManualSkillCode: ctx.ManualSkillCode, IntentCode: ctx.IntentCode, UserMessage: ctx.UserMessage, @@ -28,11 +28,10 @@ func (s *RunLogService) Build(ctx RuntimeContext, plan *ExecutionPlan, trace *Ex CreatedAt: time.Now(), } if plan != nil { - if plan.AIConfig != nil { - log.AIConfigID = plan.AIConfig.ID - log.UsedModel = plan.AIConfig.ModelName - log.UsedProvider = plan.AIConfig.Provider - } + log.AIConfigID = plan.AIConfig.ID + log.UsedModel = plan.AIConfig.ModelName + log.UsedProvider = plan.AIConfig.Provider + if plan.Skill != nil { log.SkillDefinitionID = plan.Skill.ID log.SkillCode = plan.Skill.Code @@ -53,16 +52,6 @@ func (s *RunLogService) Build(ctx RuntimeContext, plan *ExecutionPlan, trace *Ex return log } -func resolveRuntimeAIAgentID(ctx RuntimeContext, plan *ExecutionPlan) int64 { - if ctx.AIAgent != nil { - return ctx.AIAgent.ID - } - if plan != nil && plan.AIAgent != nil { - return plan.AIAgent.ID - } - return 0 -} - // Write 写入 Skill 路由日志。 func (s *RunLogService) Write(log *models.SkillRunLog) error { if log == nil { diff --git a/internal/ai/skills/types.go b/internal/ai/skills/types.go index 3b71a9e..b9b7c1f 100644 --- a/internal/ai/skills/types.go +++ b/internal/ai/skills/types.go @@ -4,18 +4,18 @@ import "cs-agent/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。 + ManualSkillCode string // ManualSkillCode 为显式指定的 Skill 编码。 + IntentCode string // IntentCode 为上游识别出的意图编码。 } // ExecutionPlan 表示 Skill Runtime 计算出的最终路由结果。 type ExecutionPlan struct { - AIAgent *models.AIAgent // AIAgent 为本次请求所属的 AI Agent。 - AIConfig *models.AIConfig // AIConfig 为本次请求实际使用的模型配置。 + AIAgent models.AIAgent // AIAgent 为本次请求所属的 AI Agent。 + AIConfig models.AIConfig // AIConfig 为本次请求实际使用的模型配置。 Skill *models.SkillDefinition // Skill 为最终命中的 Skill,未命中时为空。 MatchReason string // MatchReason 为命中原因。 RouteTrace *RouteTrace // RouteTrace 为匹配阶段的路由追踪。 diff --git a/internal/services/conversation_service.go b/internal/services/conversation_service.go index 40d251f..d6eb84f 100644 --- a/internal/services/conversation_service.go +++ b/internal/services/conversation_service.go @@ -288,13 +288,10 @@ func (s *conversationService) TransferConversation(conversationID, toUserID int6 return nil } -func (s *conversationService) HandoffByAI(conversationID int64, aiAgent *models.AIAgent, reason string) error { +func (s *conversationService) HandoffByAI(conversationID int64, aiAgent models.AIAgent, reason string) error { if conversationID <= 0 { return errorsx.InvalidParam("会话不存在") } - if aiAgent == nil { - return errorsx.InvalidParam("AI Agent 不存在") - } now := time.Now() if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error { conversation := repositories.ConversationRepository.Get(ctx.Tx, conversationID)