feat: Round 5 - Memory Service, Tool Engine, Call Records, Thinking Logs
- Fix: Session history flash (race condition + WS guard) - Fix: Chat background overlay + sidebar transparency - Fix: IoT device control (Chinese action names, status field) - Feat: Independent memory-service (port 8091, 13 endpoints) - Feat: Independent tool-engine service (port 8092, 13 tools) - Feat: Tool call logs with paginated DevTools panel - Feat: Thinking log records with DevTools panel - Feat: Future development roadmap document - Chore: Updated .gitignore, go.work, DevTools config - Chore: 5-service health check, project review docs
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/yourname/cyrene-ai/tool-engine/internal/model"
|
||||
)
|
||||
|
||||
// JSONTool provides JSON parsing, querying, and validation for the LLM.
|
||||
type JSONTool struct{}
|
||||
|
||||
// NewJSONTool creates a JSON processing tool.
|
||||
func NewJSONTool() *JSONTool {
|
||||
return &JSONTool{}
|
||||
}
|
||||
|
||||
// Definition returns the tool definition for LLM function calling.
|
||||
func (t *JSONTool) Definition() model.ToolDefinition {
|
||||
return model.ToolDefinition{
|
||||
Name: "json_ops",
|
||||
Description: "JSON处理工具。解析JSON字符串并格式化输出、用简单路径查询JSON字段、验证JSON是否合法。",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"action": map[string]interface{}{
|
||||
"type": "string",
|
||||
"enum": []string{"parse", "query", "validate"},
|
||||
"description": "操作类型。parse: 解析JSON并格式化输出;query: 用路径查询JSON中的值(如\"users.0.name\"表示取users数组第0个元素的name字段);validate: 验证JSON字符串是否合法",
|
||||
},
|
||||
"json_string": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "JSON字符串",
|
||||
},
|
||||
"path": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "查询路径(query操作时使用)。支持点分隔和数组索引,如 \"users.0.name\"、\"data.list.2.title\"",
|
||||
},
|
||||
},
|
||||
"required": []string{"action", "json_string"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Execute performs JSON operations.
|
||||
func (t *JSONTool) Execute(ctx context.Context, arguments map[string]interface{}) (*model.ToolResult, error) {
|
||||
action, ok := arguments["action"].(string)
|
||||
if !ok || action == "" {
|
||||
return &model.ToolResult{ID: "", Error: "缺少 action 参数"}, nil
|
||||
}
|
||||
|
||||
jsonStr, ok := arguments["json_string"].(string)
|
||||
if !ok || jsonStr == "" {
|
||||
return &model.ToolResult{ID: "", Error: "缺少 json_string 参数"}, nil
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "parse":
|
||||
return t.handleParse(jsonStr)
|
||||
case "query":
|
||||
path, _ := arguments["path"].(string)
|
||||
return t.handleQuery(jsonStr, path)
|
||||
case "validate":
|
||||
return t.handleValidate(jsonStr)
|
||||
default:
|
||||
return &model.ToolResult{
|
||||
ID: "",
|
||||
Error: fmt.Sprintf("未知操作: %s,支持: parse, query, validate", action),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *JSONTool) handleParse(jsonStr string) (*model.ToolResult, error) {
|
||||
var data interface{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||
return &model.ToolResult{ID: "", Error: fmt.Sprintf("JSON解析失败: %v", err)}, nil
|
||||
}
|
||||
|
||||
pretty, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return &model.ToolResult{ID: "", Error: fmt.Sprintf("JSON格式化失败: %v", err)}, nil
|
||||
}
|
||||
|
||||
return &model.ToolResult{
|
||||
ID: "",
|
||||
Output: fmt.Sprintf("解析成功\n格式化输出:\n%s", string(pretty)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *JSONTool) handleQuery(jsonStr, path string) (*model.ToolResult, error) {
|
||||
if path == "" {
|
||||
return &model.ToolResult{ID: "", Error: "query 操作需要 path 参数"}, nil
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||
return &model.ToolResult{ID: "", Error: fmt.Sprintf("JSON解析失败: %v", err)}, nil
|
||||
}
|
||||
|
||||
value, err := queryPath(data, path)
|
||||
if err != nil {
|
||||
return &model.ToolResult{ID: "", Error: err.Error()}, nil
|
||||
}
|
||||
|
||||
pretty, err := json.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
return &model.ToolResult{
|
||||
ID: "",
|
||||
Output: fmt.Sprintf("路径: %s\n值: %v", path, value),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &model.ToolResult{
|
||||
ID: "",
|
||||
Output: fmt.Sprintf("路径: %s\n值:\n%s", path, string(pretty)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *JSONTool) handleValidate(jsonStr string) (*model.ToolResult, error) {
|
||||
var data interface{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||
errStr := err.Error()
|
||||
return &model.ToolResult{
|
||||
ID: "",
|
||||
Output: fmt.Sprintf("❌ JSON不合法\n错误: %s", errStr),
|
||||
}, nil
|
||||
}
|
||||
|
||||
typeName := "object"
|
||||
switch data.(type) {
|
||||
case []interface{}:
|
||||
typeName = "array"
|
||||
case string:
|
||||
typeName = "string"
|
||||
case float64:
|
||||
typeName = "number"
|
||||
case bool:
|
||||
typeName = "boolean"
|
||||
case nil:
|
||||
typeName = "null"
|
||||
}
|
||||
|
||||
size := len(jsonStr)
|
||||
return &model.ToolResult{
|
||||
ID: "",
|
||||
Output: fmt.Sprintf("✅ JSON合法\n类型: %s\n大小: %d bytes", typeName, size),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func queryPath(data interface{}, path string) (interface{}, error) {
|
||||
path = strings.TrimPrefix(path, "$.")
|
||||
if path == "" || path == "$" {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(path, ".")
|
||||
current := data
|
||||
|
||||
for _, part := range parts {
|
||||
switch v := current.(type) {
|
||||
case map[string]interface{}:
|
||||
var ok bool
|
||||
current, ok = v[part]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("路径 '%s' 中字段 '%s' 不存在", path, part)
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
idx, err := strconv.Atoi(part)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("路径 '%s' 中 '%s' 不是有效的数组索引", path, part)
|
||||
}
|
||||
if idx < 0 || idx >= len(v) {
|
||||
return nil, fmt.Errorf("路径 '%s' 中索引 %d 越界(数组长度 %d)", path, idx, len(v))
|
||||
}
|
||||
current = v[idx]
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("路径 '%s' 中无法继续导航:'%s' 不是对象或数组", path, part)
|
||||
}
|
||||
}
|
||||
|
||||
return current, nil
|
||||
}
|
||||
Reference in New Issue
Block a user