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:
mlogclub
2026-04-10 14:43:57 +08:00
parent 7411bac57d
commit 1370e4b675
23 changed files with 642 additions and 222 deletions
@@ -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),