caa9bfced5
- 17个工具完整列表(共享插件 + AI-Core专属) - 注册机制 + ToolExecutor接口 - 对话中完整调用流程(含LLM API请求/响应示例) - 工具调用循环(最多5轮) - 后台思考白名单机制 - 超时异步处理 Co-Authored-By: Claude <noreply@anthropic.com>
184 lines
5.4 KiB
Markdown
184 lines
5.4 KiB
Markdown
# Cyrene 工具调用系统
|
||
|
||
> **日期**:2026-06-23
|
||
> **适用范围**:对话 + 后台自主思考
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
昔涟可以调用 16 个工具来完成任务——从搜索互联网、控制智能家居,到创建提醒、检索知识库。
|
||
|
||
工具调用的核心机制是 **OpenAI Function Calling 协议**:每次 LLM 请求时,将所有可用工具的名称、描述、参数格式一并发送。LLM 自行判断是否需要调用工具,返回工具名和参数,由服务端执行后把结果送回 LLM 生成最终回复。
|
||
|
||
---
|
||
|
||
## 注册机制
|
||
|
||
### 启动时注册
|
||
|
||
`ai-core/cmd/main.go` 在服务启动时将全部工具注册到 `ToolRegistry`:
|
||
|
||
```
|
||
共享插件 (pkg/plugins)
|
||
calculator 数学表达式求值
|
||
datetime 日期时间计算/格式化
|
||
text 文本处理(统计/截断)
|
||
crypto 哈希/加解密
|
||
random 随机数/字符串
|
||
markdown Markdown 渲染
|
||
json JSON 解析/查询
|
||
file 文件读写/列表
|
||
http HTTP 请求
|
||
web_search SearXNG 网络搜索
|
||
web_fetch 网页内容提取
|
||
|
||
AI-Core 专属 (internal/tools)
|
||
iot_query IoT 设备状态查询
|
||
iot_control IoT 设备操控
|
||
knowledge_search 知识库 RAG 检索
|
||
knowledge_ingest 知识库文档导入
|
||
reminder_create 创建提醒
|
||
reminder_list 列出提醒
|
||
reminder_delete 删除提醒
|
||
```
|
||
|
||
### 工具定义
|
||
|
||
每个工具实现 `ToolExecutor` 接口:
|
||
|
||
```go
|
||
type ToolExecutor interface {
|
||
Execute(ctx context.Context, args map[string]interface{}) (*ToolResult, error)
|
||
Definition() ToolDefinition
|
||
}
|
||
|
||
type ToolDefinition struct {
|
||
Name string // 工具名,LLM 用它来选择
|
||
Description string // 工具用途描述,LLM 据此判断是否需要调用
|
||
Parameters map[string]interface{} // JSON Schema 参数定义
|
||
}
|
||
```
|
||
|
||
`Description` 就是 LLM 的"说明书"——它通过阅读描述来判断当前对话是否需要调用这个工具。
|
||
|
||
---
|
||
|
||
## 对话中的调用流程
|
||
|
||
```
|
||
用户: "帮我查一下今天天气"
|
||
|
||
Synthesizer.Synthesize()
|
||
│
|
||
├─ 1. buildOpenAITools()
|
||
│ 从 ToolRegistry 取出全部工具的定义
|
||
│ 转换为 OpenAI Function Calling 格式
|
||
│
|
||
├─ 2. ChatWithTools(messages, tools)
|
||
│ POST LLM API:
|
||
│ {
|
||
│ "model": "deepseek-v4-flash",
|
||
│ "messages": [
|
||
│ {"role":"system","content":"你是昔涟..."},
|
||
│ {"role":"user","content":"帮我查一下今天天气"}
|
||
│ ],
|
||
│ "tools": [
|
||
│ {
|
||
│ "type": "function",
|
||
│ "function": {
|
||
│ "name": "web_search",
|
||
│ "description": "搜索互联网获取实时信息...",
|
||
│ "parameters": {
|
||
│ "type": "object",
|
||
│ "properties": {
|
||
│ "query": {"type": "string", "description": "搜索关键词"}
|
||
│ },
|
||
│ "required": ["query"]
|
||
│ }
|
||
│ }
|
||
│ },
|
||
│ // ... 其余 15 个工具
|
||
│ ],
|
||
│ "tool_choice": "auto"
|
||
│ }
|
||
│
|
||
│ LLM 判断: 用户要查天气 → 需要 web_search
|
||
│ 返回:
|
||
│ {
|
||
│ "choices": [{
|
||
│ "delta": {
|
||
│ "tool_calls": [{
|
||
│ "id": "call_abc123",
|
||
│ "function": {
|
||
│ "name": "web_search",
|
||
│ "arguments": "{\"query\":\"今天天气\"}"
|
||
│ }
|
||
│ }]
|
||
│ }
|
||
│ }]
|
||
│ }
|
||
│
|
||
├─ 3. ToolRegistry.Execute("web_search", {query: "今天天气"})
|
||
│ → 发送 HTTP 请求到 SearXNG
|
||
│ → 返回: {success: true, data: "晴转多云 22°C..."}
|
||
│
|
||
├─ 4. 工具结果追加到对话历史
|
||
│ messages += {
|
||
│ role: "tool",
|
||
│ tool_call_id: "call_abc123",
|
||
│ content: "{\"success\":true,\"data\":\"晴转多云 22°C...\"}"
|
||
│ }
|
||
│
|
||
└─ 5. 再次 ChatWithTools
|
||
LLM 读取工具结果 → 生成最终回复:
|
||
"今天晴转多云,22°C♪ 很适合出门走走呢~"
|
||
```
|
||
|
||
---
|
||
|
||
## 工具调用循环
|
||
|
||
LLM 可以在一轮对话中**多次调用工具**,最多 **5 轮**:
|
||
|
||
```
|
||
第 1 轮: LLM → web_search("今天天气")
|
||
第 2 轮: LLM → web_fetch("https://weather.com/detail")
|
||
第 3 轮: LLM 综合结果 → 生成回复
|
||
```
|
||
|
||
每轮 LLM 都可以选择继续调工具或直接回复。达到 5 轮上限后强制生成回复。
|
||
|
||
---
|
||
|
||
## 后台思考中的工具调用
|
||
|
||
后台思考器(Thinker)使用相同的工具注册中心,但通过 **`AutonomousToolPolicy`** 白名单控制:
|
||
|
||
```go
|
||
AllowedTools: []string{
|
||
"iot_query", "iot_control", "memory_search", "web_search",
|
||
"calculator", "datetime", "web_fetch",
|
||
"reminder_create", "reminder_list", "reminder_delete",
|
||
...
|
||
}
|
||
```
|
||
|
||
思考循环中只有白名单内的工具可被调用,防止自主思考执行高风险操作。
|
||
|
||
---
|
||
|
||
## 工具超时与异步
|
||
|
||
每个工具调用有 **8 秒超时**。超时的工具转入后台异步执行,结果在下一轮对话中返回。LLM 会收到提示:"该工具正在后台运行,你可以继续聊天"。
|
||
|
||
---
|
||
|
||
## 如何添加新工具
|
||
|
||
1. 实现 `ToolExecutor` 接口(`Definition()` + `Execute()`)
|
||
2. 在 `main.go` 中 `toolRegistry.Register(wrapTool(...))`
|
||
3. 如需在思考中可用,加入 `AutonomousToolPolicy.AllowedTools`
|
||
|
||
工具注册后,LLM 会在下次请求时自动感知到新工具——无需修改 prompt。
|