Files
ai-agent/internal/services/business_read_tool_service.go
T
t 18c9354095 refactor: 将客服后端重构为宿主可嵌入模块
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。

- 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。

- 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。

- 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
2026-08-28 22:23:13 +08:00

108 lines
3.1 KiB
Go

package services
import (
"context"
"fmt"
"sort"
"strings"
"sync"
"code.tczkiot.com/wlw/ai-agent/contract"
)
var BusinessReadToolService = &businessReadToolService{}
type businessReadToolService struct {
mu sync.RWMutex
tools map[string]contract.BusinessReadTool
}
func SetBusinessReadTools(tools []contract.BusinessReadTool) error {
registered := make(map[string]contract.BusinessReadTool, len(tools))
for _, tool := range tools {
tool.Code = strings.TrimSpace(tool.Code)
tool.Description = strings.TrimSpace(tool.Description)
if tool.Code == "" {
return fmt.Errorf("ai-agent: business read tool code is required")
}
if !strings.HasPrefix(tool.Code, "business/") {
return fmt.Errorf("ai-agent: business read tool code must start with business/: %s", tool.Code)
}
if tool.Description == "" {
return fmt.Errorf("ai-agent: business read tool description is required: %s", tool.Code)
}
if tool.Execute == nil {
return fmt.Errorf("ai-agent: business read tool executor is required: %s", tool.Code)
}
if _, exists := registered[tool.Code]; exists {
return fmt.Errorf("ai-agent: duplicate business read tool code: %s", tool.Code)
}
if tool.InputSchema == nil {
tool.InputSchema = map[string]any{"type": "object", "properties": map[string]any{}}
}
registered[tool.Code] = tool
}
BusinessReadToolService.mu.Lock()
BusinessReadToolService.tools = registered
BusinessReadToolService.mu.Unlock()
return nil
}
func (s *businessReadToolService) ListForCustomerType(customerType string) []contract.BusinessReadTool {
s.mu.RLock()
defer s.mu.RUnlock()
ret := make([]contract.BusinessReadTool, 0, len(s.tools))
for _, tool := range s.tools {
if businessReadToolSupportsCustomerType(tool, customerType) {
ret = append(ret, tool)
}
}
sort.Slice(ret, func(i, j int) bool { return ret[i].Code < ret[j].Code })
return ret
}
func (s *businessReadToolService) ResolveForCustomerType(code, customerType string) (contract.BusinessReadTool, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
tool, ok := s.tools[strings.TrimSpace(code)]
if !ok || !businessReadToolSupportsCustomerType(tool, customerType) {
return contract.BusinessReadTool{}, false
}
return tool, true
}
func (s *businessReadToolService) Resolve(code string) (contract.BusinessReadTool, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
tool, ok := s.tools[strings.TrimSpace(code)]
return tool, ok
}
func (s *businessReadToolService) Execute(
ctx context.Context,
tool contract.BusinessReadTool,
businessContext contract.BusinessReadContext,
arguments map[string]any,
) (any, error) {
if tool.Execute == nil {
return nil, fmt.Errorf("business read tool executor is unavailable: %s", tool.Code)
}
return tool.Execute(ctx, businessContext, arguments)
}
func businessReadToolSupportsCustomerType(tool contract.BusinessReadTool, customerType string) bool {
if len(tool.CustomerTypes) == 0 {
return true
}
customerType = strings.TrimSpace(customerType)
for _, candidate := range tool.CustomerTypes {
if strings.EqualFold(strings.TrimSpace(candidate), customerType) {
return true
}
}
return false
}