feat: enhance AI agent functionality with tool management and logging improvements
- Added AllowedToolCodes field to Context for managing tool access. - Introduced ResumeSource in aiReplyTraceData to track resume points. - Enhanced logging with additional fields: PlannedSkillName, SkillRouteTrace, InterruptType, ResumeSource, and FinalStatus. - Implemented functions to parse and resolve allowed tool codes for agents and skills. - Refactored skill definition creation and update logic to streamline request handling. - Updated MCP tool catalog to include source type and integrated built-in tools. - Improved UI components to display additional tool information and enhance user experience.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
einoadapter "cs-agent/internal/ai/runtime/internal/impl/adapter"
|
||||
@@ -17,14 +18,16 @@ import (
|
||||
)
|
||||
|
||||
type AgentFactory struct {
|
||||
chatModelFactory *ChatModelFactory
|
||||
toolFactory *ToolFactory
|
||||
chatModelFactory *ChatModelFactory
|
||||
toolFactory *ToolFactory
|
||||
instructionAssembler *InstructionAssembler
|
||||
}
|
||||
|
||||
func NewAgentFactory() *AgentFactory {
|
||||
return &AgentFactory{
|
||||
chatModelFactory: NewChatModelFactory(),
|
||||
toolFactory: NewToolFactory(),
|
||||
chatModelFactory: NewChatModelFactory(),
|
||||
toolFactory: NewToolFactory(),
|
||||
instructionAssembler: NewInstructionAssembler(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,13 +97,35 @@ func buildAgentInstruction(aiAgent *models.AIAgent, selectedSkill *models.SkillD
|
||||
5. 如果用户只是咨询、抱怨或泛泛表达不满,但没有明确要求建单,优先继续澄清,不要主动创建工单。
|
||||
`))
|
||||
}
|
||||
if len(appendixParts) == 0 {
|
||||
return baseInstruction
|
||||
projectRoot, _ := os.Getwd()
|
||||
return NewInstructionAssembler().Build(InstructionAssemblerInput{
|
||||
ProjectRoot: projectRoot,
|
||||
AgentInstruction: baseInstruction,
|
||||
SkillInstruction: firstAppendixPart(appendixParts),
|
||||
ToolAppendices: remainingAppendixParts(appendixParts),
|
||||
})
|
||||
}
|
||||
|
||||
func firstAppendixPart(parts []string) string {
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
if baseInstruction == "" {
|
||||
return strings.Join(appendixParts, "\n\n")
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
func remainingAppendixParts(parts []string) []string {
|
||||
if len(parts) <= 1 {
|
||||
return nil
|
||||
}
|
||||
return baseInstruction + "\n\n" + strings.Join(appendixParts, "\n\n")
|
||||
ret := make([]string, 0, len(parts)-1)
|
||||
for _, item := range parts[1:] {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, item)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func buildSelectedSkillInstruction(skill *models.SkillDefinition, toolDefinitions []einoadapter.MCPToolDefinition) string {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type InstructionAssembler struct {
|
||||
governanceInstruction string
|
||||
}
|
||||
|
||||
type InstructionAssemblerInput struct {
|
||||
AgentInstruction string
|
||||
SkillInstruction string
|
||||
ToolAppendices []string
|
||||
ProjectRoot string
|
||||
ProjectInstruction string
|
||||
}
|
||||
|
||||
var (
|
||||
projectInstructionOnce sync.Once
|
||||
projectInstructionText string
|
||||
)
|
||||
|
||||
func NewInstructionAssembler() *InstructionAssembler {
|
||||
return &InstructionAssembler{
|
||||
governanceInstruction: strings.TrimSpace(`
|
||||
你正在一个有明确工程约束的客服系统中工作。
|
||||
执行时必须严格遵守当前注入的项目规则、Agent 规则和技能规则。
|
||||
如果存在工具白名单限制,只能调用当前允许的工具;信息不足时优先追问,不要伪造事实或跳过必要确认。
|
||||
`),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InstructionAssembler) Build(input InstructionAssemblerInput) string {
|
||||
parts := make([]string, 0, 5)
|
||||
projectInstruction := strings.TrimSpace(input.ProjectInstruction)
|
||||
if projectInstruction == "" {
|
||||
projectInstruction = loadProjectInstruction(input.ProjectRoot)
|
||||
}
|
||||
if projectInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("项目级规则", projectInstruction))
|
||||
}
|
||||
if a != nil && strings.TrimSpace(a.governanceInstruction) != "" {
|
||||
parts = append(parts, buildInstructionSection("系统治理规则", a.governanceInstruction))
|
||||
}
|
||||
if agentInstruction := strings.TrimSpace(input.AgentInstruction); agentInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("Agent 规则", agentInstruction))
|
||||
}
|
||||
if skillInstruction := strings.TrimSpace(input.SkillInstruction); skillInstruction != "" {
|
||||
parts = append(parts, buildInstructionSection("当前技能上下文", skillInstruction))
|
||||
}
|
||||
if appendix := buildToolAppendix(input.ToolAppendices); appendix != "" {
|
||||
parts = append(parts, buildInstructionSection("工具补充规则", appendix))
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(parts, "\n\n"))
|
||||
}
|
||||
|
||||
func buildInstructionSection(title, body string) string {
|
||||
title = strings.TrimSpace(title)
|
||||
body = strings.TrimSpace(body)
|
||||
if body == "" {
|
||||
return ""
|
||||
}
|
||||
if title == "" {
|
||||
return body
|
||||
}
|
||||
return title + ":\n" + body
|
||||
}
|
||||
|
||||
func buildToolAppendix(input []string) string {
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, 0, len(input))
|
||||
for _, item := range input {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, item)
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(parts, "\n\n"))
|
||||
}
|
||||
|
||||
func loadProjectInstruction(projectRoot string) string {
|
||||
// TODO 这里还要读取工程目录中的AGENTS.md吗?
|
||||
projectInstructionOnce.Do(func() {
|
||||
candidates := []string{
|
||||
"AGENTS.md",
|
||||
}
|
||||
projectRoot = strings.TrimSpace(projectRoot)
|
||||
if projectRoot != "" {
|
||||
candidates = append([]string{filepath.Join(projectRoot, "AGENTS.md")}, candidates...)
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
candidate = strings.TrimSpace(candidate)
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
data, err := os.ReadFile(candidate)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
projectInstructionText = strings.TrimSpace(string(data))
|
||||
if projectInstructionText != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return projectInstructionText
|
||||
}
|
||||
@@ -32,10 +32,14 @@ func (f *ToolFactory) BuildMCPTools(aiAgent *models.AIAgent) ([]impladapter.MCPT
|
||||
if toolCode == "" {
|
||||
toolCode = toolx.BuildMCPToolCode(item.ServerCode, item.ToolName)
|
||||
}
|
||||
serverCode, toolName := toolx.SplitMCPToolCode(toolCode)
|
||||
if serverCode == "" || toolName == "" {
|
||||
continue
|
||||
}
|
||||
definition := impladapter.MCPToolDefinition{
|
||||
ToolCode: toolCode,
|
||||
ServerCode: strings.TrimSpace(item.ServerCode),
|
||||
ToolName: strings.TrimSpace(item.ToolName),
|
||||
ServerCode: serverCode,
|
||||
ToolName: toolName,
|
||||
Title: strings.TrimSpace(item.Title),
|
||||
Description: strings.TrimSpace(item.Description),
|
||||
FixedArgs: cloneStringMap(item.Arguments),
|
||||
|
||||
Reference in New Issue
Block a user