快速开始
Agentia 是声明式 agent 服务框架:装饰器声明四类单元,主 agent 作为路由器自主编排,一次 run 交付结构化结果与完整调用树(runId == traceId)。三分钟跑起来:
npm i -g @migor/cli
agentia create my-app # 脚手架:目录约定 + units.ts 注册表
cd my-app && npm install
agentia g subagent doc-reviewer # 生成单元并自动登记
export ANTHROPIC_API_KEY=sk-ant-...
npm run dev -- "帮我审查 docs/report.md"
入口 src/main.ts 只有一件事:装配应用,然后触发 run。
import { createApp, SystemPrompt } from '@migor/agentia';
const app = await createApp({
name: 'my-app',
discover: 'units', // 目录约定:units/<name>/ 一单元一文件夹
system: new SystemPrompt().add('role', '你是 my-app 的主 agent,按任务自主调度菜单里的单元。', true),
});
const { result } = await app.run([{ role: 'user', content: process.argv[2] }]);
console.log(result.finalText);
四类单元
单元即流水线的阶段。四类单元对主 agent 都是菜单里的可调用项,共用命名空间,装配期统一查重。
@Tool — 函数调用
类方法即工具。入参按 JSON Schema 解析,先校验再执行;失败回 is_error,run 不中断。
import { Tool } from '@migor/agentia';
export default class Weather {
@Tool({
description: '查询城市天气',
schema: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
additionalProperties: false,
},
strict: true,
})
get_weather(input: { city: string }) {
return `上海 72°F sunny(${input.city})`;
}
}
@Skill — 代码控制的流程
方法体是确定性脚本:要不要调模型、调几次、结果怎么加工,全由代码决定。模型调用只在显式 ctx.llm() 时发生,在 skill 自己的 unit span 下开 llm.turn 记账。
import { Skill } from '@migor/agentia';
import type { SkillContext } from '@migor/agentia';
export default class WeeklyReport {
@Skill({
description: '就给定主题调用 LLM 产出要点',
schema: {
type: 'object',
properties: { topic: { type: 'string' } },
required: ['topic'],
additionalProperties: false,
},
})
async weekly_report(input: { topic: string }, ctx: SkillContext) {
const r = await ctx.llm({ prompt: `就「${input.topic}」给出三个要点` });
return r.text; // 返回值即产物,以 tool_result 交回主 agent
}
}
@SubAgent — 隔离的子代理
独立循环 + 裁剪上下文。方法体不会执行——框架按 system 另起 agent 跑一轮,中间过程不外泄,只有最终报告回流主上下文。
import { SubAgent, asset } from '@migor/agentia';
export default class DocReviewer {
@SubAgent({
description: '按 system.md 的角色设定独立审查文档',
schema: {
type: 'object',
properties: { task: { type: 'string' } },
required: ['task'],
additionalProperties: false,
},
system: asset(import.meta.url, './system.md'),
})
// 方法体不会执行:只读取方法名与装饰器元数据
doc_reviewer(_input: { task: string }): void {}
}
@Prompt — 纯文本资产
模板与 playbook 资产,编译成菜单里一个无副作用的拉取型工具:模型判定需要时调用,文本以 tool_result 注入上下文。长文本放同目录 .md。
import { Prompt, asset } from '@migor/agentia';
export default class StyleGuide {
@Prompt({ description: '写作规范(描述何时该拉取)' })
style_guide(): string {
return asset(import.meta.url, './asset.md');
}
}
目录约定与装配
一单元一文件夹,长文本放 .md;每个单元是一个 default export 的类,用装饰器声明能力。
my-app/
├─ units.ts # 显式注册表(agentia g 自动维护,可手改)
├─ src/main.ts # createApp 装配入口
└─ units/
├─ weather/index.ts # @Tool 单元
└─ doc-reviewer/
├─ index.ts # @SubAgent 单元
└─ system.md # 长文本资产
两条装配路线
- 目录扫描:
createApp({ discover: 'units' })启动期扫描units/*/index.ts,default export 为类时以文件夹名为 DI token 注册(因动态 import,带 discover 的 createApp 返回 Promise)。 - 显式注册表:
createApp({ providers, system }),providers 来自units.ts(由agentia g自动维护)。
两条路线二选一或混用;同 token 后者覆盖前者,不会重复收集菜单。装配期做静态校验:菜单查重、引用存在性、DI 循环依赖——在启动时失败,而不是在线上。
中间件(UnitMiddleware)
挂在每一次单元(tool / skill / subagent / prompt)调用的前后:鉴权、限流、结果缓存、审计日志、超时包装等横切关注点都走这里,不进业务单元。
type UnitMiddleware = (call: UnitCall, next: UnitNext) => unknown;
interface UnitCall {
readonly unit: AgentTool; // name / description / inputSchema 可读
readonly input: unknown; // schema 校验已过的入参
readonly ctx?: ToolRunContext;
}
// 放行到下一层;next(newInput) 可改写入参,缺省沿用当前 input
type UnitNext = (input?: unknown) => unknown;
洋葱模型
- 链序 = 注册顺序:先注册的最外层,像洋葱一样层层包裹;
next()放行,next(newInput)改写入参;不调 next 即短路(结果缓存等);- 抛错按单元失败处理:engine 包成
is_error回给模型,run 不中断; - 装配期包裹(AgentApp 构造函数),对 engine 零侵入;链为空时原样返回,零开销。
const app = await createApp({
name: 'my-app',
discover: 'units',
middleware: [auth, cached], // auth 在最外层
system,
});
例:鉴权
import type { UnitMiddleware } from '@migor/agentia';
// 敏感单元要求执行上下文里带授权标记,否则按单元失败抛错
export const auth: UnitMiddleware = (call, next) => {
if (call.unit.name.startsWith('admin:') && !call.ctx) {
throw new Error('forbidden: 缺少执行上下文');
}
return next();
};
例:结果缓存
import type { UnitMiddleware } from '@migor/agentia';
const store = new Map<string, unknown>();
// 命中即短路(不调 next),未命中放行后回写
export const cached: UnitMiddleware = async (call, next) => {
const key = `${call.unit.name}:${JSON.stringify(call.input)}`;
if (store.has(key)) return store.get(key);
const out = await next();
store.set(key, out);
return out;
};
触发方式
同步 RPC、异步任务、定时调度共用一份输入契约;换宿主不换语义。
同步 RPC
await app.run(messages) 直接拿结果与 trace,适合请求-响应式宿主。
异步任务
AsyncRunner 提交即返回任务记录,后台执行;FileTaskStore 按 JSONL 一行一快照落盘,宿主重启后 resumePending() 续跑 queued/running。
const task = await runner.submit(
[{ role: 'user', content: '生成上周运营周报' }],
{ idempotencyKey: 'weekly-report:2026-W36' },
);
定时调度
Scheduler 支持 every(间隔)与 at(定点)两种计划,到点转为异步任务提交。
幂等
异步宿主是 at-least-once 语义:同一 idempotencyKey 重复 submit,若上一任务未失败则直接返回既有记录(last-wins 去重);失败的同键允许重提产生新任务。
长上下文策略
createBudgetPolicy 提供预算驱动的上下文护栏,规则带滞回,避免每回合反复压缩:
- 估算整组消息 token,≤ 预算 → 原样放行(快路径);
- 超预算先 context editing:丢旧工具对,不额外调模型;
- 仍超预算且有
summarize→ compaction:旧前缀做成摘要,只留最近keepRecent条;距上次压缩不足compactEvery个回合则跳过(防抖); - 框架不替你造 token:缺省 token 估算是 CJK 感知启发式,真机可注入按
/count_tokens的 estimate。
import { createBudgetPolicy } from '@migor/agentia';
const policy = createBudgetPolicy({
budgetTokens: 60_000, // 预算(估算 input tokens)
keepRecent: 20, // compaction 保留的最近消息条数
editBeforeCompact: true, // 先编辑再压缩
compactEvery: 1, // 压缩滞回(回合数)
// summarize: (historyText) => ..., // 提供才允许 compaction
});
结构化结果
给 run 一个 resultSchema,模型完成工作后通过隐藏的 submit_result 工具提交结果,框架校验后挂在 result.typed 上——不再从纯文本里猜 JSON。
const { result } = await app.run(messages, {
resultSchema: {
type: 'object',
properties: { pass: { type: 'boolean' }, reason: { type: 'string' } },
required: ['pass', 'reason'],
additionalProperties: false,
},
});
console.log(result.typed); // { pass: true, reason: '…' }schema 也可以来自 zod(peer 可选):fromZod(z.toJSONSchema(S), S),校验走 zod,错误路径原样回给模型自我修正。
宿主与导出
同一份应用,一行接入 HTTP;任务记录可落 SQLite;trace 可导出到任何 OTLP 收集器。
import { createServer } from 'node:http';
import { createHttpHandler, SqliteTaskStore, AsyncRunner, createOtlpExporter } from '@migor/agentia';
const runner = new AsyncRunner(app, { store: new SqliteTaskStore('./tasks.db') });
createServer(createHttpHandler(app, { runner })).listen(8080);
// POST /run(同步) POST /tasks(异步) GET /tasks/:id(轮询)
const otlp = createOtlpExporter({ endpoint: 'http://localhost:4318' });
await otlp.export(result.trace);多模型与记忆
engine 只依赖 ModelClient 结构面:Anthropic 天然满足,OpenAI 兼容端点(含 DeepSeek 等)用 createOpenAIClient 一行接入;memory 选项在 run 间水合/回写 blackboard。
import { createOpenAIClient, InMemoryMemoryStore } from '@migor/agentia';
const { result } = await app.run(messages, {
client: createOpenAIClient({ baseURL: 'https://api.deepseek.com' }),
model: 'deepseek-chat',
memory: { store: new InMemoryMemoryStore(), keys: ['profile'] },
});CLI 命令参考
全局安装 npm i -g @migor/cli 后使用 agentia 命令。
| 命令 | 作用 |
|---|---|
| agentia create <name> | 脚手架新项目:目录约定、units.ts 注册表、src/main.ts 入口、tsconfig。 |
| agentia g <type> <name> | 生成单元(tool / skill / subagent / prompt)到 units/<name>/,并自动登记 units.ts;长文本资产(system.md / asset.md)一并生成。 |
| agentia dev | tsx watch 启动 src/main.ts:改单元文件自动重启。 |
| agentia doctor | 装配体检:未登记/悬空单元、命名规范、重复条目。 |
| agentia add <pkg> | 安装第三方单元包(@AgentModule)并登记到注册表。 |
环境变量
| 变量 | 说明 |
|---|---|
| ANTHROPIC_API_KEY | 必填。Anthropic API 密钥(亦支持 ANTHROPIC_AUTH_TOKEN / ANTHROPIC_BASE_URL 兼容端点)。 |
| AGENTIA_MODEL | 可选。全局缺省模型覆盖,无则回落 claude-opus-5;显式传参优先级最高。 |
| OPENAI_API_KEY | 可选。OpenAI 兼容 provider 的密钥,多 provider 场景使用。 |