Files
agent-desk-ng/internal/ai/application/runtime/agent_turn.go

103 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package runtime
import (
"context"
"fmt"
"strings"
"agent-desk/internal/models"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/pkg/toolx"
"agent-desk/internal/pkg/utils"
svc "agent-desk/internal/services"
)
type agentLoopTurn struct {
RetrieverCount int
RetrieveErr error
ResponsePolicy agentLoopResponsePolicy
SystemPrompt string
UserPrompt string
HistoryCount int
AllowedTools []string
ToolPolicy agentLoopToolPolicy
Skills map[int64]models.SkillDefinition
Workflows map[int64]svc.AgentRevisionWorkflowBinding
MaxSteps int
}
func (e *AgentLoopEngine) prepareTurn(ctx context.Context, req RunInput, snapshot *svc.AgentRevisionSnapshot) agentLoopTurn {
maxSteps := req.AIAgent.MaxSteps
if maxSteps <= 0 {
maxSteps = 4
}
if maxSteps > 8 {
maxSteps = 8
}
knowledgeContext, retrieverCount, retrieveErr := e.retrieveKnowledge(ctx, req.AIAgent, req.UserMessage.Content)
responsePolicy := evaluateAgentLoopResponsePolicy(req.AIAgent, knowledgeContext, retrieveErr)
systemPrompt := buildAgentLoopSystemPrompt(req.AIAgent, len(utils.SplitInt64s(req.AIAgent.KnowledgeIDs)) > 0, knowledgeContext, retrieveErr)
userPrompt, historyCount := e.buildUserPrompt(req)
if knowledgeContext != "" {
userPrompt += "\n\nKnowledge evidence:\n" + knowledgeContext
}
skills := svc.SkillDefinitionService.GetByIDs(utils.SplitInt64s(req.AIAgent.SkillIDs))
workflows := make(map[int64]svc.AgentRevisionWorkflowBinding, len(snapshot.WorkflowBindings))
allowedTools := agentLoopSafeBuiltinCodes()
// TODO 这么实现我觉得不太好,最好是能够有个统一的能力目录
catalog := []string{
"- " + toolx.BuiltinConversationContext.Code + " | Builtin | Read current conversation and customer context | direct function: " + toolx.SanitizeToolFunctionName(toolx.BuiltinConversationContext.Code),
"- " + toolx.BuiltinKnowledgeRetrieve.Code + " | Builtin | Retrieve linked knowledge base on demand | direct function: " + toolx.SanitizeToolFunctionName(toolx.BuiltinKnowledgeRetrieve.Code),
"- " + toolx.GraphTriageServiceRequest.Code + " | Builtin | Analyze service request and generate handling advice | direct function: " + toolx.SanitizeToolFunctionName(toolx.GraphTriageServiceRequest.Code),
"- " + toolx.GraphAnalyzeConversation.Code + " | Builtin | Analyze conversation intent and risk signals | direct function: " + toolx.SanitizeToolFunctionName(toolx.GraphAnalyzeConversation.Code),
"- " + toolx.GraphPrepareTicketDraft.Code + " | Builtin | Generate ticket draft without writing | direct function: " + toolx.SanitizeToolFunctionName(toolx.GraphPrepareTicketDraft.Code),
}
for id, skill := range skills {
if skill.Status != enums.StatusOk {
continue
}
code := agentLoopSkillCode(id)
allowedTools = append(allowedTools, code)
catalog = append(catalog, fmt.Sprintf("- %s | Skill | %s | %s | direct function: %s", code, strings.TrimSpace(skill.Name), strings.TrimSpace(skill.Description), toolx.SanitizeToolFunctionName(code)))
}
for _, binding := range snapshot.WorkflowBindings {
if binding.WorkflowVersionID <= 0 {
continue
}
workflows[binding.WorkflowVersionID] = binding
code := agentLoopWorkflowCode(binding.WorkflowVersionID)
allowedTools = append(allowedTools, code)
catalog = append(catalog, fmt.Sprintf("- %s | Workflow | %s | %s | direct function: %s", code, strings.TrimSpace(binding.ToolName), strings.TrimSpace(binding.TriggerInstruction), toolx.SanitizeToolFunctionName(code)))
}
mcpTools, _ := toolx.ParseAgentMCPToolsJSON(req.AIAgent.AllowedMCPTools)
for _, tool := range mcpTools {
if strings.TrimSpace(tool.ToolCode) == "" {
continue
}
allowedTools = append(allowedTools, tool.ToolCode)
catalog = append(catalog, fmt.Sprintf("- %s | MCP | %s | %s | direct function: %s", tool.ToolCode, tool.Title, tool.Description, toolx.SanitizeToolFunctionName(tool.ToolCode)))
}
systemPrompt += "\n\nAvailable capabilities:\n" + strings.Join(catalog, "\n")
systemPrompt += "\n\nUse tool_search with an exact capability code only when needed. You decide whether to answer directly, activate a Skill, execute a Workflow, retrieve knowledge, or call MCP. A Skill activation returns instructions for this same run. Never invent a capability code. Never pass a capability code containing slashes as a tool function name; use the exact direct function name provided in the catalog or call tool_search with toolCode."
systemPrompt += "\n\nHUMAN OPERATOR TRANSFER & SEMANTIC INTENT RECOGNITION:\n" +
"- Customers may request to speak to a human operator, specialist, agent, manager, representative, or live person in many different ways across ANY language (e.g., 'переведи на оператора', 'соедини со специалистом', 'позови сотрудника', 'хочу живого человека', 'мне нужен оператор', 'хватит бота', 'с кем я могу поговорить кроме вас', 'can I speak with someone?', 'connect me to an agent', 'I want a real person', 'representative please', 'let me talk to support', 'quiero hablar con un humano', 'ich möchte mit einem Mitarbeiter sprechen', etc.).\n" +
"- Do NOT require a specific hardcoded phrase. You MUST understand this intent semantically from the user's message regardless of their wording, slang, or language.\n" +
"- When the customer indicates or asks that they want to be connected to a human operator or specialist, you MUST IMMEDIATELY call the conversation_decision tool with action='handoff', handoffInitiator='customer', handoffConfirmed=true, and a short reason. Do NOT ask for confirmation again, do NOT refuse, and do NOT claim in text that the handoff succeeded without calling conversation_decision.\n" +
"- Use action='ask_handoff_confirmation' only when YOU (the AI agent), not the customer, proactively suggest transferring to a human, with handoffInitiator='agent' and handoffConfirmed=false.\n" +
"- For any regular response to the customer, call conversation_decision with action='reply', handoffInitiator='none', handoffConfirmed=false, and your response in reply."
systemPrompt += "\n\nCRITICAL MULTILINGUAL RESPONSE DIRECTIVE: You MUST ALWAYS reply in the EXACT SAME LANGUAGE that the customer uses in their inquiry (e.g. Russian, English, Spanish, German, French, Arabic, Turkish, Kazakh, Italian, Portuguese, Japanese, etc.). Under NO circumstances should you default or drift into Chinese or any language other than the customer's language. Even if capabilities, tool names, or tool outputs contain Chinese or English, translate everything seamlessly into the customer's language."
return agentLoopTurn{
RetrieverCount: retrieverCount,
RetrieveErr: retrieveErr,
ResponsePolicy: responsePolicy,
SystemPrompt: systemPrompt,
UserPrompt: userPrompt,
HistoryCount: historyCount,
AllowedTools: allowedTools,
ToolPolicy: parseAgentLoopToolPolicy(req.AIAgent.ToolPolicy),
Skills: skills,
Workflows: workflows,
MaxSteps: maxSteps,
}
}