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.
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
+5
-15
@@ -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")
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 为匹配阶段的路由追踪。
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user