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),
|
||||
|
||||
@@ -21,10 +21,17 @@ func (r *Registry) Resolve(ctx Context) (*ToolSet, error) {
|
||||
Tools: make([]einotool.BaseTool, 0, len(r.tools)),
|
||||
ToolCodes: make(map[string]string),
|
||||
}
|
||||
allowedToolCodes := makeAllowedToolCodeSet(ctx.AllowedToolCodes)
|
||||
for _, toolDef := range r.tools {
|
||||
if toolDef == nil || !toolDef.Enabled(ctx) {
|
||||
continue
|
||||
}
|
||||
toolCode := strings.TrimSpace(toolDef.Code())
|
||||
if len(allowedToolCodes) > 0 {
|
||||
if _, ok := allowedToolCodes[toolCode]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
tool, err := toolDef.Build(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -33,7 +40,6 @@ func (r *Registry) Resolve(ctx Context) (*ToolSet, error) {
|
||||
continue
|
||||
}
|
||||
toolName := strings.TrimSpace(toolDef.Name())
|
||||
toolCode := strings.TrimSpace(toolDef.Code())
|
||||
if toolName == "" || toolCode == "" {
|
||||
continue
|
||||
}
|
||||
@@ -42,3 +48,18 @@ func (r *Registry) Resolve(ctx Context) (*ToolSet, error) {
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func makeAllowedToolCodeSet(input []string) map[string]struct{} {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make(map[string]struct{}, len(input))
|
||||
for _, item := range input {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
ret[item] = struct{}{}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Conversation *models.Conversation
|
||||
AIAgent *models.AIAgent
|
||||
AIConfig *models.AIConfig
|
||||
UserMessage *models.Message
|
||||
Conversation *models.Conversation
|
||||
AIAgent *models.AIAgent
|
||||
AIConfig *models.AIConfig
|
||||
UserMessage *models.Message
|
||||
AllowedToolCodes []string
|
||||
}
|
||||
|
||||
type ToolSet struct {
|
||||
|
||||
@@ -36,6 +36,7 @@ type aiReplyTraceData struct {
|
||||
RecheckMs int64 `json:"recheckMs,omitempty"`
|
||||
CommitMs int64 `json:"commitMs,omitempty"`
|
||||
FinalAction string `json:"finalAction,omitempty"`
|
||||
ResumeSource string `json:"resumeSource,omitempty"`
|
||||
ReplySent bool `json:"replySent,omitempty"`
|
||||
ReplyMessageID int64 `json:"replyMessageId,omitempty"`
|
||||
Runtime json.RawMessage `json:"runtime,omitempty"`
|
||||
@@ -162,6 +163,7 @@ func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, conversatio
|
||||
return fmt.Errorf("ai config is nil")
|
||||
}
|
||||
runtimeStartedAt := time.Now()
|
||||
trace.ResumeSource = "pending_interrupt"
|
||||
summary, err := Service.Resume(ctx, ResumeRequest{
|
||||
Conversation: &conversation,
|
||||
AIAgent: &aiAgent,
|
||||
@@ -327,9 +329,14 @@ func (s *aiReplyService) writeRunLog(startedAt time.Time, message models.Message
|
||||
UserMessage: strings.TrimSpace(question),
|
||||
PlannedAction: plannedAction,
|
||||
PlannedSkillCode: strings.TrimSpace(summaryPlannedSkillCode(summary)),
|
||||
PlannedSkillName: strings.TrimSpace(summaryPlannedSkillName(summary)),
|
||||
SkillRouteTrace: strings.TrimSpace(summarySkillRouteTrace(summary)),
|
||||
PlannedToolCode: plannedToolCode,
|
||||
PlanReason: planReason,
|
||||
InterruptType: firstInterruptType(summary),
|
||||
ResumeSource: runLogResumeSource(trace),
|
||||
FinalAction: toRunLogFinalAction(summary),
|
||||
FinalStatus: runLogFinalStatus(summary),
|
||||
ReplyText: buildRunLogReplyText(summary),
|
||||
ErrorMessage: errorMessage,
|
||||
LatencyMs: time.Since(startedAt).Milliseconds(),
|
||||
@@ -422,6 +429,34 @@ func summaryPlannedSkillCode(summary *Summary) string {
|
||||
return strings.TrimSpace(summary.PlannedSkillCode)
|
||||
}
|
||||
|
||||
func summaryPlannedSkillName(summary *Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(summary.PlannedSkillName)
|
||||
}
|
||||
|
||||
func summarySkillRouteTrace(summary *Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(summary.SkillRouteTrace)
|
||||
}
|
||||
|
||||
func runLogResumeSource(trace *aiReplyTraceData) string {
|
||||
if trace == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(trace.ResumeSource)
|
||||
}
|
||||
|
||||
func runLogFinalStatus(summary *Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(summary.Status)
|
||||
}
|
||||
|
||||
func (s *aiReplyService) incrementAIReplyRounds(conversationID int64, nextRounds int, aiAgentName string) error {
|
||||
return repositories.ConversationRepository.Updates(sqls.DB(), conversationID, map[string]any{
|
||||
"ai_reply_rounds": nextRounds,
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"cs-agent/internal/ai/runtime/tools"
|
||||
"cs-agent/internal/ai/skills"
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/toolx"
|
||||
)
|
||||
|
||||
var Service = newService()
|
||||
@@ -89,10 +90,11 @@ func (s *service) prepareToolsForRun(req *Request) error {
|
||||
return nil
|
||||
}
|
||||
toolSet, err := s.registry.Resolve(registry.Context{
|
||||
Conversation: req.Conversation,
|
||||
AIAgent: req.AIAgent,
|
||||
AIConfig: req.AIConfig,
|
||||
UserMessage: req.UserMessage,
|
||||
Conversation: req.Conversation,
|
||||
AIAgent: req.AIAgent,
|
||||
AIConfig: req.AIConfig,
|
||||
UserMessage: req.UserMessage,
|
||||
AllowedToolCodes: resolveAllowedToolCodes(req.AIAgent, req.SelectedSkill),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -200,3 +202,76 @@ func cloneSkillDefinition(item *models.SkillDefinition) *models.SkillDefinition
|
||||
clone := *item
|
||||
return &clone
|
||||
}
|
||||
|
||||
func parseSkillAllowedToolCodes(skill *models.SkillDefinition) []string {
|
||||
if skill == nil {
|
||||
return nil
|
||||
}
|
||||
raw := strings.TrimSpace(skill.AllowedToolCodes)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
var items []string
|
||||
if err := json.Unmarshal([]byte(raw), &items); err != nil {
|
||||
return nil
|
||||
}
|
||||
ret := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, item)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func parseAgentAllowedToolCodes(aiAgent *models.AIAgent) []string {
|
||||
if aiAgent == nil || strings.TrimSpace(aiAgent.AllowedMCPTools) == "" {
|
||||
return nil
|
||||
}
|
||||
items, err := toolx.ParseAgentMCPToolsJSON(aiAgent.AllowedMCPTools)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ret := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
toolCode := strings.TrimSpace(item.ToolCode)
|
||||
if toolCode == "" {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, toolCode)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func resolveAllowedToolCodes(aiAgent *models.AIAgent, skill *models.SkillDefinition) []string {
|
||||
agentAllowed := parseAgentAllowedToolCodes(aiAgent)
|
||||
skillAllowed := parseSkillAllowedToolCodes(skill)
|
||||
switch {
|
||||
case len(agentAllowed) == 0:
|
||||
return skillAllowed
|
||||
case len(skillAllowed) == 0:
|
||||
return agentAllowed
|
||||
default:
|
||||
skillSet := make(map[string]struct{}, len(skillAllowed))
|
||||
for _, item := range skillAllowed {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
skillSet[item] = struct{}{}
|
||||
}
|
||||
ret := make([]string, 0, len(agentAllowed))
|
||||
for _, item := range agentAllowed {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := skillSet[item]; ok {
|
||||
ret = append(ret, item)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/dto"
|
||||
"cs-agent/internal/pkg/dto/request"
|
||||
"cs-agent/internal/pkg/toolx"
|
||||
"cs-agent/internal/services"
|
||||
|
||||
componenttool "github.com/cloudwego/eino/components/tool"
|
||||
@@ -20,8 +21,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CreateTicketConfirmToolCode = "builtin/create_ticket_with_confirmation"
|
||||
CreateTicketConfirmToolName = "create_ticket_with_confirmation"
|
||||
CreateTicketConfirmToolCode = toolx.BuiltinCreateTicketConfirmToolCode
|
||||
CreateTicketConfirmToolName = toolx.BuiltinCreateTicketConfirmToolName
|
||||
)
|
||||
|
||||
type CreateTicketConfirmState struct {
|
||||
|
||||
@@ -18,9 +18,14 @@ func BuildAgentRunLog(item *models.AgentRunLog) response.AgentRunLogResponse {
|
||||
UserMessage: item.UserMessage,
|
||||
PlannedAction: item.PlannedAction,
|
||||
PlannedSkillCode: item.PlannedSkillCode,
|
||||
PlannedSkillName: item.PlannedSkillName,
|
||||
SkillRouteTrace: item.SkillRouteTrace,
|
||||
PlannedToolCode: item.PlannedToolCode,
|
||||
PlanReason: item.PlanReason,
|
||||
InterruptType: item.InterruptType,
|
||||
ResumeSource: item.ResumeSource,
|
||||
FinalAction: item.FinalAction,
|
||||
FinalStatus: item.FinalStatus,
|
||||
ReplyText: item.ReplyText,
|
||||
ErrorMessage: item.ErrorMessage,
|
||||
LatencyMs: item.LatencyMs,
|
||||
|
||||
@@ -174,12 +174,29 @@ func buildAIAgentResponse(item *models.AIAgent) response.AIAgentResponse {
|
||||
if toolCode == "" {
|
||||
toolCode = toolx.BuildMCPToolCode(tool.ServerCode, tool.ToolName)
|
||||
}
|
||||
serverCode := strings.TrimSpace(tool.ServerCode)
|
||||
toolName := strings.TrimSpace(tool.ToolName)
|
||||
if toolCode == toolx.BuiltinCreateTicketConfirmToolCode {
|
||||
serverCode = toolx.BuiltinToolCatalogServerCode
|
||||
toolName = toolx.BuiltinCreateTicketConfirmToolName
|
||||
} else if parsedServerCode, parsedToolName := toolx.SplitMCPToolCode(toolCode); parsedServerCode != "" && parsedToolName != "" {
|
||||
serverCode = parsedServerCode
|
||||
toolName = parsedToolName
|
||||
}
|
||||
title := strings.TrimSpace(tool.Title)
|
||||
if title == "" && toolCode == toolx.BuiltinCreateTicketConfirmToolCode {
|
||||
title = toolx.BuiltinCreateTicketConfirmToolTitle
|
||||
}
|
||||
description := strings.TrimSpace(tool.Description)
|
||||
if description == "" && toolCode == toolx.BuiltinCreateTicketConfirmToolCode {
|
||||
description = toolx.BuiltinCreateTicketConfirmToolDescription
|
||||
}
|
||||
ret.DirectTools = append(ret.DirectTools, response.AIAgentMCPToolResponse{
|
||||
ToolCode: toolCode,
|
||||
ServerCode: strings.TrimSpace(tool.ServerCode),
|
||||
ToolName: strings.TrimSpace(tool.ToolName),
|
||||
Title: strings.TrimSpace(tool.Title),
|
||||
Description: strings.TrimSpace(tool.Description),
|
||||
ServerCode: serverCode,
|
||||
ToolName: toolName,
|
||||
Title: title,
|
||||
Description: description,
|
||||
Arguments: tool.Arguments,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ func (c *MCPController) AnyCatalog() *web.JsonResult {
|
||||
ToolCode: item.ToolCode,
|
||||
ServerCode: item.ServerCode,
|
||||
ToolName: item.ToolName,
|
||||
SourceType: item.SourceType,
|
||||
Title: item.Title,
|
||||
Description: item.Description,
|
||||
InputSchema: item.InputSchema,
|
||||
|
||||
@@ -2,18 +2,13 @@ package console
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cs-agent/internal/builders"
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/constants"
|
||||
"cs-agent/internal/pkg/dto/request"
|
||||
"cs-agent/internal/pkg/dto/response"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
"cs-agent/internal/pkg/errorsx"
|
||||
"cs-agent/internal/pkg/utils"
|
||||
"cs-agent/internal/services"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
@@ -80,29 +75,8 @@ func (c *SkillDefinitionController) PostCreate() *web.JsonResult {
|
||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
if err := validateSkillDefinitionRequest(req); err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
if services.SkillDefinitionService.Take("code = ?", strings.TrimSpace(req.Code)) != nil {
|
||||
return web.JsonErrorMsg("Skill 编码已存在")
|
||||
}
|
||||
|
||||
item := &models.SkillDefinition{
|
||||
Code: strings.TrimSpace(req.Code),
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
Content: strings.TrimSpace(req.Content),
|
||||
Examples: mustMarshalJSONStringArray(req.Examples),
|
||||
AllowedToolCodes: mustMarshalJSONStringArray(req.AllowedToolCodes),
|
||||
Priority: normalizeSkillPriority(req.Priority),
|
||||
Status: enums.StatusOk,
|
||||
Remark: strings.TrimSpace(req.Remark),
|
||||
AuditFields: utils.BuildAuditFields(operator),
|
||||
}
|
||||
if item.Priority <= 0 {
|
||||
item.Priority = services.SkillDefinitionService.NextPriority()
|
||||
}
|
||||
if err := services.SkillDefinitionService.Create(item); err != nil {
|
||||
item, err := services.SkillDefinitionService.CreateSkillDefinition(req, operator)
|
||||
if err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
return web.JsonData(builders.BuildSkillDefinitionResponse(item))
|
||||
@@ -118,35 +92,7 @@ func (c *SkillDefinitionController) PostUpdate() *web.JsonResult {
|
||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
if req.ID <= 0 {
|
||||
return web.JsonErrorMsg("Skill ID 不合法")
|
||||
}
|
||||
if err := validateSkillDefinitionRequest(req.CreateSkillDefinitionRequest); err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
|
||||
item := services.SkillDefinitionService.Get(req.ID)
|
||||
if item == nil {
|
||||
return web.JsonErrorMsg("Skill 不存在")
|
||||
}
|
||||
exists := services.SkillDefinitionService.Take("code = ? AND id <> ?", strings.TrimSpace(req.Code), req.ID)
|
||||
if exists != nil {
|
||||
return web.JsonErrorMsg("Skill 编码已存在")
|
||||
}
|
||||
|
||||
if err := services.SkillDefinitionService.Updates(req.ID, map[string]any{
|
||||
"code": strings.TrimSpace(req.Code),
|
||||
"name": strings.TrimSpace(req.Name),
|
||||
"description": strings.TrimSpace(req.Description),
|
||||
"content": strings.TrimSpace(req.Content),
|
||||
"examples": mustMarshalJSONStringArray(req.Examples),
|
||||
"allowed_tool_codes": mustMarshalJSONStringArray(req.AllowedToolCodes),
|
||||
"priority": resolveSkillPriorityForUpdate(req.Priority, item.Priority),
|
||||
"remark": strings.TrimSpace(req.Remark),
|
||||
"update_user_id": operator.UserID,
|
||||
"update_user_name": operator.Username,
|
||||
"updated_at": time.Now(),
|
||||
}); err != nil {
|
||||
if err := services.SkillDefinitionService.UpdateSkillDefinition(req, operator); err != nil {
|
||||
return web.JsonError(err)
|
||||
}
|
||||
return web.JsonSuccess()
|
||||
@@ -240,75 +186,3 @@ func (c *SkillDefinitionController) PostDebug_run() *web.JsonResult {
|
||||
}
|
||||
return web.JsonData(resp)
|
||||
}
|
||||
|
||||
func validateSkillDefinitionRequest(req request.CreateSkillDefinitionRequest) error {
|
||||
code := strings.TrimSpace(req.Code)
|
||||
name := strings.TrimSpace(req.Name)
|
||||
content := strings.TrimSpace(req.Content)
|
||||
if code == "" {
|
||||
return errorsx.InvalidParam("Skill 编码不能为空")
|
||||
}
|
||||
if name == "" {
|
||||
return errorsx.InvalidParam("Skill 名称不能为空")
|
||||
}
|
||||
if content == "" {
|
||||
return errorsx.InvalidParam("Content 不能为空")
|
||||
}
|
||||
if _, err := normalizeJSONStringArray(req.Examples); err != nil {
|
||||
return err
|
||||
}
|
||||
allowedToolCodes, err := normalizeJSONStringArray(req.AllowedToolCodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, toolCode := range allowedToolCodes {
|
||||
if err := services.ToolCatalogService.ValidateMCPToolCode(toolCode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeSkillPriority(priority int) int {
|
||||
if priority < 0 {
|
||||
return 0
|
||||
}
|
||||
return priority
|
||||
}
|
||||
|
||||
func resolveSkillPriorityForUpdate(input, current int) int {
|
||||
input = normalizeSkillPriority(input)
|
||||
if input <= 0 {
|
||||
return current
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func normalizeJSONStringArray(input []string) ([]string, error) {
|
||||
ret := make([]string, 0, len(input))
|
||||
seen := make(map[string]struct{})
|
||||
for _, item := range input {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[item]; exists {
|
||||
continue
|
||||
}
|
||||
seen[item] = struct{}{}
|
||||
ret = append(ret, item)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func mustMarshalJSONStringArray(input []string) string {
|
||||
items, _ := normalizeJSONStringArray(input)
|
||||
if len(items) == 0 {
|
||||
return "[]"
|
||||
}
|
||||
buf, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
return "[]"
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
@@ -950,9 +950,14 @@ type AgentRunLog struct {
|
||||
UserMessage string `gorm:"type:longtext"`
|
||||
PlannedAction string `gorm:"type:varchar(30);not null;default:'';index"`
|
||||
PlannedSkillCode string `gorm:"type:varchar(100);not null;default:'';index"`
|
||||
PlannedSkillName string `gorm:"type:varchar(100);not null;default:''"`
|
||||
SkillRouteTrace string `gorm:"type:text"`
|
||||
PlannedToolCode string `gorm:"type:varchar(200);not null;default:'';index"`
|
||||
PlanReason string `gorm:"type:varchar(500);not null;default:''"`
|
||||
InterruptType string `gorm:"type:varchar(50);not null;default:'';index"`
|
||||
ResumeSource string `gorm:"type:varchar(50);not null;default:'';index"`
|
||||
FinalAction string `gorm:"type:varchar(30);not null;default:'';index"`
|
||||
FinalStatus string `gorm:"type:varchar(30);not null;default:'';index"`
|
||||
ReplyText string `gorm:"type:longtext"`
|
||||
ErrorMessage string `gorm:"type:text"`
|
||||
LatencyMs int64 `gorm:"type:bigint;not null;default:0"`
|
||||
|
||||
@@ -69,6 +69,7 @@ type MCPToolCatalogResponse struct {
|
||||
ToolCode string `json:"toolCode"`
|
||||
ServerCode string `json:"serverCode"`
|
||||
ToolName string `json:"toolName"`
|
||||
SourceType string `json:"sourceType"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
InputSchema any `json:"inputSchema"`
|
||||
|
||||
@@ -46,9 +46,14 @@ type AgentRunLogResponse struct {
|
||||
UserMessage string `json:"userMessage"`
|
||||
PlannedAction string `json:"plannedAction"`
|
||||
PlannedSkillCode string `json:"plannedSkillCode"`
|
||||
PlannedSkillName string `json:"plannedSkillName"`
|
||||
SkillRouteTrace string `json:"skillRouteTrace"`
|
||||
PlannedToolCode string `json:"plannedToolCode"`
|
||||
PlanReason string `json:"planReason"`
|
||||
InterruptType string `json:"interruptType"`
|
||||
ResumeSource string `json:"resumeSource"`
|
||||
FinalAction string `json:"finalAction"`
|
||||
FinalStatus string `json:"finalStatus"`
|
||||
ReplyText string `json:"replyText"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
LatencyMs int64 `json:"latencyMs"`
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package toolx
|
||||
|
||||
const (
|
||||
BuiltinToolCatalogServerCode = "builtin"
|
||||
BuiltinCreateTicketConfirmToolCode = "builtin/create_ticket_with_confirmation"
|
||||
BuiltinCreateTicketConfirmToolName = "create_ticket_with_confirmation"
|
||||
BuiltinCreateTicketConfirmToolTitle = "创建工单并发起确认"
|
||||
BuiltinCreateTicketConfirmToolDescription = "当用户明确要求创建工单,且标题和描述已经整理清楚后调用。工具会先向用户确认,确认后才真正创建工单。"
|
||||
)
|
||||
@@ -35,23 +35,32 @@ func NormalizeMCPToolRequest(item request.AIAgentMCPToolRequest) (request.AIAgen
|
||||
toolName := strings.TrimSpace(item.ToolName)
|
||||
if toolCode != "" {
|
||||
parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode)
|
||||
if parsedServerCode == "" || parsedToolName == "" {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 格式不合法")
|
||||
if parsedServerCode != "" && parsedToolName != "" {
|
||||
if serverCode != "" && !strings.EqualFold(serverCode, parsedServerCode) {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 serverCode 不一致")
|
||||
}
|
||||
if toolName != "" && !strings.EqualFold(toolName, parsedToolName) {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 toolName 不一致")
|
||||
}
|
||||
serverCode = parsedServerCode
|
||||
toolName = parsedToolName
|
||||
} else {
|
||||
serverCode = ""
|
||||
toolName = ""
|
||||
}
|
||||
if serverCode != "" && !strings.EqualFold(serverCode, parsedServerCode) {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 serverCode 不一致")
|
||||
}
|
||||
if toolName != "" && !strings.EqualFold(toolName, parsedToolName) {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 toolName 不一致")
|
||||
}
|
||||
serverCode = parsedServerCode
|
||||
toolName = parsedToolName
|
||||
} else {
|
||||
toolCode = BuildMCPToolCode(serverCode, toolName)
|
||||
}
|
||||
if toolCode == "" || serverCode == "" || toolName == "" {
|
||||
if toolCode == "" {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode、serverCode 和 toolName 不能为空")
|
||||
}
|
||||
if parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode); parsedServerCode != "" && parsedToolName != "" {
|
||||
serverCode = parsedServerCode
|
||||
toolName = parsedToolName
|
||||
}
|
||||
if serverCode == "" && toolName == "" && strings.Contains(toolCode, "/") && !strings.HasPrefix(toolCode, "builtin/") {
|
||||
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 格式不合法")
|
||||
}
|
||||
ret := request.AIAgentMCPToolRequest{
|
||||
ToolCode: toolCode,
|
||||
ServerCode: serverCode,
|
||||
|
||||
@@ -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