@migor/agentia 0.10.2 → 0.10.4
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 +969 -64
- package/LICENSE +1 -1
- package/README.en.md +93 -38
- package/README.md +105 -218
- package/dist/AGENTS.md +234 -41
- package/dist/core/limits.d.ts +33 -1
- package/dist/core/limits.js +37 -1
- package/dist/core/limits.js.map +1 -1
- package/dist/core/run.d.ts +2 -2
- package/dist/core/timeout.d.ts +5 -5
- package/dist/core/timeout.js +5 -5
- package/dist/core/tool.d.ts +2 -2
- package/dist/core/trace-sink.d.ts +8 -0
- package/dist/core/trace-sink.js +24 -0
- package/dist/core/trace-sink.js.map +1 -0
- package/dist/core/trace.d.ts +12 -2
- package/dist/core/trace.js +10 -1
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/concurrency.d.ts +1 -1
- package/dist/engine/concurrency.js +1 -1
- package/dist/engine/errors.d.ts +2 -2
- package/dist/engine/errors.js +2 -2
- package/dist/engine/loop-result.d.ts +3 -3
- package/dist/engine/loop.js +99 -56
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/mcp-server-http.js +1 -1
- package/dist/engine/mcp-server-http.js.map +1 -1
- package/dist/engine/mcp-server-stdio.js +7 -3
- package/dist/engine/mcp-server-stdio.js.map +1 -1
- package/dist/engine/mcp-server.d.ts +17 -2
- package/dist/engine/mcp-server.js +42 -17
- package/dist/engine/mcp-server.js.map +1 -1
- package/dist/engine/resume-input.d.ts +1 -1
- package/dist/engine/resume-input.js +1 -1
- package/dist/engine/retry.d.ts +15 -0
- package/dist/engine/retry.js +51 -2
- package/dist/engine/retry.js.map +1 -1
- package/dist/engine/span-scope.d.ts +15 -0
- package/dist/engine/span-scope.js +8 -1
- package/dist/engine/span-scope.js.map +1 -1
- package/dist/engine/spec.d.ts +5 -1
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/tracer.d.ts +8 -4
- package/dist/engine/tracer.js +24 -17
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/turn.d.ts +2 -2
- package/dist/engine/turn.js +19 -4
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +13 -5
- package/dist/engine/types.js +1 -0
- package/dist/engine/types.js.map +1 -1
- package/dist/eval/export.js +2 -2
- package/dist/eval/export.js.map +1 -1
- package/dist/eval/harvest.d.ts +4 -1
- package/dist/eval/harvest.js +32 -6
- package/dist/eval/harvest.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.d.ts +1 -1
- package/dist/integrations/anthropic.js +1 -1
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/file-sink.d.ts +0 -16
- package/dist/integrations/file-sink.js +24 -6
- package/dist/integrations/file-sink.js.map +1 -1
- package/dist/integrations/mcp-http.js +4 -3
- package/dist/integrations/mcp-http.js.map +1 -1
- package/dist/integrations/mcp-protocol.d.ts +35 -10
- package/dist/integrations/mcp-protocol.js +19 -1
- package/dist/integrations/mcp-protocol.js.map +1 -1
- package/dist/integrations/mcp-stdio.js +91 -50
- package/dist/integrations/mcp-stdio.js.map +1 -1
- package/dist/integrations/mcp.d.ts +1 -1
- package/dist/integrations/mcp.js +19 -3
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics-otlp.d.ts +1 -1
- package/dist/integrations/metrics-otlp.js +8 -2
- package/dist/integrations/metrics-otlp.js.map +1 -1
- package/dist/integrations/metrics-render.js +17 -6
- package/dist/integrations/metrics-render.js.map +1 -1
- package/dist/integrations/metrics-state.d.ts +2 -2
- package/dist/integrations/metrics-state.js +4 -4
- package/dist/integrations/metrics-state.js.map +1 -1
- package/dist/integrations/metrics.d.ts +2 -2
- package/dist/integrations/otlp.d.ts +29 -6
- package/dist/integrations/otlp.js +128 -7
- package/dist/integrations/otlp.js.map +1 -1
- package/dist/integrations/report.d.ts +14 -2
- package/dist/integrations/report.js +39 -6
- package/dist/integrations/report.js.map +1 -1
- package/dist/runtime/run.d.ts +1 -1
- package/dist/runtime/run.js +19 -17
- package/dist/runtime/run.js.map +1 -1
- package/dist/store/fsStore.js +9 -5
- package/dist/store/fsStore.js.map +1 -1
- package/dist/store/record.d.ts +2 -2
- package/dist/store/record.js +1 -1
- package/dist/store/sqliteStore.d.ts +2 -2
- package/dist/store/sqliteStore.js +3 -3
- package/dist/store/sqliteStore.js.map +1 -1
- package/dist/store/store.d.ts +27 -7
- package/dist/store/store.js +43 -5
- package/dist/store/store.js.map +1 -1
- package/dist/toolkit/collect.d.ts +11 -5
- package/dist/toolkit/collect.js +10 -4
- package/dist/toolkit/collect.js.map +1 -1
- package/dist/toolkit/discover.js +27 -1
- package/dist/toolkit/discover.js.map +1 -1
- package/dist/toolkit/module.js +183 -50
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/prompt.d.ts +1 -1
- package/dist/toolkit/prompt.js +2 -2
- package/dist/toolkit/prompt.js.map +1 -1
- package/dist/toolkit/subagent.d.ts +1 -1
- package/dist/toolkit/subagent.js +17 -3
- package/dist/toolkit/subagent.js.map +1 -1
- package/dist/transport/approval-policy.js +1 -1
- package/dist/transport/approval-policy.js.map +1 -1
- package/dist/transport/approval-supervisor.d.ts +8 -0
- package/dist/transport/approval-supervisor.js +23 -3
- package/dist/transport/approval-supervisor.js.map +1 -1
- package/dist/transport/async.d.ts +3 -0
- package/dist/transport/async.js +128 -25
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/http-endpoints.js +1 -1
- package/dist/transport/http-route.js +2 -2
- package/dist/transport/http-route.js.map +1 -1
- package/dist/transport/http-shapes.d.ts +1 -1
- package/dist/transport/http-shapes.js +1 -1
- package/dist/transport/resume-scanner.d.ts +7 -0
- package/dist/transport/resume-scanner.js +40 -9
- package/dist/transport/resume-scanner.js.map +1 -1
- package/dist/transport/run-call-options.js +7 -3
- package/dist/transport/run-call-options.js.map +1 -1
- package/dist/transport/scheduler.js +5 -0
- package/dist/transport/scheduler.js.map +1 -1
- package/dist/transport/serve.d.ts +92 -0
- package/dist/transport/serve.js +155 -0
- package/dist/transport/serve.js.map +1 -0
- package/dist/transport/signal-supervisor.d.ts +10 -2
- package/dist/transport/signal-supervisor.js +15 -2
- package/dist/transport/signal-supervisor.js.map +1 -1
- package/dist/transport/wake-policy.d.ts +1 -1
- package/package.json +23 -10
package/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](./README.md) · [English](./README.en.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/migoric/agentia/actions/workflows/ci.yml) [](https://www.npmjs.com/package/@migor/agentia) [](LICENSE)
|
|
6
|
+
|
|
7
|
+
**A declarative framework for building production agent services in TypeScript.** Declare four kinds of
|
|
8
|
+
capability with decorators + DI; a main agent orchestrates them; every run yields structured output and an
|
|
9
|
+
observable call tree (trace, cost, metrics).
|
|
6
10
|
|
|
7
11
|
面向应用开发的声明式 agent 服务开发框架:装饰器 + DI 声明四类能力,主 agent 编排执行;每次 run 产出结构化结果与可观测调用树(trace、成本、指标),交付可直接上线的服务。
|
|
8
12
|
|
|
9
|
-
**1 个包 · ≈2 MB · 0 运行时依赖 · 90+ 条守卫** ——
|
|
10
|
-
`npm i @migor/agentia` 只往 `node_modules` 放**一个**包,不装任何厂商 SDK。
|
|
11
|
-
数字由 `tests/docs/scoreboard.test.ts` 每次现场复算,**手写的读数会腐烂**(本仓的老病)。
|
|
13
|
+
**1 个包 · ≈2 MB · 0 运行时依赖 · 90+ 条守卫** —— 由 `tests/docs/scoreboard.test.ts` 每次现场复算。
|
|
12
14
|
|
|
13
15
|
- [安装与配置](#安装与配置)
|
|
14
16
|
- [快速开始](#快速开始)
|
|
@@ -25,62 +27,55 @@
|
|
|
25
27
|
## 安装与配置
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
npx @migor/cli create my-app
|
|
29
|
-
cd my-app && npm install
|
|
30
|
-
$EDITOR .env
|
|
31
|
-
npm run dev
|
|
32
|
-
# 可选:ANTHROPIC_BASE_URL(兼容端点)、AGENTIA_MODEL(缺省 claude-opus-5)
|
|
33
|
-
|
|
34
|
-
# 工程内直接用:npx agentia g tool fetch-weather / npx agentia doctor / npx agentia --version
|
|
35
|
-
# (脚手架把 @migor/cli 装进 devDependencies ⇒ 走本地 bin,离线可用、版本与工程一同 pin)
|
|
36
|
-
# 首次创建必须带 scope —— npm 上另有一个别人的 `agentia` 包,短名会装错东西。
|
|
37
|
-
# 想全局装(到处都能敲 agentia):npm i -g @migor/cli
|
|
30
|
+
npx @migor/cli create my-app --minimal # 最小模板:单文件 + 裸工具(含 .env / .env.example)
|
|
31
|
+
cd my-app && npm install
|
|
32
|
+
$EDITOR .env # 填 ANTHROPIC_API_KEY(也可直接 export,真实环境变量优先)
|
|
33
|
+
npm run dev -- "你的问题" # = tsx src/main.ts,跑一次并退出
|
|
38
34
|
```
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
> **运行时**:Node ≥ 18(`engines` 唯一要求)。CI 覆盖 **18 / 20 / 24** —— `import-floor` 矩阵跑
|
|
50
|
-
> `engines` 声明的**下限**,其余 job 跟**当前 LTS** 走。按 Node 设计并测试,
|
|
51
|
-
> **未对 Deno / edge 做验证**。唯一碰内置模块的 store 是 `SqliteTaskStore`(需 Node ≥ 22.5 的
|
|
52
|
-
> `node:sqlite`)—— 未提供时**构造期给可读报错**,不影响包本身被导入。
|
|
36
|
+
- **首次创建必须带 scope** —— npm 上另有一个别人的 `agentia` 包,短名会装错东西。
|
|
37
|
+
- 可选:`ANTHROPIC_BASE_URL`(兼容端点)、`AGENTIA_MODEL`(缺省 `claude-opus-5`)。
|
|
38
|
+
- 框架**不自动**读 `.env`:模板入口的 `loadEnvFile()` 负责把它读进 `process.env`。
|
|
39
|
+
- **运行时**:Node ≥ 18(`engines` 唯一要求),CI 覆盖 18 / 20 / 24。按 Node 设计并测试;
|
|
40
|
+
**Bun / Deno 已实测可跑**(四步判定 + 各宿主最小代码 + 实测版本见 [`docs/frameworks.md`](docs/frameworks.md),
|
|
41
|
+
门面上不写版本号 —— 会腐烂);**edge 仍未验证**。
|
|
42
|
+
`SqliteTaskStore` 需 Node ≥ 22.5 的 `node:sqlite`,未提供时构造期给可读报错。
|
|
43
|
+
- 工程内命令:`npx agentia g tool fetch-weather` / `npx agentia doctor` / `npx agentia --version`;
|
|
44
|
+
想全局装:`npm i -g @migor/cli`。
|
|
53
45
|
|
|
46
|
+
> **版本**:`0.10.4`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。
|
|
54
47
|
|
|
55
48
|
## 快速开始
|
|
56
49
|
|
|
57
|
-
两条路线,按需选一即可(也可混用)。
|
|
58
|
-
|
|
59
50
|
### 路线 A:CLI 脚手架
|
|
60
51
|
|
|
52
|
+
minimal 先跑通模型与裸工具,不要求先理解装饰器、DI 或 HTTP 宿主。需要 inspector、四类能力目录和
|
|
53
|
+
生产服务时,使用**不带 `--minimal` 的默认完整模板**(既有行为不变):
|
|
54
|
+
|
|
61
55
|
```bash
|
|
62
|
-
agentia create my-
|
|
63
|
-
cd my-
|
|
56
|
+
agentia create my-service
|
|
57
|
+
cd my-service && npm install
|
|
64
58
|
agentia g tool weather # 生成 src/tools/weather/index.ts 并登记 src/registry.ts
|
|
65
59
|
agentia g subagent doc-reviewer # 生成 src/subagents/doc-reviewer/{index.ts,system.md}
|
|
66
60
|
agentia g skill note-writer # 生成 src/skills/note-writer/index.ts
|
|
67
61
|
agentia g prompt style-guide # 生成 src/prompts/style-guide/{index.ts,asset.md}
|
|
68
|
-
agentia dev # 本地 inspector
|
|
62
|
+
agentia dev # 本地 inspector:输入 prompt 驱动一次 run + 看 trace
|
|
69
63
|
```
|
|
70
64
|
|
|
65
|
+
完整模板的 `src/main.ts` 是 HTTP 服务入口,`src/batch.ts` 是 `npm run start:batch` 的一次性入口。
|
|
66
|
+
|
|
71
67
|
**目录约定**:四分类目录,一能力一文件夹 —— `src/tools/` · `src/skills/` · `src/prompts/` · `src/subagents/`。
|
|
72
|
-
|
|
73
|
-
长文本放文件夹内 `.md
|
|
68
|
+
目录名就是类型;每个文件夹的 `index.ts` default export 一个 provider 类,DI token 缺省 = 文件夹名;
|
|
69
|
+
长文本放文件夹内 `.md`,用 `asset(import.meta.url, './system.md')` 读取。
|
|
74
70
|
|
|
75
71
|
### 路线 B:手写装配
|
|
76
72
|
|
|
77
|
-
不依赖 CLI
|
|
73
|
+
不依赖 CLI,直接声明能力并装配(与路线 A 可混用):
|
|
78
74
|
|
|
79
75
|
```ts
|
|
80
76
|
import { Tool, createApp, SystemPrompt, RunContext } from '@migor/agentia';
|
|
81
77
|
|
|
82
78
|
class WeatherTools {
|
|
83
|
-
// 工具名缺省取方法名(建议 snake_case);入参 = 模型按 schema 解析的结构化 input
|
|
84
79
|
@Tool({
|
|
85
80
|
description: '查询城市天气',
|
|
86
81
|
schema: {
|
|
@@ -92,9 +87,7 @@ class WeatherTools {
|
|
|
92
87
|
strict: true,
|
|
93
88
|
})
|
|
94
89
|
get_weather(input: { city: string }): string {
|
|
95
|
-
//
|
|
96
|
-
// 想让键有补全/校验:在一次 declare module 里合并 Blackboard(见 docs/usage-guide.md §5.1);
|
|
97
|
-
// 未声明时键为 string、值为 unknown,这里按需断言。
|
|
90
|
+
// 方法体内可读本次 run 的上下文(blackboard);键的类型补全见 usage-guide §5.1
|
|
98
91
|
const token = RunContext.current()?.get('authToken') as string | undefined;
|
|
99
92
|
return `city=${input.city};token=${token ?? 'none'}`;
|
|
100
93
|
}
|
|
@@ -108,16 +101,15 @@ const app = createApp({
|
|
|
108
101
|
|
|
109
102
|
const { run, result } = await app.run(
|
|
110
103
|
[{ role: 'user', content: '上海天气如何?' }],
|
|
111
|
-
{ blackboard: { authToken: 'sk-...' } },
|
|
104
|
+
{ blackboard: { authToken: 'sk-...' } },
|
|
112
105
|
);
|
|
113
106
|
console.log(result.finalText); // 最终文本
|
|
114
107
|
console.log(result.trace); // 调用树 + 每步 token/成本(traceId == runId)
|
|
115
108
|
```
|
|
116
109
|
|
|
117
|
-
|
|
110
|
+
也可以按目录扫描(与显式注册表混用):
|
|
118
111
|
|
|
119
112
|
```ts
|
|
120
|
-
// 按给定顺序扫各目录(返回 Promise);也可用 CLI 维护的 src/registry.ts 显式注册
|
|
121
113
|
const app = await createApp({
|
|
122
114
|
name: 'my-app',
|
|
123
115
|
discover: ['src/tools', 'src/skills', 'src/prompts', 'src/subagents'],
|
|
@@ -125,6 +117,17 @@ const app = await createApp({
|
|
|
125
117
|
});
|
|
126
118
|
```
|
|
127
119
|
|
|
120
|
+
### 路线 C:已有服务
|
|
121
|
+
|
|
122
|
+
不新建项目,也不重写现有服务。把已有函数包成 `@Tool`,在 `createServer` 里加**一条分支** ——
|
|
123
|
+
业务路由优先(行为不变),其余交给 `createHttpHandler`;鉴权复用同一个函数,`drain()` 接进已有的 SIGTERM。
|
|
124
|
+
|
|
125
|
+
六步、约 30 行胶水。可跑示例:[`examples/agent-endpoint/`](examples/agent-endpoint/) ——
|
|
126
|
+
一个已在跑的订单服务(`src/orders.ts` 一行未改),旁边加了 `/run` + `/tasks`。
|
|
127
|
+
|
|
128
|
+
> 三个刻意的决定:**一条鉴权路径**(框架给缝,不给策略);**RAG 是你的工具**(用 `@Tool` 包自己的
|
|
129
|
+
> 检索,框架不内置);**不设预算旋钮 = 没有保险丝**。
|
|
130
|
+
|
|
128
131
|
## 四类能力
|
|
129
132
|
|
|
130
133
|
主 agent 按 `description` 从同一张「能力菜单」自选;装饰器只决定**谁控制流程**:
|
|
@@ -136,62 +139,11 @@ const app = await createApp({
|
|
|
136
139
|
| 子 agent | `@SubAgent` | **模型自己**(独立循环 + 裁剪上下文) | 自主多步、且中间过程不该污染主上下文 |
|
|
137
140
|
| 提示资产 | `@Prompt` | 模型拉取(按需注入的文本) | 长文规范/模板,平时不进上下文 |
|
|
138
141
|
|
|
139
|
-
`@Tool`
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
import { Skill, SubAgent, Prompt, asset, type SkillContext } from '@migor/agentia';
|
|
143
|
-
|
|
144
|
-
// @Skill:代码控制的流程,模型调用只发生在显式 ctx.llm()
|
|
145
|
-
class Notes {
|
|
146
|
-
@Skill({ description: '按主题整理要点' })
|
|
147
|
-
async note_writer(input: { topic: string }, ctx: SkillContext): Promise<string> {
|
|
148
|
-
const r = await ctx.llm({ prompt: `就「${input.topic}」给出三个要点` });
|
|
149
|
-
return r.text; // 返回值即产物,以 tool_result 交回主 agent
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// @SubAgent:独立循环 + 裁剪上下文,只有最终报告回流主上下文(方法体不执行)
|
|
154
|
-
class Pipeline {
|
|
155
|
-
@SubAgent({
|
|
156
|
-
name: 'reviewer',
|
|
157
|
-
description: '审查给定文档并输出书面评审意见',
|
|
158
|
-
schema: { type: 'object', properties: { doc: { type: 'string' } }, required: ['doc'], additionalProperties: false },
|
|
159
|
-
system: '你是评审 agent。结论必须以“审查通过/不通过”开头。',
|
|
160
|
-
tools: ['weather'], // 子 agent 自己的工具菜单(provider token)
|
|
161
|
-
})
|
|
162
|
-
reviewer(_input: { doc: string }): void {}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// @Prompt:纯文本资产,模型判定需要时拉取进上下文
|
|
166
|
-
class Assets {
|
|
167
|
-
@Prompt({ description: '品牌基调文案规范' })
|
|
168
|
-
style_guide(): string {
|
|
169
|
-
return asset(import.meta.url, './asset.md'); // 从能力文件夹读 .md
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
四类一起装配(`providers` 里逐个登记;token 即子 agent / 技能引用工具的菜单名):
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
import { createApp, SystemPrompt } from '@migor/agentia';
|
|
178
|
-
|
|
179
|
-
const app = createApp({
|
|
180
|
-
name: 'weather-app',
|
|
181
|
-
providers: [
|
|
182
|
-
{ provide: 'weather', useClass: WeatherTools },
|
|
183
|
-
{ provide: 'notes', useClass: Notes },
|
|
184
|
-
{ provide: 'pipeline', useClass: Pipeline },
|
|
185
|
-
{ provide: 'assets', useClass: Assets },
|
|
186
|
-
],
|
|
187
|
-
system: new SystemPrompt().add('role', '你是天气助手。', true),
|
|
188
|
-
});
|
|
189
|
-
```
|
|
142
|
+
`@Tool` 见上方路线 B;其余三类的完整写法与 spec 字段见
|
|
143
|
+
[`docs/usage-guide.md` §3](docs/usage-guide.md)(四类可同时装配,token 即子 agent / 技能引用工具的菜单名)。
|
|
190
144
|
|
|
191
145
|
## 运行时特性
|
|
192
146
|
|
|
193
|
-
### 触发方式
|
|
194
|
-
|
|
195
147
|
同一份应用,三种触发任选:
|
|
196
148
|
|
|
197
149
|
```ts
|
|
@@ -210,95 +162,53 @@ const scheduler = new Scheduler(runner);
|
|
|
210
162
|
scheduler.every(60_000, '巡检一次', { idempotencyPrefix: 'patrol' });
|
|
211
163
|
```
|
|
212
164
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
import { createBudgetPolicy } from '@migor/agentia';
|
|
217
|
-
|
|
218
|
-
const app = createApp({
|
|
219
|
-
// ...
|
|
220
|
-
contextPolicy: createBudgetPolicy({
|
|
221
|
-
budgetTokens: 60_000, // 超预算先丢旧工具对,仍超且有 summarize 才压缩
|
|
222
|
-
summarize: (text) => mySummarizer(text), // 可选,框架不替你调模型
|
|
223
|
-
}),
|
|
224
|
-
});
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### 结构化结果
|
|
228
|
-
|
|
229
|
-
```ts
|
|
230
|
-
const { result } = await app.run(messages, {
|
|
231
|
-
resultSchema: {
|
|
232
|
-
type: 'object',
|
|
233
|
-
properties: { pass: { type: 'boolean' }, reason: { type: 'string' } },
|
|
234
|
-
required: ['pass', 'reason'],
|
|
235
|
-
additionalProperties: false,
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
console.log(result.typed); // 校验过的结构化结果,不再从文本里猜 JSON
|
|
239
|
-
// schema 也可来自 zod(peer 可选):fromZod(z.toJSONSchema(S), S)
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### 中间件
|
|
165
|
+
另外三处,按需查 [`docs/usage-guide.md`](docs/usage-guide.md):
|
|
243
166
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const app = createApp({
|
|
248
|
-
// ...
|
|
249
|
-
middleware: [
|
|
250
|
-
async (call, next) => {
|
|
251
|
-
if (call.capability.name === 'danger_op' && !RunContext.current()?.get('isAdmin')) {
|
|
252
|
-
return 'forbidden'; // 短路,不调 next 即拦截
|
|
253
|
-
}
|
|
254
|
-
return next(); // next(newInput) 还可改写入参
|
|
255
|
-
},
|
|
256
|
-
],
|
|
257
|
-
});
|
|
258
|
-
```
|
|
167
|
+
- **长上下文预算** `createBudgetPolicy({ budgetTokens, summarize })` —— 超预算先丢旧工具对,仍超且有 `summarize` 才压缩(§6.3)
|
|
168
|
+
- **结构化结果** `run(..., { resultSchema })` → `result.typed`,schema 也可来自 zod:`fromZod(z.toJSONSchema(S), S)`(§5.3)
|
|
169
|
+
- **中间件** `middleware: [(call, next) => ...]` —— 能力调用前后的洋葱链,鉴权/限流/缓存/审计都走这里(§6.6)
|
|
259
170
|
|
|
260
171
|
## 可观测性与成本
|
|
261
172
|
|
|
262
|
-
一次 run == 一条 trace(`traceId === runId`),**Turn 0
|
|
173
|
+
一次 run == 一条 trace(`traceId === runId`),**Turn 0 起内建**,不是外挂的第三方追踪 SDK:
|
|
263
174
|
|
|
264
175
|
```ts
|
|
265
|
-
const {
|
|
176
|
+
const { result } = await app.run(messages);
|
|
266
177
|
result.trace.spans; // 调用树:llm.turn / 能力 span / 事件
|
|
267
|
-
result.trace.totalUsage; // token 汇总(只累加 llm.turn
|
|
178
|
+
result.trace.totalUsage; // token 汇总(只累加 llm.turn)
|
|
268
179
|
```
|
|
269
180
|
|
|
270
181
|
- **每步记账**:span 属性带 model、input/output/cache tokens、成本估计、状态、错误类型
|
|
271
|
-
-
|
|
182
|
+
- **一条缝**:`TraceSink { export(trace) }` —— run 收尾(成功/失败)都投递,sink 抛错不影响 run;落库/采样/脱敏在缝外用 sink 组合(`docs/observability.md`)
|
|
272
183
|
- **指标**:`metricsSink` 满足 `TraceSink` 即可接入(Prometheus 文本 / OTLP metrics),零依赖
|
|
273
|
-
- **成本**:`priceOverrides`
|
|
274
|
-
- **调优闭环**:`buildRunReport` 出「能力 / 模型的耗时·token
|
|
275
|
-
- **回放**:`traceToMessages` 把已完成的 trace
|
|
276
|
-
- **本地开发**:`agentia dev` 的 inspector 面板与 `@migor/trace-view` 共用同一份渲染器(避免两处漂移)
|
|
277
|
-
- **导出 OTLP**:
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
import { createOtlpExporter } from '@migor/agentia';
|
|
281
|
-
|
|
282
|
-
await createOtlpExporter({ endpoint: 'http://localhost:4318' }).export(result.trace);
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
> 权威口径见 `docs/usage-guide.md` 的「观测」与「成本硬管控」两节。
|
|
184
|
+
- **成本**:`priceOverrides` 注入价目表;未定价模型发 `usage.unpriced` 事件;`createBudgetGuard` 做硬管控(超限以 `budget_exceeded` 收尾、算失败)
|
|
185
|
+
- **调优闭环**:`buildRunReport` 出「能力 / 模型的耗时·token·成本·错误率排行」,`agentia report <trace.jsonl>` 直接渲染
|
|
186
|
+
- **回放**:`traceToMessages` 把已完成的 trace 线性化喂回模型
|
|
286
187
|
|
|
287
188
|
## 集成
|
|
288
189
|
|
|
289
|
-
|
|
190
|
+
**HTTP 宿主**(`POST /run` 同步 / `POST /tasks` 异步 / `GET /tasks/:id` 轮询):
|
|
290
191
|
|
|
291
192
|
```ts
|
|
292
193
|
import { createServer } from 'node:http';
|
|
293
194
|
import { createHttpHandler } from '@migor/agentia';
|
|
294
195
|
|
|
295
|
-
// POST /run(同步) POST /tasks(异步) GET /tasks/:id(轮询)
|
|
296
196
|
createServer(createHttpHandler(app, { runner })).listen(8080);
|
|
297
197
|
```
|
|
298
198
|
|
|
299
|
-
|
|
199
|
+
**Web 框架宿主** —— `createHttpHandler` 返回的就是 `(req, res) => void`,任何能挂原始 node handler 的地方都能挂,不需要适配层:
|
|
300
200
|
|
|
301
|
-
|
|
201
|
+
| 宿主 | 最小接入 | 必须配的 |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `node:http` | `createServer(createHttpHandler(app, { runner }))` | 无 |
|
|
204
|
+
| NestJS 12(CJS / ESM) | `nestApp.use('/agent', handler)` 三行 | CJS 模式需 Node ≥ 22.12(或 `await import()`) |
|
|
205
|
+
| Egg 4 | 中间件 + `ctx.respond = false` | CSRF / bodyParser 两条 ignore(字符串写法会让 worker 启动即崩) |
|
|
206
|
+
| Bun / Deno | 与 `node:http` 相同,零改动 | Deno 的 `--allow-net/read/write/run` |
|
|
207
|
+
|
|
208
|
+
cluster / 多实例下 task store **必须共享** —— 每 worker 一份内存 store,同一个 taskId 轮询会 `404 200` 交替。
|
|
209
|
+
结论表、最小代码与 Egg 的三个坑见 [`docs/frameworks.md`](docs/frameworks.md)。
|
|
210
|
+
|
|
211
|
+
**MCP** —— 连接器(stdio / StreamableHTTP)随框架发布,只用标准库,不新增第三方依赖:
|
|
302
212
|
|
|
303
213
|
```ts
|
|
304
214
|
import { createApp, createStdioMcpConnector, createStreamableHttpMcpConnector, mcpTools } from '@migor/agentia';
|
|
@@ -316,9 +226,9 @@ const remote = createStreamableHttpMcpConnector('https://mcp.example/mcp', {
|
|
|
316
226
|
createApp({ system, providers: [...], tools });
|
|
317
227
|
```
|
|
318
228
|
|
|
319
|
-
`McpClientLike` 这条缝仍然在:接官方 SDK /
|
|
229
|
+
`McpClientLike` 这条缝仍然在:接官方 SDK / 自研传输时实现 `listTools()` + `callTool()` 两个方法即可(框架**不 import** MCP SDK)。
|
|
320
230
|
|
|
321
|
-
|
|
231
|
+
**多模型(OpenAI 兼容端点)**:
|
|
322
232
|
|
|
323
233
|
```ts
|
|
324
234
|
import { createOpenAIClient } from '@migor/agentia';
|
|
@@ -329,20 +239,7 @@ const { result } = await app.run(messages, {
|
|
|
329
239
|
});
|
|
330
240
|
```
|
|
331
241
|
|
|
332
|
-
|
|
333
|
-
> `createOpenAIClient({ baseURL: 'http://localhost:11434/v1' })` + `model: 'qwen3'`,
|
|
334
|
-
> **不需要任何新代码或新依赖**。
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
### 跨 run 记忆
|
|
338
|
-
|
|
339
|
-
```ts
|
|
340
|
-
import { InMemoryMemoryStore } from '@migor/agentia';
|
|
341
|
-
|
|
342
|
-
const { result } = await app.run(messages, {
|
|
343
|
-
memory: { store: new InMemoryMemoryStore(), keys: ['profile'] },
|
|
344
|
-
});
|
|
345
|
-
```
|
|
242
|
+
Ollama 等本地模型同样走这个适配器(`baseURL: 'http://localhost:11434/v1'`),不需要新代码或新依赖。
|
|
346
243
|
|
|
347
244
|
## API 速查
|
|
348
245
|
|
|
@@ -358,67 +255,58 @@ const { result } = await app.run(messages, {
|
|
|
358
255
|
| `AsyncRunner` / `Scheduler` / `runSync` / `createHttpHandler` | 异步 / 定时 / 同步 / HTTP 触发 |
|
|
359
256
|
| `FileTaskStore` / `SqliteTaskStore` / `RedisTaskStore` / `InMemoryTaskStore` | 任务记录存储 |
|
|
360
257
|
| `createBudgetPolicy` | 长上下文预算护栏 |
|
|
361
|
-
| `createAnthropicClient` | 默认 ModelClient(Anthropic):自定义只传 `apiKey` / `baseURL
|
|
258
|
+
| `createAnthropicClient` | 默认 ModelClient(Anthropic):自定义只传 `apiKey` / `baseURL` |
|
|
362
259
|
| `createOpenAIClient` | OpenAI 兼容端点适配(多模型) |
|
|
363
260
|
| `InMemoryMemoryStore`(`memory` 选项) | 跨 run 记忆水合/回写 |
|
|
364
261
|
| `createOtlpExporter` | trace 导出 OTLP |
|
|
365
|
-
| `traceToMessages` | trace
|
|
262
|
+
| `traceToMessages` | trace 重放基底 |
|
|
366
263
|
| `fromZod` | zod schema 接入(peer 可选) |
|
|
367
264
|
| `runAgent` / `executeRun` | 裸引擎入口(不走装配) |
|
|
368
265
|
|
|
369
266
|
## 稳定性与版本策略
|
|
370
267
|
|
|
371
268
|
**`0.x` 的含义**:版本号按 SemVer,但 `0.x` 期间 **minor 版本可以包含破坏性变更**。
|
|
372
|
-
规矩只有一条 —— **破坏性变更必须留痕**:`CHANGELOG.md`
|
|
269
|
+
规矩只有一条 —— **破坏性变更必须留痕**:`CHANGELOG.md` 对应版本里有一段专门写它
|
|
373
270
|
(小节标题含「迁移」或「破坏性变更」),写清「**你要改什么**」。
|
|
374
271
|
|
|
375
272
|
```bash
|
|
376
273
|
grep -nE '^#{3,4} .*(迁移|破坏性)' CHANGELOG.md # 列出所有「要你做动作」的小节
|
|
377
274
|
```
|
|
378
275
|
|
|
379
|
-
>
|
|
380
|
-
>
|
|
381
|
-
> **下限**而非全集,要读全就通读该版本的条目。
|
|
382
|
-
> **「N 个版本 / M 次迁移」这类读数本 README 刻意不写死** —— 写死的读数会腐烂(本仓为此吃过教训),
|
|
383
|
-
> 所以给命令、不给数字。
|
|
276
|
+
> 这条命令是**下限而非全集**:早期版本没按这个格式写,要读全就通读该版本条目。
|
|
277
|
+
> 版本数、迁移次数这类读数刻意不写死 —— 写死的读数会腐烂,所以给命令、不给数字。
|
|
384
278
|
|
|
385
279
|
**承诺的范围**:
|
|
386
280
|
|
|
387
281
|
| 面 | 承诺 | **不**承诺 |
|
|
388
282
|
|---|---|---|
|
|
389
|
-
| 公共 API | `@migor/agentia` 的**具名导出**(真源 `src/index.ts
|
|
283
|
+
| 公共 API | `@migor/agentia` 的**具名导出**(真源 `src/index.ts`)+ `@migor/cli` 的命令行面 | 深路径导入(`dist/**`)、`src/**` 内部模块、`packages/*`、`examples/*` —— 由 `package.json` 的 `exports` 封着 |
|
|
390
284
|
| 行为 | 该版本条目里写明的**迁移动作** | 没写进迁移说明的任何行为变化 |
|
|
391
|
-
| 运行环境 | `engines.node` 声明的区间;CI
|
|
392
|
-
| 依赖 | **运行时零第三方依赖**(`
|
|
393
|
-
|
|
394
|
-
**1.0 的门槛**(三条都是可核验的,不是口号):
|
|
285
|
+
| 运行环境 | `engines.node` 声明的区间;CI 在区间内每个版本跑完整条链 | edge(**未验证**);已 EOL 的 Node。Bun / Deno 已实测但**不在 `engines` 承诺内**([`docs/frameworks.md`](docs/frameworks.md)) |
|
|
286
|
+
| 依赖 | **运行时零第三方依赖**(`no-runtime-deps.test.ts` 守着,非口头承诺) | `devDependencies` 与 `examples/*` 的依赖面 |
|
|
395
287
|
|
|
396
|
-
1.
|
|
397
|
-
2. `docs/guards.md` §2「待守」为空 —— 已声明的边界都由机器守着,而不是靠人记得;
|
|
398
|
-
3. 公共导出面**连续 3 个 minor 无破坏性变更**。
|
|
288
|
+
**1.0 的门槛**(三条都可核验):
|
|
399
289
|
|
|
400
|
-
|
|
401
|
-
|
|
290
|
+
1. `docs/spec.md` §11「开放项」每一条要么落地、要么被明确划掉(写清为什么不阻塞);
|
|
291
|
+
2. `docs/guards.md` §2「待守」为空 —— 已声明的边界由机器守着,不靠人记得;
|
|
292
|
+
3. 公共导出面连续 3 个 minor 无破坏性变更。
|
|
402
293
|
|
|
403
294
|
## 性能量级
|
|
404
295
|
|
|
405
|
-
框架自带五个基准(`scripts/bench-*.ts
|
|
406
|
-
换实现能省掉的是什么),不是给你一个 SLA:
|
|
296
|
+
框架自带五个基准(`scripts/bench-*.ts`)。它们回答的是**形状**问题(哪笔钱在什么条件下付),不是 SLA:
|
|
407
297
|
|
|
408
298
|
| 你想问的 | 本机读数(macOS / Node 24,**量级**) | 形状结论(**不会随机器变**的那部分) | 复跑 |
|
|
409
299
|
| --- | --- | --- | --- |
|
|
410
|
-
| 装配一次 app 多贵 | 纯装配 `0.4 ms`;发现+装配 `8.1 ms`;整进程启动+装配 `4040 ms
|
|
411
|
-
| MCP 连接器什么时候付钱 | 冷(spawn + initialize + list)`279.5 ms
|
|
412
|
-
| trace 记不记内容,差多少 | 大出参(≈37 KB/次)5
|
|
413
|
-
| 指标快照 / OTLP flush
|
|
414
|
-
| 挂起任务的到期扫描 | sqlite N=10000:全表 `
|
|
415
|
-
| redis store 的 `list()` 形状 | N=10000:往返 **10001** 次、解析 **42.1 MB
|
|
416
|
-
|
|
417
|
-
> ⚠️
|
|
418
|
-
> 真正恒定的是**形状**(差多少倍、往返几次、哪一笔跟什么成正比)—— 那才是这五个基准要说的东西。
|
|
300
|
+
| 装配一次 app 多贵 | 纯装配 `0.4 ms`;发现+装配 `8.1 ms`;整进程启动+装配 `4040 ms` | 常驻 runner 里换一次能力选择只付 `S2`(≈8 ms),不需要付整进程启动(≈4 s)⇒ 差 **~500×** | `npm run bench:assembly` |
|
|
301
|
+
| MCP 连接器什么时候付钱 | 冷(spawn + initialize + list)`279.5 ms`;热(复用,仅 `tools/list`)`0.3 ms` | 只有**重建连接器**才付冷的那一笔 ⇒ 差 **~900×**;是否值得重建取决于连接器跟谁同寿 | `npm run bench:assembly` |
|
|
302
|
+
| trace 记不记内容,差多少 | 大出参(≈37 KB/次)5 次:缺省 `17.2 KB` / 不截断 `233.9 KB`(**13.6×**)/ 截断 200 `6.2 KB` | 缺省**已经截断**;`traceContent:'full'` 是按出参字节付费的开关,代价与出参多大成正比 | `PAYLOAD_ROWS=1000 CALLS='[5,20]' npm run bench:trace` |
|
|
303
|
+
| 指标快照 / OTLP flush | `snapshot()` `6.56 ms`;`buildOtlpPayload()` `0.073 ms` | flush 成本几乎全在 `snapshot()` ⇒ 与 flush 频率成正比,与是否被消费无关 | `npm run bench:otlp` |
|
|
304
|
+
| 挂起任务的到期扫描 | sqlite N=10000:全表 `61.9 ms` / 到期索引 `0.1 ms`;file store:`load` `69.5 ms` | **有没有索引**是结构性的(差 **~600×**),不是调优问题 | `npx tsx scripts/bench-resume-scan.ts 10000` |
|
|
305
|
+
| redis store 的 `list()` 形状 | N=10000:往返 **10001** 次、解析 **42.1 MB**;有辅助 ZSET ⇒ 往返 **1001** | 往返次数是结构量;本机无 redis 实例,延迟只做外推 | `npx tsx scripts/bench-redis-due.ts 10000` |
|
|
306
|
+
|
|
307
|
+
> ⚠️ **毫秒数不是承诺**:随机器 / Node 版本 / 负载而变,抄走当 SLA 会错。恒定的是**形状**。
|
|
419
308
|
>
|
|
420
|
-
> 五个基准**刻意不进 CI**(计时类基准在 CI
|
|
421
|
-
> 定下的处置是「要看时跑」)⇒ 上表的数**没有守卫帮你盯着**,要看就按「复跑」那一列自己跑一遍。
|
|
309
|
+
> 五个基准**刻意不进 CI**(计时类基准在 CI 上只会制造抖动)⇒ 上表的数没有守卫盯着,要看就按「复跑」那列自己跑。
|
|
422
310
|
|
|
423
311
|
## 开发本仓库
|
|
424
312
|
|
|
@@ -428,20 +316,19 @@ npm run build # 编译框架(dist/)
|
|
|
428
316
|
npm run typecheck # 类型检查
|
|
429
317
|
npm run lint # lint + 格式检查(Biome)
|
|
430
318
|
npm test # 单元测试(node:test)
|
|
431
|
-
npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑 examples/complete 与 examples/deploy
|
|
319
|
+
npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑 examples/complete 与 examples/deploy
|
|
432
320
|
```
|
|
433
321
|
|
|
434
|
-
>
|
|
435
|
-
> 脚手架项目里的 `agentia dev` 不受此限。
|
|
322
|
+
> 本仓库的 `npm run dev`(tsx)前需先 `npm approve-scripts` 批准 esbuild/tsx 的 postinstall;脚手架项目不受此限。
|
|
436
323
|
|
|
437
324
|
## 延伸阅读
|
|
438
325
|
|
|
439
|
-
- **使用者向完整说明**(API
|
|
326
|
+
- **使用者向完整说明**(API / 类型链路 / 已知边界 / 反例):[`docs/usage-guide.md`](docs/usage-guide.md)
|
|
440
327
|
- 完整导出清单:[`src/index.ts`](src/index.ts)
|
|
441
328
|
- 设计规格:[`docs/spec.md`](docs/spec.md)
|
|
442
|
-
-
|
|
443
|
-
- 上线清单(store 选型 / 鉴权边界 / 优雅停机 / 预算 /
|
|
444
|
-
-
|
|
445
|
-
-
|
|
446
|
-
-
|
|
447
|
-
|
|
329
|
+
- 生产可观测配方:[`docs/observability.md`](docs/observability.md)
|
|
330
|
+
- 上线清单(store 选型 / 鉴权边界 / 优雅停机 / 预算 / 回滚):[`docs/deployment.md`](docs/deployment.md)
|
|
331
|
+
- 接数据库(业务数据构造注入 / 自建 `TaskStore` 的 MySQL 骨架 / 别把业务事务和 run 混一个事务):[`docs/database.md`](docs/database.md)
|
|
332
|
+
- 框架兼容性实测(NestJS / Egg / Bun / Deno 结论表 + 最小代码 + Egg 三坑):[`docs/frameworks.md`](docs/frameworks.md)
|
|
333
|
+
- 完整示例:[`examples/complete/`](examples/complete/) · 最小部署:[`examples/deploy/`](examples/deploy/) · 存量改造:[`examples/agent-endpoint/`](examples/agent-endpoint/)
|
|
334
|
+
- 官网:[agentia-web.pages.dev](https://agentia-web.pages.dev)([Playground](https://agentia-web.pages.dev/playground) · [文档](https://agentia-web.pages.dev/docs) · [取舍对照](https://agentia-web.pages.dev/tradeoffs))
|