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:
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/config"
|
||||
"cs-agent/internal/pkg/dto"
|
||||
"cs-agent/internal/pkg/dto/request"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
@@ -288,10 +287,6 @@ func (s *aIAgentService) normalizeDirectTools(input []request.AIAgentMCPToolRequ
|
||||
if len(input) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
cfg := config.Current()
|
||||
if !cfg.MCP.Enabled {
|
||||
return nil, errorsx.InvalidParam("系统未启用 MCP,不能配置 Direct Tool")
|
||||
}
|
||||
ret := make([]request.AIAgentMCPToolRequest, 0, len(input))
|
||||
seen := make(map[string]struct{})
|
||||
for _, item := range input {
|
||||
@@ -299,10 +294,8 @@ func (s *aIAgentService) normalizeDirectTools(input []request.AIAgentMCPToolRequ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverCode := strings.TrimSpace(normalized.ServerCode)
|
||||
server, ok := cfg.MCP.Servers[serverCode]
|
||||
if !ok || !server.Enabled {
|
||||
return nil, errorsx.InvalidParam("Direct Tool 绑定的 MCP 服务不存在或未启用")
|
||||
if err := ToolCatalogService.ValidateToolCode(normalized.ToolCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := strings.TrimSpace(normalized.ToolCode)
|
||||
if _, exists := seen[key]; exists {
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/dto"
|
||||
"cs-agent/internal/pkg/dto/request"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
"cs-agent/internal/pkg/errorsx"
|
||||
"cs-agent/internal/pkg/utils"
|
||||
"cs-agent/internal/repositories"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
@@ -86,3 +95,156 @@ func (s *skillDefinitionService) UpdatePriority(ids []int64) error {
|
||||
func (s *skillDefinitionService) GetByCode(code string) *models.SkillDefinition {
|
||||
return repositories.SkillDefinitionRepository.GetByCode(sqls.DB(), code)
|
||||
}
|
||||
|
||||
func (s *skillDefinitionService) CreateSkillDefinition(req request.CreateSkillDefinitionRequest, operator *dto.AuthPrincipal) (*models.SkillDefinition, error) {
|
||||
if operator == nil {
|
||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
||||
}
|
||||
normalized, err := s.normalizeSkillDefinitionRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Take("code = ?", normalized.Code) != nil {
|
||||
return nil, errorsx.InvalidParam("Skill 编码已存在")
|
||||
}
|
||||
item := &models.SkillDefinition{
|
||||
Code: normalized.Code,
|
||||
Name: normalized.Name,
|
||||
Description: normalized.Description,
|
||||
Content: normalized.Content,
|
||||
Examples: mustMarshalSkillStringArray(normalized.Examples),
|
||||
AllowedToolCodes: mustMarshalSkillStringArray(normalized.AllowedToolCodes),
|
||||
Priority: normalized.Priority,
|
||||
Status: enums.StatusOk,
|
||||
Remark: normalized.Remark,
|
||||
AuditFields: utils.BuildAuditFields(operator),
|
||||
}
|
||||
if item.Priority <= 0 {
|
||||
item.Priority = s.NextPriority()
|
||||
}
|
||||
if err := repositories.SkillDefinitionRepository.Create(sqls.DB(), item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *skillDefinitionService) UpdateSkillDefinition(req request.UpdateSkillDefinitionRequest, operator *dto.AuthPrincipal) error {
|
||||
if operator == nil {
|
||||
return errorsx.Unauthorized("未登录或登录已过期")
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
return errorsx.InvalidParam("Skill ID 不合法")
|
||||
}
|
||||
current := s.Get(req.ID)
|
||||
if current == nil {
|
||||
return errorsx.InvalidParam("Skill 不存在")
|
||||
}
|
||||
normalized, err := s.normalizeSkillDefinitionRequest(req.CreateSkillDefinitionRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists := s.Take("code = ? AND id <> ?", normalized.Code, req.ID); exists != nil {
|
||||
return errorsx.InvalidParam("Skill 编码已存在")
|
||||
}
|
||||
return repositories.SkillDefinitionRepository.Updates(sqls.DB(), req.ID, map[string]any{
|
||||
"code": normalized.Code,
|
||||
"name": normalized.Name,
|
||||
"description": normalized.Description,
|
||||
"content": normalized.Content,
|
||||
"examples": mustMarshalSkillStringArray(normalized.Examples),
|
||||
"allowed_tool_codes": mustMarshalSkillStringArray(normalized.AllowedToolCodes),
|
||||
"priority": resolveSkillPriorityForService(normalized.Priority, current.Priority),
|
||||
"remark": normalized.Remark,
|
||||
"update_user_id": operator.UserID,
|
||||
"update_user_name": operator.Username,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *skillDefinitionService) normalizeSkillDefinitionRequest(req request.CreateSkillDefinitionRequest) (*request.CreateSkillDefinitionRequest, error) {
|
||||
normalized := &request.CreateSkillDefinitionRequest{
|
||||
Code: strings.TrimSpace(req.Code),
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
Content: strings.TrimSpace(req.Content),
|
||||
Priority: normalizeSkillPriorityForService(req.Priority),
|
||||
Remark: strings.TrimSpace(req.Remark),
|
||||
}
|
||||
if normalized.Code == "" {
|
||||
return nil, errorsx.InvalidParam("Skill 编码不能为空")
|
||||
}
|
||||
if normalized.Name == "" {
|
||||
return nil, errorsx.InvalidParam("Skill 名称不能为空")
|
||||
}
|
||||
if normalized.Content == "" {
|
||||
return nil, errorsx.InvalidParam("Content 不能为空")
|
||||
}
|
||||
examples, err := normalizeSkillStringArray(req.Examples)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowedToolCodes, err := normalizeSkillStringArray(req.AllowedToolCodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, toolCode := range allowedToolCodes {
|
||||
if err := ToolCatalogService.ValidateMCPToolCode(toolCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
normalized.Examples = examples
|
||||
normalized.AllowedToolCodes = allowedToolCodes
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func normalizeSkillStringArray(input []string) ([]string, error) {
|
||||
buf, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return nil, errorsx.InvalidParam("JSON 数组格式不合法")
|
||||
}
|
||||
var ret []string
|
||||
if err := json.Unmarshal(buf, &ret); err != nil {
|
||||
return nil, errorsx.InvalidParam("JSON 数组格式不合法")
|
||||
}
|
||||
normalized := make([]string, 0, len(ret))
|
||||
seen := make(map[string]struct{}, len(ret))
|
||||
for _, item := range ret {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[item]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item] = struct{}{}
|
||||
normalized = append(normalized, item)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func mustMarshalSkillStringArray(input []string) string {
|
||||
items, err := normalizeSkillStringArray(input)
|
||||
if err != nil || len(items) == 0 {
|
||||
return "[]"
|
||||
}
|
||||
buf, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
return "[]"
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func normalizeSkillPriorityForService(priority int) int {
|
||||
if priority < 0 {
|
||||
return 0
|
||||
}
|
||||
return priority
|
||||
}
|
||||
|
||||
func resolveSkillPriorityForService(input, current int) int {
|
||||
input = normalizeSkillPriorityForService(input)
|
||||
if input <= 0 {
|
||||
return current
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ type MCPToolCatalogItem struct {
|
||||
ToolCode string
|
||||
ServerCode string
|
||||
ToolName string
|
||||
SourceType string
|
||||
Title string
|
||||
Description string
|
||||
InputSchema any
|
||||
@@ -46,6 +47,14 @@ func (s *toolCatalogService) ListMCPTools(ctx context.Context) ([]MCPToolCatalog
|
||||
}
|
||||
slices.Sort(serverCodes)
|
||||
ret := make([]MCPToolCatalogItem, 0)
|
||||
ret = append(ret, MCPToolCatalogItem{
|
||||
ToolCode: toolx.BuiltinCreateTicketConfirmToolCode,
|
||||
ServerCode: toolx.BuiltinToolCatalogServerCode,
|
||||
ToolName: toolx.BuiltinCreateTicketConfirmToolName,
|
||||
SourceType: toolx.BuiltinToolCatalogServerCode,
|
||||
Title: toolx.BuiltinCreateTicketConfirmToolTitle,
|
||||
Description: toolx.BuiltinCreateTicketConfirmToolDescription,
|
||||
})
|
||||
for _, serverCode := range serverCodes {
|
||||
tools, err := mcps.Runtime.ListTools(ctx, serverCode)
|
||||
if err != nil {
|
||||
@@ -56,6 +65,7 @@ func (s *toolCatalogService) ListMCPTools(ctx context.Context) ([]MCPToolCatalog
|
||||
ToolCode: toolx.BuildMCPToolCode(serverCode, item.Name),
|
||||
ServerCode: serverCode,
|
||||
ToolName: strings.TrimSpace(item.Name),
|
||||
SourceType: "mcp",
|
||||
Title: strings.TrimSpace(item.Title),
|
||||
Description: strings.TrimSpace(item.Description),
|
||||
InputSchema: item.InputSchema,
|
||||
@@ -67,14 +77,25 @@ func (s *toolCatalogService) ListMCPTools(ctx context.Context) ([]MCPToolCatalog
|
||||
}
|
||||
|
||||
func (s *toolCatalogService) ValidateMCPToolCode(toolCode string) error {
|
||||
return s.ValidateToolCode(toolCode)
|
||||
}
|
||||
|
||||
func (s *toolCatalogService) ValidateToolCode(toolCode string) error {
|
||||
cfg := config.Current()
|
||||
if !cfg.MCP.Enabled {
|
||||
return errorsx.InvalidParam("MCP未启用")
|
||||
toolCode = strings.TrimSpace(toolCode)
|
||||
if toolCode == "" {
|
||||
return errorsx.InvalidParam("toolCode不能为空")
|
||||
}
|
||||
if toolCode == toolx.BuiltinCreateTicketConfirmToolCode {
|
||||
return nil
|
||||
}
|
||||
serverCode, toolName := toolx.SplitMCPToolCode(toolCode)
|
||||
if serverCode == "" || toolName == "" {
|
||||
return errorsx.InvalidParam("toolCode格式不合法")
|
||||
}
|
||||
if !cfg.MCP.Enabled {
|
||||
return errorsx.InvalidParam("MCP未启用")
|
||||
}
|
||||
server, ok := cfg.MCP.Servers[serverCode]
|
||||
if !ok || !server.Enabled {
|
||||
return errorsx.InvalidParam("toolCode 绑定的 MCP 服务不存在或未启用")
|
||||
|
||||
Reference in New Issue
Block a user