mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-30 06:13:14 +00:00
`ParallelSafe` / `Serial` / `stream_index` 三个新能力此前**零文档**: README 提到 `Serial` 的那处是 UART 串口,与并发声明无关。插件作者只能 读源码注释才能知道这些字段存在及其优先级。 ## docs/guide/parallel-tool-declaration.md - 保守 opt-in 的理由:存量插件不改一行就得串行,不会被升级意外并发 - 声明 `ParallelSafe` 的三个条件(线程安全 / 不争抢资源 / 顺序无关) - `Serial` 存在的意义:让"我确认过**必须**串行"与"我没想过"可区分 - **`Serial` 胜出**,不允许被 `ParallelSafe` 或默认值覆盖 - 声明字段放在 `ToolDef` 末尾,遵循既有 `NoMemory` 风格 - 压测效果 N=2/4/8 → 1.41×/2.22×/3.88×,并附"必须同时统计实际执行数" 的理由(旧适配器耗时更短但实际处理 0 个工具) ## docs/guide/stream-tool-call-index.md 面向写 Lua 适配器的人: - 上游 `index` 字段的用途:分桶累积 `id`/`name`/`arguments` - **键名是 `stream_index` 不是 `index`** —— 写错会被 Go 解码器静默丢弃 - 不透传的实际后果:name 互相覆盖、args 碎片混拼、工具被当空参数调用 - 顺带记两个易踩点:不能按 name 过滤分片;扁平结构的协议族同样要带 - 自检命令;并注明 `gemini.lua` 不涉及(协议是 `functionCall`) ## 其它 - `mkdocs.yml` nav 登记两篇 —— 之前它们会被 mkdocs 明确警告 "not included in the nav configuration",等于在站点里不可达 - 重跑 `tools/apidoc/build.sh` 同步 `docs/api/*`、`llms.txt`(生成物) 核实过的事实,避免臆造: - 内置工具声明是**核心仓**的 `toolDefOptions`/`parallelOpts()` (`internal/agent/core/tooldefs.go`),不是 `sdk.BuiltinToolDef` —— 初稿写错 - `gemini.lua` 对 `functionCall|tool_calls` 匹配数为 0,确认无流式实现 - `server/kimicode/anthropic/ollama` 四个适配器确有 `stream_index`(2~5 处) - 跨仓相对链接不可解析,故改为纯文本路径指路 `docs/api/tools.md` 属生成物(首行注明"请勿手改"),其 `ToolDef` 签名被截断、 不展示字段 —— 这是生成器既有行为,本次 diff 只是行号漂移,未改它。
113 lines
4.1 KiB
Markdown
113 lines
4.1 KiB
Markdown
# 工具并发声明:`ParallelSafe` / `Serial`
|
||
|
||
> 对应 `sdk.ToolDef` 的两个字段。内核在**同一轮**收到多个 `tool_call` 时,
|
||
> 依据它们决定并发还是整批串行。
|
||
>
|
||
> 状态:已随 2026-09 的并行内核落地并在生产启用。
|
||
|
||
## 1. 为什么是"保守 opt-in"
|
||
|
||
**默认整批串行。** 只有当**批内每一个**工具都显式声明 `ParallelSafe: true`
|
||
时,那一批才并发;**只要有一个不声明,整批退回串行**。
|
||
|
||
这不是"漏了声明导致退化"的将就,而是刻意的设计:
|
||
|
||
- 存量插件**不改一行**就得到保守行为(整批串行),不会被升级意外并发
|
||
- 声明是**责任**而非特权 —— 声明者必须自己确认线程安全
|
||
- 宁可慢,不可错:一次错误的并发可能让两个工具抢同一个 SQLite 写、
|
||
同一台设备、或同一个输出通道
|
||
|
||
```go
|
||
// 同批全是安全工具 → 并发
|
||
tools: [a(ParallelSafe), b(ParallelSafe)] ⇒ 并发
|
||
|
||
// 只要有一个没声明 → 整批串行
|
||
tools: [a(ParallelSafe), b(默认)] ⇒ 串行
|
||
```
|
||
|
||
## 2. 三个条件都满足才可以声明 `ParallelSafe`
|
||
|
||
1. **handler 自身线程安全** —— 不持有跨调用的可变状态
|
||
2. **不与同批其它工具争抢同一资源** —— SQLite 写、设备、同一输出通道
|
||
3. **执行顺序无关** —— 顺序敏感的工具应留 `false`,由内核保序
|
||
|
||
第 3 条常被忽略:内核能保证**用户可见的消息**按声明顺序落盘,但**工具
|
||
之间的实际执行先后**在并发模式下不确定。有顺序依赖就留 `false`。
|
||
|
||
## 3. `Serial`:显式的反向标记
|
||
|
||
```go
|
||
Serial bool `json:"serial,omitempty"`
|
||
```
|
||
|
||
`ParallelSafe` 的零值 `false` 已经表达"串行",插件**无法区分**:
|
||
|
||
- "我没想过"
|
||
- "我确认过**必须**串行,且有原因"
|
||
|
||
一旦工具作者需要把"这里**故意**串行,是有原因的"写进代码(而不只是没填),
|
||
这个区分就是必需的 —— 否则只能靠命名约定传递意图。
|
||
|
||
适用场景:读操作但有隐含顺序约束(终端 `read`/`resize` 这类共享会话
|
||
状态);写操作虽已加锁但需要串行以获得可预测的交错顺序。
|
||
|
||
**优先级:`Serial` 胜出。** 即使同时写了 `ParallelSafe: true`,`Serial`
|
||
仍然生效 —— 显式声明"必须串行"不允许被 `ParallelSafe` 或任何默认值覆盖。
|
||
|
||
## 4. 写法
|
||
|
||
声明字段放在 `ToolDef` 结构体的**末尾**,遵循既有 `NoMemory` 的风格:
|
||
|
||
```go
|
||
sdk.RegisterTool(sdk.ToolDef{
|
||
Name: "my_readonly_query",
|
||
Description: "……",
|
||
Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
|
||
Handler: h.query,
|
||
ParallelSafe: true, // 声明在末尾
|
||
}, s)
|
||
```
|
||
|
||
需要"故意串行"时:
|
||
|
||
```go
|
||
sdk.RegisterTool(sdk.ToolDef{
|
||
Name: "my_terminal_input",
|
||
Handler: h.input,
|
||
Serial: true, // 胜出,忽略 ParallelSafe
|
||
}, s)
|
||
```
|
||
|
||
## 5. 内置工具
|
||
|
||
核心仓的内置工具用 `toolDefOptions` / `parallelOpts()` 声明
|
||
(`internal/agent/core/tooldefs.go`),最终由 `buildToolDefs` 汇总成
|
||
`ToolDef.ParallelSafe`。
|
||
|
||
内核只提供并行调度基础设施,**不硬编码任何工具名的安全状态表** ——
|
||
状态由每个工具在自己的声明结构里给出,查询时走聚合表。
|
||
|
||
## 6. 效果(实测)
|
||
|
||
同批 N 个各约 250ms 的工具,新版内核"内部并发"对"强制串行":
|
||
|
||
| N | 加速比 |
|
||
| --- | --- |
|
||
| 2 | ~1.41× |
|
||
| 4 | ~2.22× |
|
||
| 8 | ~3.88× |
|
||
|
||
批耗时几乎不随 N 增长,串行批严格线性。
|
||
|
||
> 压测时**必须同时记录实际执行的工具数**,不能只看耗时。
|
||
> 某次对照中旧版本耗时更短、但因适配器缺 `stream_index` 导致
|
||
> **实际处理 0 个工具** —— 那不是性能提升,是全失败。
|
||
|
||
## 相关
|
||
|
||
设计背景与踩坑见**核心仓**文档(不在本仓):
|
||
|
||
- `docs/zh/toolcall-contract-and-sequence-design.md` —— 契约与序列设计
|
||
- `docs/zh/toolcall-parallel-execution-plan.md` —— 阶段、实测压测数据
|
||
- `docs/zh/deploy-runbook.md` —— 生产部署(含适配器 `stream_index` 相关)
|