@migor/cli 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +134 -1
- package/dist/AGENTS.md +46 -8
- package/dist/templates.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,137 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.6.3] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
> 本版主题:把第六轮 16 条的共同根因(「约定写在文档里、但没有门禁」)收口 ——
|
|
11
|
+
> 守卫注册表 + 三个新架构守卫 + 适配器对拍矩阵;`exactOptionalPropertyTypes` 全量迁移;
|
|
12
|
+
> OpenAI 适配器补齐客户端内层重试(与 anthropic 对称)。**无破坏性变更**。
|
|
13
|
+
|
|
14
|
+
### 新增(守卫基建 + 适配器对齐)
|
|
15
|
+
|
|
16
|
+
- **守卫注册表 `docs/guards.md`**:「哪类危险由谁守」的单源清单(§1 已挂守卫 → 保护的
|
|
17
|
+
不变量 → 退化后果;§2 待守缺口;§3 写法纪律),配套 PR 模板的「危险类自查 5 问」。
|
|
18
|
+
- **两个新架构守卫**:`tests/architecture/transport-errors.test.ts`(`integrations` 的
|
|
19
|
+
传输抛错必须带数值 `status`,否则 `classifyError` 判 unknown、重试层静默失效 ——
|
|
20
|
+
上线当天即抓到 `otlp.ts` 的同形漏网)与 `tests/architecture/tsconfig-strictness.test.ts`
|
|
21
|
+
(`exactOptionalPropertyTypes` / `strict` / `types:["node"]` 三个承重开关不得被关)。
|
|
22
|
+
另有元守卫 `tests/docs/guards-registry.test.ts` 防注册表本身腐化。
|
|
23
|
+
- **`OpenAIClientOptions.maxRetries`(缺省 2)**:OpenAI 适配器补齐**客户端内层重试**
|
|
24
|
+
(408/409/429/5xx + `retry-after` 尊重,与 `createAnthropicClient` 逐字对齐)——
|
|
25
|
+
此前同一个 429 在 anthropic 打 3 次网络请求、在 openai 只打 1 次(引擎层那一次)。
|
|
26
|
+
对称性由 `tests/integrations/adapter-parity.test.ts` 守住(一份场景表跑两侧 + 跨侧
|
|
27
|
+
对称断言)。
|
|
28
|
+
- **`exactOptionalPropertyTypes` 开启并完成迁移**(39 处 `error TS` 全清):结果/状态
|
|
29
|
+
记录改必填 `T | undefined`、内部管道 `?: T | undefined`、**公共入参签名不动**(调用点
|
|
30
|
+
条件展开或 `omitUndefined`)。「显式 undefined ≠ 不传」从此是类型级约束 ——
|
|
31
|
+
`retry: { maxAttempts: undefined }` 静默关重试这类写法在编译期就写不出来。
|
|
32
|
+
|
|
33
|
+
### 修复
|
|
34
|
+
|
|
35
|
+
- **`createOtlpExporter` 非 2xx 改抛 `OtlpExportError`**(带数值 `status`):此前裸
|
|
36
|
+
`Error` 会被 `classifyError` 判 `unknown` + 不可重试(与 OpenAI 适配器同形的病,
|
|
37
|
+
由新守卫抓到)。
|
|
38
|
+
|
|
39
|
+
### 重构
|
|
40
|
+
|
|
41
|
+
- 两条适配器的 client 层退避(`backoffMs` / `interruptibleSleep`)单源收进
|
|
42
|
+
`core/timeout.ts` —— 此前曾短暂存在 anthropic / openai 两份逐字副本;「不与引擎层
|
|
43
|
+
`backoffDelay` 合并」的例外仍在(±25%+retry-after vs ±20%,策略不同)。
|
|
44
|
+
|
|
45
|
+
## [0.6.2] - 2026-09-18
|
|
46
|
+
|
|
47
|
+
> 本版主题:第六轮全量 review 收口 —— 16 条「不报错地不干活」修复(含两处**语义变更**,见下)、
|
|
48
|
+
> eval 分数进指标的 `beforeFlush` 时序缝、metrics 三维度基数封顶、trace 跨进程入站关联。
|
|
49
|
+
> **无破坏性变更**:两处语义变更(OpenAI 流式截断判据、MCP 桥对显式 `toolTimeoutMs: 0` 的裁判)
|
|
50
|
+
> 都是「此前错误的行为被改正」,正常用法不受影响。
|
|
51
|
+
|
|
52
|
+
### 修复(第六轮全量 review:「不报错地不干活」一次收口)
|
|
53
|
+
|
|
54
|
+
- **OpenAI 兼容端点的引擎重试此前整体失效**:非 2xx 抛的是裸 `Error`(无结构化 `status`),
|
|
55
|
+
`classifyError` 一律归 `unknown + 不可重试` —— 兼容端点吃一个 429 就整轮 run 失败。
|
|
56
|
+
现在抛 `OpenAICompatApiError`(带数值 `status`,与 `AnthropicApiError` 同形;module 级导出,
|
|
57
|
+
不进公共面),429 → `rate_limit` 可重试;流内 `error` 分片(塞进 200 的流里的故障)按
|
|
58
|
+
`type`/`code` 反推 status(`rate_limit` / `insufficient_quota` / `too_many` → 429)。
|
|
59
|
+
- **OpenAI 流式截断不再被记成成功**(**语义变更**):终止判据从「累积为空」换成
|
|
60
|
+
「既无 `[DONE]` 也无 `finish_reason` ⇒ 上游故障」—— 此前截断发生在已吐出半句之后时,
|
|
61
|
+
半截输出被 `end_turn` 收尾上报。反向也对齐:正常终止但空的流若 `finish_reason=content_filter`
|
|
62
|
+
不再误抛,与非流式路径同样记 `refusal`。
|
|
63
|
+
- **Anthropic 适配器的 usage 合并不再被显式 `null` 清零**:网关型端点的 `message_delta`
|
|
64
|
+
带 `input_tokens: null` 时,浅合并会把 `message_start` 的真实计量抹掉,该回合 input/cache
|
|
65
|
+
token 与 `costEstimate` 归零(`maxCostUsd` 护栏随之失效)。现在 `mergeUsage` 跳过
|
|
66
|
+
`null`/`undefined` —— 缺值的语义是「保持已有值」,不是「清空已有值」。
|
|
67
|
+
- **`maxToolConcurrency` 的 (0,1) 小数不再静默丢弃全部工具**:`Math.floor(0.5) = 0` 曾意味着
|
|
68
|
+
零 worker、工具一次都不执行。现在正数一律至少 1 个 worker;run 根快照记**生效的整数**
|
|
69
|
+
(不限记 `'off'`,不再把 `NaN` / `-1` 写进 trace)。
|
|
70
|
+
- **`.env` 引号值 + 行内注释不再把字面引号写进值**:`A="sk-..." # prod` 曾被解析成含引号的
|
|
71
|
+
`"sk-..."`(每个请求 401,文件看上去完全正确)。引号判定改为「扫到闭合引号为止,其后只允许
|
|
72
|
+
空白或 `#` 注释」;未闭合 / 有残留回退未加引号分支,不猜。
|
|
73
|
+
- **`toolTimeoutMs` 现在透传给子 agent / skill 的子循环**:此前嵌套 run 里工具**永不超时**,
|
|
74
|
+
且 MCP 桥找不到引擎预算会另起 60s 兜底 —— 双计时器 + 双账本。
|
|
75
|
+
- **MCP 桥裁判判据改为 `!= null`**(**语义变更**):显式 `toolTimeoutMs: 0`(引擎表态「不限」)
|
|
76
|
+
时桥不再自作主张判 60s —— 说了不限就该不限。
|
|
77
|
+
- **异步任务的崩溃恢复不再可能重复派发**:`resumePending` 认领时**先落库再派发** —— 此前认领
|
|
78
|
+
只改内存,对 `list()` 返回反序列化新对象的 store(sqlite/redis),窗口内第二次扫描会把
|
|
79
|
+
同进程正在跑的任务再派发一遍(重复执行、重复副作用、重复花费)。
|
|
80
|
+
- **优雅停机不再可能永不返回**:`handler.drain({ timeoutMs })` 在 deadline 已过时直接返回
|
|
81
|
+
`false`,不再把「已到点」透传给 `0 = 不限` 的语义(SIGTERM 容器被强杀、在飞任务硬切)。
|
|
82
|
+
- **显式 `undefined` 不再覆盖重试缺省**:`{ maxAttempts: undefined }` 这类透传组装曾把重试
|
|
83
|
+
静默关闭(快照记 0,像用户主动关的)/ 让退避算出 `NaN`。现在显式 `undefined` 回落缺省。
|
|
84
|
+
- **静态 `@Prompt` 不再被同名实例方法静默撞掉**:静态扫描改为按**解析后的菜单名**去重,
|
|
85
|
+
实例↔静态真重名交给装配期抛「菜单能力重名」(对齐 spec §7「重名即抛」)。
|
|
86
|
+
- **`tool_use_no_blocks` 收尾现在带结构化 `error`**(`type:'agent_error'`)—— 此前该分支
|
|
87
|
+
`status:'failed'` 但 `result.error` 是 `undefined`,HTTP body / 任务记录里看不出为什么失败。
|
|
88
|
+
- **`POST /tasks` 的 store 落库故障不再回 400 + 内部原文**:新增 `TaskInputError`(module 级,
|
|
89
|
+
不进公共面)区分「调用方参数错」(400 + 原因)与「服务端故障」(500 + 走 `exposeErrors`
|
|
90
|
+
策略);读 body 期间开始停机的竞态回 503。
|
|
91
|
+
- **长上下文压缩不再切出孤儿 `tool_result`**:`compactMessages` 的切点校验换成「保留段工具
|
|
92
|
+
自洽」—— 非相邻工具对(tool_use 与 tool_result 中间隔着普通消息)此前会切出孤儿块、下一次
|
|
93
|
+
请求被 API 400;退无可退时照 `trimToolPairs` 先例整体放弃本次压缩。
|
|
94
|
+
|
|
95
|
+
### 新增(质量闭环收尾 + 指标基数封顶)
|
|
96
|
+
|
|
97
|
+
- **`beforeFlush(trace, result)`**(`RunAppOptions` / `ExecuteRunOptions`,可选):sinks 冲刷
|
|
98
|
+
**之前**的最后一笔 —— 「拿到 run 结果才判得出的结论」(典型是 `defineEval` 的 score)在这个
|
|
99
|
+
时点挂上,`metricsSink` 才聚合得到。**此前 eval 的 score 挂在冲刷之后,永远进不了
|
|
100
|
+
`agentia_score` 指标族**(usage-guide / roadmap 承诺的「eval → trace → 监控」链路是断的)。
|
|
101
|
+
宿主漏透传该字段时 `defineEval` 退回「跑完再断言」:断言照做,不误报全挂,只是分数进不了指标。
|
|
102
|
+
- **`metricsSink` 三个维度的键空间都封顶**:新增 `maxModels`(缺省 50)/ `maxScores`(缺省 200),
|
|
103
|
+
与 `maxCapabilities` 同口径 —— 超限键折叠进 `__other__`(**量不丢,只丢标签粒度**),
|
|
104
|
+
snapshot 新增 `droppedModels` / `droppedScores`(各自最多记账 1024 个不同键,满了以后是下界)。
|
|
105
|
+
此前 `models` / `scores` 无上限,与 usage-guide 承诺的内存上界不符。
|
|
106
|
+
|
|
107
|
+
### 重构(内部去重下沉 core,公共面不变)
|
|
108
|
+
|
|
109
|
+
- `sseLines` / `percentile` / `capabilityKindOf` / `textOf` / 可中断 `sleep` 各只剩一份
|
|
110
|
+
(`src/core/{sse,stats,trace,text,timeout}.ts`)—— `integrations` 只准依赖 `core`,core 是让
|
|
111
|
+
两处重复合一的唯一合法落点。`textOf(message, separator)` 三个调用点各传各的原值,行为零变化;
|
|
112
|
+
两份 backoff(引擎 ±20% 均匀抖动 vs client ±25% 且尊重 `retry-after`)**刻意不合并** ——
|
|
113
|
+
合并即改行为。
|
|
114
|
+
|
|
115
|
+
### 新增(trace 跨进程关联:`traceparent` → run 根 span links)
|
|
116
|
+
|
|
117
|
+
- **入站链路上下文**:`RunInvocationOptions.traceContext`(`{ traceId, spanId? }`)与 HTTP 请求头
|
|
118
|
+
`traceparent`(W3C)现在会记成 run 根 span 的一条 **`links`**(新类型 `SpanLink`),
|
|
119
|
+
`createOtlpExporter` 映射为 OTLP **span links** —— 于是「这条 run 是被谁触发的」在跨进程 / 跨服务
|
|
120
|
+
时也可查。**不改 `traceId == runId` 的 1:1 不变量**:run 仍是自洽的一棵新树,上游是被**链接**
|
|
121
|
+
而不是被**继承**成父 span(理由与取舍见 `docs/spec.md` §10 2026-09-17 ⑤)。
|
|
122
|
+
- 新增 `parseTraceparent(value)` 导出:把 `traceparent` 头解析成 `TraceContext`。
|
|
123
|
+
**非法 / 缺失 / 版本 `ff` / 全零 id / 位宽不符一律返回 `undefined`**(不抛)——
|
|
124
|
+
链路是观测行为,不该把业务请求打成 400。`createHttpHandler` 在 `POST /run` 与 `POST /tasks`
|
|
125
|
+
上自动用它;`POST /tasks` 的 body 里显式给的 `options.traceContext` 优先于该头。
|
|
126
|
+
- **异步宿主零改动即继承**:`traceContext` 随 `spec.options` 落进 `TaskRecord`,所以另一个进程
|
|
127
|
+
`resumePending` 续跑的那次 run 也带得上(队列消费者场景)。
|
|
128
|
+
- `TraceRecorder.addLink()` 记为公共能力;没记 link 的 span **没有 `links` 键**(不是空数组)。
|
|
129
|
+
- **已知边界(如实标注)**:只做**入站** —— 框架不生成出站 `traceparent`(运行中没有「当前 span」
|
|
130
|
+
可导出,硬造会给出假 spanId);link 只落 run 根,不自动跨进程传播(队列场景由调用方把
|
|
131
|
+
`traceContext` 传下去)。
|
|
132
|
+
|
|
133
|
+
### 文档
|
|
134
|
+
|
|
135
|
+
- `docs/usage-guide.md`:新增「跨进程关联」小节(含队列消费者配方与「只做入站」的边界)、
|
|
136
|
+
`app.run` 选项表补 `traceContext`、已知边界补一条;官网 API 页补 `TraceContext` / `SpanLink` /
|
|
137
|
+
`parseTraceparent` 三行并把 `Span.links` 写进签名。
|
|
138
|
+
|
|
8
139
|
## [0.6.1] - 2026-09-17
|
|
9
140
|
|
|
10
141
|
### 文档(对外文案不再暴露内部流程;使用说明按用途重排)
|
|
@@ -237,7 +368,9 @@
|
|
|
237
368
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
238
369
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
239
370
|
|
|
240
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.
|
|
371
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.3...HEAD
|
|
372
|
+
[0.6.3]: https://github.com/retrychx/agentia/releases/tag/v0.6.3
|
|
373
|
+
[0.6.2]: https://github.com/retrychx/agentia/releases/tag/v0.6.2
|
|
241
374
|
[0.6.1]: https://github.com/retrychx/agentia/releases/tag/v0.6.1
|
|
242
375
|
[0.6.0]: https://github.com/retrychx/agentia/releases/tag/v0.6.0
|
|
243
376
|
[0.5.0]: https://github.com/retrychx/agentia/releases/tag/v0.5.0
|
package/dist/AGENTS.md
CHANGED
|
@@ -187,6 +187,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
187
187
|
| `client` | 注入 `ModelClient`(换 OpenAI 兼容端点等) |
|
|
188
188
|
| `onText` | 文本增量回调(SSE/终端) |
|
|
189
189
|
| `signal` | `AbortSignal`:中止则在飞请求被取消,run 以 `stopReason='aborted'` 收尾(算失败) |
|
|
190
|
+
| `traceContext` | 入站链路上下文 `{ traceId, spanId? }`:触发本次 run 的上游 span 记成 run 根的一条 `links`(不改 `traceId == runId`)。HTTP 宿主认 `traceparent` 头,自动填 —— 见 §6「跨进程关联」 |
|
|
190
191
|
| `blackboard` | 预置黑板种子(配 `Blackboard` 声明合并有键补全) |
|
|
191
192
|
| `contextPolicy` | 单次覆盖上下文策略 |
|
|
192
193
|
| `retry` | 单次覆盖重试策略:`false` 关闭,或 `{ maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参(只重试「本次尝试尚未产出文本」的可重试失败) |
|
|
@@ -203,6 +204,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
203
204
|
| `maxEventChars` | trace 事件正文截断上限(字符):数字 = 入参/出参统一用该上限,`false` = **不截断**;缺省按类型收敛(入参/成功出参 2000、失败出参 1000)。**透传给子 agent/skill 的子循环** —— 同一棵调用树上口径一致。只影响**记账**,回给模型的 tool_result 永远完整 |
|
|
204
205
|
| `session` | 会话持久化 `{ store, id }`:run 前拼历史、成功收尾追加本轮(见 `SessionStore`) |
|
|
205
206
|
| `memory` | 跨 run 记忆 `{ store, keys }`:run 前水合进 blackboard(用户种子优先)、收尾写回;与 `session` 正交(见 `MemoryStore`) |
|
|
207
|
+
| `beforeFlush` | `(trace, result) => void \| Promise<void>`:**sinks 冲刷之前**的最后一笔(run 正常收尾后调一次,抛错被吞)。给「**拿到结果才判得出**的结论」用的缝 —— 典型是 `defineEval` 的 score:等 `app.run` 返回再 `attachScore`,`metricsSink` 早在导出那一刻聚完账,分数就永远进不了指标。读 trace 就够的判断不必用它,写进 sinks 里即可(见 §6 判官配方) |
|
|
206
208
|
|
|
207
209
|
返回 `AgentRunOutput`:`{ run, result }`。`result` 含 `trace` / `stopReason` / `finalText` / `iterations` / `error` / `typed`。
|
|
208
210
|
|
|
@@ -448,7 +450,7 @@ process.on('SIGTERM', async () => {
|
|
|
448
450
|
| `mapWithConcurrency` | 有界并发 map(结果保序);`maxToolConcurrency` 的底座,也可自用 |
|
|
449
451
|
|
|
450
452
|
- **取消**:`app.run(messages, { signal })` 传 `AbortSignal` —— 框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发 `signal`),run 以 `stopReason='aborted'` 收尾(算失败)。`createHttpHandler` 已内置「客户端断开即中止」;`AsyncRunner.runTimeoutMs` 到点同样是**真中止**(构造期校验:必须 ≥ 0 的**有限**数 —— NaN/Infinity 会被 `setTimeout` 钳到 1ms,等于每个任务立即超时,故直接抛错;要「不限」传 0 或不设)。
|
|
451
|
-
- **重试**:缺省自动重试可重试失败(429 / 5xx / 连接失败),指数退避 + 抖动。`retry: false` 关闭,或 `retry: { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参。**只在本次尝试尚未产出任何文本时重试**(已吐出的字无法撤回)。⚠️ 与底层 client
|
|
453
|
+
- **重试**:缺省自动重试可重试失败(429 / 5xx / 连接失败),指数退避 + 抖动。`retry: false` 关闭,或 `retry: { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参。**只在本次尝试尚未产出任何文本时重试**(已吐出的字无法撤回)。⚠️ 与底层 client 的**内置重试**叠加 —— **两条内置适配器口径一致**(`createAnthropicClient` / `createOpenAIClient` 都有 `maxRetries`,缺省 2,重试同一状态码集合 408/409/429/5xx)—— 建议二选一调(这里 `maxAttempts: 1`,或 `<适配器>({ maxRetries: 0 })`)。
|
|
452
454
|
- **流式**:`POST /run` 带 `Accept: text/event-stream` → SSE 逐帧下发(`text.delta` / `run.end` / `error`);不带该头仍回一元 JSON。
|
|
453
455
|
- **工具超时 / 并发闸门**:`toolTimeoutMs` 超时**不杀 run**(该条 tool_result 记 `is_error`,模型可换路);`maxToolConcurrency` 给同回合的并行工具设上限(默认全并行)。⚠️ 超时 = **放弃等待**,`AgentTool.run` 没有 signal 参数,**副作用可能已发生** —— 想真停的工具请自行读 `ToolRunContext.signal`。**超时判定只有一个裁判**:`toolTimeoutMs` 是唯一判据 —— 工具自带的超时(如 MCP 桥的 `timeoutMs`)在设了本项时**不参与**判定;反过来说,工具自判的超时(抛 `code='timeout'` 的错误)与引擎判的记**同一类账**(`errorKind='timeout'`),并同样回 `is_error`。
|
|
454
456
|
|
|
@@ -536,7 +538,8 @@ trace 出去之后能干什么:指标、调用树面板、调优报告、生
|
|
|
536
538
|
|---|---|
|
|
537
539
|
| `TraceSink` | `{ export(trace) }`,run 收尾(成功/失败)都投递,抛错被吞 |
|
|
538
540
|
| `registerDefaultTraceSink` | 注册全局默认 sink(构造期快照合并) |
|
|
539
|
-
| `TraceRecorder` | 内存 recorder(一次 run
|
|
541
|
+
| `TraceRecorder` | 内存 recorder(一次 run 一个);`addLink(spanId, { traceId, spanId? })` 记一条跨 trace 链路(见 §6「跨进程关联」) |
|
|
542
|
+
| `parseTraceparent` | 解析 W3C `traceparent` 头 → `{ traceId, spanId? }`;**非法 / 缺头一律返回 `undefined`**(不抛、不打 400)—— 结果直接交给 `traceContext` 选项,见 §6「跨进程关联」 |
|
|
540
543
|
| `createOtlpExporter` | OTLP/JSON 导出,零依赖;选项 `OtlpExporterOptions`:`endpoint` / `headers` / `serviceName` / `timeoutMs`(单次导出超时,缺省 10000,非正数 = 不限 —— 裸 fetch 无超时,collector 半开连接会让 run 收尾永久挂起;超时按导出失败处理,不击穿 run) |
|
|
541
544
|
| `metricsSink` | 指标累加器(Prometheus 文本 / OTLP metrics),满足 `TraceSink` 即接入 —— 见 §6「指标」 |
|
|
542
545
|
| `buildRunReport` | 从一条 trace 生成**调优报告**(能力/模型的耗时、token、成本、错误率排行)—— 见 §6「调优报告」 |
|
|
@@ -558,6 +561,36 @@ capability span 按 **attributes** 分(`subagent` / `skill`;span 的 `name`
|
|
|
558
561
|
> `examples/observability/`。**完整的示例**(四类能力 + 三种触发 + 鉴权 + 全观测栈)在 `examples/complete/`;
|
|
559
562
|
> 最小可交付示例(Dockerfile + compose)在 `examples/deploy/`。
|
|
560
563
|
|
|
564
|
+
#### 跨进程关联(这条 run 是谁触发的)
|
|
565
|
+
|
|
566
|
+
一次 run 仍是一条**自洽**的 trace(`traceId == runId` 的 1:1 不变量不变),但可以用一条 **link**
|
|
567
|
+
把它接回上游 —— 触发它的那个 span(网关请求 / 队列消息 / 另一个服务)记在 run 根 span 的 `links` 上,
|
|
568
|
+
OTLP 导出时就是标准的 **span links**,后端能把因果边画出来。
|
|
569
|
+
|
|
570
|
+
**入站两种给法**:
|
|
571
|
+
|
|
572
|
+
| 场景 | 写法 |
|
|
573
|
+
|---|---|
|
|
574
|
+
| HTTP 宿主(网关 / 消费者通过 HTTP 打过来) | 请求头 `traceparent: 00-<32位trace>-<16位span>-01`(W3C)—— `createHttpHandler` 自动解析,`POST /run` 与 `POST /tasks` 都认;**畸形头静默当作没有上游**,不会打 400 |
|
|
575
|
+
| 程序内直接调 `app.run` / `runner.submit` | `options.traceContext: { traceId, spanId? }` —— 也可写 `parseTraceparent(头值)` 自己解析 |
|
|
576
|
+
|
|
577
|
+
**队列消费者(Kafka / RabbitMQ / SQS)的形态**:拿消息键当 `idempotencyKey`,把消息里带的
|
|
578
|
+
`traceparent` 一并传进去 —— 它随 `spec.options` 落进 `TaskRecord`,所以**另一个进程
|
|
579
|
+
`resumePending` 接着跑的那次 run 也带得上**,关联不断链:
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
// 消费者回调里(不要 await run 跑完:位移提交点与 run 终态不是一个时刻,重复靠幂等键兜)
|
|
583
|
+
runner.submit(msg.value, {
|
|
584
|
+
idempotencyKey: msg.key,
|
|
585
|
+
options: { traceContext: parseTraceparent(msg.headers.traceparent) },
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
异步任务可以在 `POST /tasks` 的 body 里显式给 `options.traceContext`,它优先于 `traceparent` 头。
|
|
590
|
+
|
|
591
|
+
> **只做入站**:框架**不生成**出站 `traceparent`(运行中没有「当前 span」这个概念,硬造一个会是假的
|
|
592
|
+
> spanId,比不做更糟)。要从 run 往外传,用结果里的 `runId`(== `traceId`)拼你自己的头,见 §7 已知边界。
|
|
593
|
+
|
|
561
594
|
#### 指标(从 trace 派生)
|
|
562
595
|
|
|
563
596
|
| API | 说明 |
|
|
@@ -602,6 +635,8 @@ capability span 按 **attributes** 分(`subagent` / `skill`;span 的 `name`
|
|
|
602
635
|
| `prefix` | 指标名前缀,缺省 `agentia_` |
|
|
603
636
|
| `labelMode` | 能力标签粒度:`'capability'`(缺省,`tool:search` 这种)/ `'kind'`(只按类型,基数极小)/ `'none'`(不产出能力指标) |
|
|
604
637
|
| `maxCapabilities` | 能力标签基数上限(缺省 200):超出后新能力归入 `capability="__other__"`(防标签爆炸);非正数抛错 |
|
|
638
|
+
| `maxModels` | 模型维度基数上限(缺省 50):超出后新模型归入 `model="__other__"` ——`model` 是 per-run 可覆盖的,上游把版本号拼进模型 id 时键会无界增长;非正数抛错 |
|
|
639
|
+
| `maxScores` | 评分维度基数上限(缺省 200):评分键是 `name@source`,eval 名带时间戳时同样无界;超出的归入 `name="__other__"`;非正数抛错 |
|
|
605
640
|
| `buckets` | 直方图桶边界(毫秒,严格升序);缺省 `DEFAULT_BUCKETS` |
|
|
606
641
|
|
|
607
642
|
#### `MetricsSink`(`metricsSink()` 的返回值)
|
|
@@ -609,15 +644,16 @@ capability span 按 **attributes** 分(`subagent` / `skill`;span 的 `name`
|
|
|
609
644
|
| 成员 | 说明 |
|
|
610
645
|
|---|---|
|
|
611
646
|
| `export` | `TraceSink` 的实现(run 收尾投递)—— 也是接进 `sinks` 的形状 |
|
|
612
|
-
| `snapshot` | `{ runs, failed, latencyP50, latencyP95, tokens, costUsd, capabilities, models, scores, droppedCapabilities }` |
|
|
647
|
+
| `snapshot` | `{ runs, failed, latencyP50, latencyP95, tokens, costUsd, capabilities, models, scores, droppedCapabilities, droppedModels, droppedScores }` |
|
|
613
648
|
| `render` | Prometheus 文本(`/metrics` 直接回它) |
|
|
614
649
|
| `flush` | 主动导出一次(`export:'otlp'` 时有意义;prometheus 模式为空操作) |
|
|
615
650
|
| `stop` | 停掉定时导出(进程收尾 / 测试用) |
|
|
616
|
-
| `reset` |
|
|
651
|
+
| `reset` | 清空累计(含能力 / 模型 / 评分三个维度,以及各自的基数配额) |
|
|
617
652
|
|
|
618
653
|
- `tokens` 口径 = **四类之和**(input + output + cacheRead + cacheCreation),与 `BudgetGuard` 一致;分项在 `render()` 里以 label 给出,不会丢。
|
|
619
654
|
- 分位是**窗口内精确值**(最近 rank 法),只反映最近 `windowSize` 条样本;**直方图计数是累积的**(全历史),两者语义不同、各有各的用处。
|
|
620
|
-
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` ——
|
|
655
|
+
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` —— 三个维度都由基数上限封顶(`maxCapabilities` / `maxModels` / `maxScores`),长跑宿主不会被拖住。
|
|
656
|
+
- 超上限的键折叠进 `__other__`:**丢的只是标签粒度,量不丢** —— `__other__` 桶照常累加,`snapshot()` 里各维度的总数仍然对得上。被折叠的**不同**键数见 `droppedCapabilities` / `droppedModels` / `droppedScores`(各自最多记账 1024 个键,满了以后是下界)。
|
|
621
657
|
- `costUsd` 依赖模型在价格表内(不在表里时不计、并计入 `unpricedTurns` 与 `usage.unpriced` 事件);根 span 未收尾(如失败路径的半截 trace)的 run 不进延迟样本。
|
|
622
658
|
|
|
623
659
|
#### 调用树面板(`agentia dev` 的本地面板 / 官网 Playground)
|
|
@@ -1000,10 +1036,10 @@ const callable = {
|
|
|
1000
1036
|
| 会话只存对话轮次 | `SessionStore` 存「用户输入 + 最终回复」,run 内部的 tool 往返**不进历史**(要完整过程用 `traceToMessages`);且只有**跑成功**的轮次才回写 |
|
|
1001
1037
|
| 同 session 并发 run 要自行串行化 | `SessionStore` 是 **append-only**:并发写不互相覆盖、不丢数据,但**不保证角色交替** —— 两个并发 run 共用同一 sessionId 时,各自追加的轮次可能交错成「连续两条 user」,下一轮 load 出来撞角色交替校验(400)。同一 session 的并发 run 请调用方自行串行化(每 session 一把锁 / 一条队列) |
|
|
1002
1038
|
| OpenAI 适配器听端点的话 | 请求发 `stream:true`,但**按响应形态解析**:端点回 JSON 就退回一次性(没有打字机效果),回 `event-stream` 才逐 token |
|
|
1003
|
-
| OpenAI 流式的上游故障按失败处理 |
|
|
1039
|
+
| OpenAI 流式的上游故障按失败处理 | 三种形态都**抛错**按失败处理:流中 `error` 分片(上游把故障塞进 200 的流;按 `type`/`code` 反推 status,限流能被引擎重试认出);**未收到 `[DONE]` 也无 `finish_reason`**(流被上游/代理截断 —— 哪怕已吐出半句、有累积文本,也按不完整响应抛错,不报 `end_turn`);正常终止却无任何文本与工具调用(与非流式空 `choices` 同一守卫)。**例外**:`finish_reason=content_filter` 的空流是合法 refusal,不抛 —— 与非流式路径同一个响应同一个结论 |
|
|
1004
1040
|
| MCP 只做 tools | `sampling`(server 反向请求模型)/ `resources` / `prompts` 原语不做;连接器(stdio / HTTP)不在框架内 |
|
|
1005
1041
|
| MCP 的协议层错误框架看不见 | `isError: true` 只有连接器能看见 —— 它必须转成抛错,否则模型收到的是一条「成功」的结果 |
|
|
1006
|
-
| MCP 超时同样是「不等了」 | 桥的 `timeoutMs` 取消不了 server 侧执行(拿不到取消句柄);它只是**兜底** —— 引擎设了 `toolTimeoutMs`
|
|
1042
|
+
| MCP 超时同样是「不等了」 | 桥的 `timeoutMs` 取消不了 server 侧执行(拿不到取消句柄);它只是**兜底** —— 引擎设了 `toolTimeoutMs` 时**不参与**判定(一次调用只有一个裁判;**显式 `toolTimeoutMs: 0` 也算设了** —— 那是引擎表态「不限」,桥不会再自作主张判 60s),两条路径**同判定、同账**(`errorKind='timeout'`) |
|
|
1007
1043
|
| MCP 名字可能被归一化 | 原名含 `-` / `.` / 空格 → 进菜单时变成 `_`;回调 server 用的仍是原名(`mcp.tool.<菜单名>` attribute 逐次可查;`mcp.tool` 是最近一次) |
|
|
1008
1044
|
| MCP 工具不能进 DI 容器 | 它没有 provider token,也不能被别的能力的 `tools` 引用(两种引用粒度都要先有 token) |
|
|
1009
1045
|
| 指标分位是窗口内精确值 | `*_last{quantile=...}` 只反映最近 `windowSize`(缺省 1024)条样本;要跨实例聚合请用直方图(`*_bucket` / `_sum` / `_count`,累积语义) |
|
|
@@ -1013,10 +1049,12 @@ const callable = {
|
|
|
1013
1049
|
| 工具没有 token/成本指标 | 工具是**你的代码**、本身不消耗 token,所以只产出调用数/失败数/耗时;token 与成本只对 `skill`/`subagent`(有 `capability` span)与模型维度产出 |
|
|
1014
1050
|
| `@Prompt` 没有能力指标 | 资产类能力不建 span、无独立耗时,故不出现在能力排行里(这是刻意的:硬凑一个假耗时会误导调优) |
|
|
1015
1051
|
| 能力标签有基数上限 | `labelMode:'capability'`(缺省)+ `maxCapabilities`(缺省 200),超出的能力归入 `capability="__other__"`;`snapshot().droppedCapabilities` 给出被归并的能力个数。要完整明细请用 `buildRunReport`(不设上限) |
|
|
1052
|
+
| 模型 / 评分维度也有基数上限 | `maxModels`(缺省 50)/ `maxScores`(缺省 200):`model` 与评分键(`name@source`)都可能是无界键(上游把版本号拼进模型 id、eval 名带时间戳),而每个模型键都持一份时长窗口 —— 光是能力封顶不够。折叠只丢标签粒度,`__other__` 桶照常累加,总数仍对得上;被折叠的不同键数见 `droppedModels` / `droppedScores` |
|
|
1016
1053
|
| 提示词版本只是标记 | 框架不存版本库、不回滚:`version` 只落 run 根 attribute;`system` 传已拼好的 `SystemParam` 时无版本可记 |
|
|
1017
1054
|
| `agentia harvest` 的产物是轨迹骨架 | trace **不记 assistant 文本**(llm.turn 只记 usage/事件),故 harvest 用例脚本里的 text 块是占位、预填 `expect` 是从原 trace 抄录的实际轨迹 —— 脚手架不是成品,人工核对后再进 CI(见 §6「线上 trace 回流」) |
|
|
1018
1055
|
| 分叉重放不是续跑 | `forkMessages` 与 `traceToMessages` / harvest **同源有损**:trace 不记 assistant 文本与 run 原始输入(重放里 assistant 是标注占位、首尾 user 是合成),也不记 blackboard(分叉种子经 `RunInvocationOptions.blackboard` 自带);它产出喂回 `app.run` 的 messages、起的是**新 run**,不是接着原 run 的循环位置跑 |
|
|
1019
1056
|
| 评分来自 run 之外 | `Score` 走 run 根 `score` **事件**而非 span 字段(评分通常在 run 跑完后才产生);`attachScore` 找不到根 span 时静默忽略,多次调用即多条事件(不同维度各记各的) |
|
|
1057
|
+
| 链路关联只做**入站** | `traceContext` / `traceparent` 头只把**上游**接进来(run 根的 `links`);框架**不生成**出站 `traceparent` —— 运行中没有「当前 span」可导出,硬造会给出假 spanId。要从 run 往外传,用 `result.trace.traceId`(== `runId`)自行拼头。另:link 只落在 run 根(子 span 不散),且**一进程内**不跨进程自动传播 —— 队列场景要自己把 `traceContext` 传下去(HTTP 头带走,或随 `TaskRecord.spec.options` 落库) |
|
|
1020
1058
|
| 配额不是框架子系统 | 只给缝(middleware + TraceSink + BudgetGuard),计数放哪(内存 / Redis / DB)与超限怎么办都是你的策略 |
|
|
1021
1059
|
| 人工介入只到「闸门」 | `middleware` 能 `await` 审批决策再放行;**跨进程挂起/续跑框架不做** —— `RunStatus` 无「待批准」态、循环位置不落库,`traceToMessages` 重放有损,不能拿它假装续跑(要跨重启审批请上工作流引擎)|
|
|
1022
1060
|
| 内容护栏不给实现 | 同「配额」:只给缝(入参包 `app.run` / 工具前 `middleware` / 出参包返回值或 `sinks`),策略(正则 / 分类器 / 外部 API)是你的 |
|
|
@@ -1048,5 +1086,5 @@ const callable = {
|
|
|
1048
1086
|
| MCP 调用「成功」但内容是错误文本 | 连接器没把协议层 `isError: true` 转成抛错(框架只认抛错) |
|
|
1049
1087
|
| eval 里模型调了不存在的工具 | 脚本里的工具名必须是**菜单里的名字**(MCP 工具是归一化后的 `mcp_<server>_<name>`) |
|
|
1050
1088
|
| `metricsSink` 的数字一直是 0 | 没接进 `createApp({ sinks })`(或 `registerDefaultTraceSink`)—— 它靠 run 收尾投递,不自己埋点 |
|
|
1051
|
-
| `metricsSink({ export: 'otlp' })` 构造期报错 | 没给 `endpoint` —— OTLP 导出必须知道往哪发,响亮失败好过静默不导出;补上 `endpoint`(如 `http://localhost:4318`)即可。`windowSize` / `maxCapabilities` 非正数、`buckets` 非严格升序同理是构造期配置校验 |
|
|
1089
|
+
| `metricsSink({ export: 'otlp' })` 构造期报错 | 没给 `endpoint` —— OTLP 导出必须知道往哪发,响亮失败好过静默不导出;补上 `endpoint`(如 `http://localhost:4318`)即可。`windowSize` / `maxCapabilities` / `maxModels` / `maxScores` 非正数、`buckets` 非严格升序同理是构造期配置校验 |
|
|
1052
1090
|
|
package/dist/templates.js
CHANGED
|
@@ -62,7 +62,7 @@ export function projectPackageJson(name) {
|
|
|
62
62
|
start: 'node dist/main.js',
|
|
63
63
|
typecheck: 'tsc --noEmit -p tsconfig.json',
|
|
64
64
|
},
|
|
65
|
-
dependencies: { '@migor/agentia': '^0.6.
|
|
65
|
+
dependencies: { '@migor/agentia': '^0.6.3' },
|
|
66
66
|
devDependencies: {
|
|
67
67
|
tsx: '^4.19.0',
|
|
68
68
|
typescript: '^7.0.2',
|