快速开始

Agentia 是声明式 agent 服务框架:装饰器声明四类单元,主 agent 作为路由器自主编排,一次 run 交付结构化结果与完整调用树(runId == traceId)。三分钟跑起来:

terminal
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。

src/main.ts
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 不中断。

units/weather/index.ts
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 记账。

units/weekly-report/index.ts
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 跑一轮,中间过程不外泄,只有最终报告回流主上下文。

units/doc-reviewer/index.ts
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。

units/style-guide/index.ts
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 的类,用装饰器声明能力。

project layout
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 零侵入;链为空时原样返回,零开销。
src/main.ts — 注册
const app = await createApp({
  name: 'my-app',
  discover: 'units',
  middleware: [auth, cached], // auth 在最外层
  system,
});

例:鉴权

middleware/auth.ts
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();
};

例:结果缓存

middleware/cache.ts
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。

async submit
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:丢旧工具对,不额外调模型;
  • 仍超预算且有 summarizecompaction:旧前缀做成摘要,只留最近 keepRecent 条;距上次压缩不足 compactEvery 个回合则跳过(防抖);
  • 框架不替你造 token:缺省 token 估算是 CJK 感知启发式,真机可注入按 /count_tokens 的 estimate。
policy
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。

typed.ts
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 收集器。

server.ts
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。

models.ts
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 devtsx 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 场景使用。