Agentic 基础原语

本页按你通常会遇到的顺序,介绍 Ageniti 提供的 5 个核心原语。

1. Action Contract

每个可调用能力都是一个 action:类型化的输入输出对,加上声明式的副作用策略。Contract 是所有 surface 的事实来源。

import { defineAction, s } from "@ageniti/core";
 
export const createTask = defineAction({
  name: "create_task",
  description: "Create a task in the user's inbox.",
  sideEffects: "write",
  idempotency: "conditional",
  input: s.object({
    title: s.string().min(1),
    priority: s.enum(["low", "high"]).default("low"),
  }),
  output: s.object({ id: s.string(), title: s.string() }),
  async run({ title, priority }, ctx) {
    ctx.logger.info("Creating task", { title });
    const task = await ctx.services.tasks.create({ title, priority });
    return { id: task.id, title: task.title };
  },
});

必填字段是 namedescriptionrun。与内置 CLI 命令冲突的保留名称(actionsmcpdev 等)会在定义时直接报错。

2. 用你已有的 Schema

defineAction 接受任何实现 Standard Schema v1 的 schema,或任何 Zod 风格的 schema(带 .safeParse / .parse):

import { z } from "zod";
import { defineAction } from "@ageniti/core";
 
export const search = defineAction({
  name: "search_tasks",
  description: "Search for tasks matching a query.",
  input: z.object({
    query: z.string(),
    limit: z.number().int().optional(),
  }),
  output: z.object({
    results: z.array(z.object({ id: z.string(), title: z.string() })),
  }),
  async run({ query, limit }) {
    return { results: await tasks.search(query, limit ?? 20) };
  },
});

外部 schema 通过 duck typing 识别。MCP 工具定义和 OpenAI tool spec 所需的 JSON Schema 通过 best-effort introspection 生成(覆盖 ZodObject / ZodArray / ZodUnion / ZodOptional / ZodDefault 等常见类型)。

如果你的 schema 比较特殊,可以手动覆盖 JSON Schema:

import { wrapSchema } from "@ageniti/core/schema-adapter";
 
const wrapped = wrapSchema(myCustomSchema, {
  jsonSchema: { type: "object", properties: { … } },
});

3. 批量包装已有函数

针对 Next.js Server Actions、tRPC procedures、普通的 handler record:

import { actionsFromHandlers, s } from "@ageniti/core";
import * as handlers from "@/app/actions/tasks"; // 你已有的函数
 
export const actions = actionsFromHandlers(handlers, {
  createTask: {
    description: "Create a task.",
    input: s.object({ title: s.string() }),
    sideEffects: "write",
  },
  searchTasks: {
    description: "Search tasks.",
    input: s.object({ query: s.string() }),
  },
});

或者用 defineActions 获得完全控制权(值可以是 config 对象,也可以是只读无入参的简化形式):

import { defineActions, s } from "@ageniti/core";
 
export const actions = defineActions({
  createTask: {
    description: "Create a task.",
    input: s.object({ title: s.string() }),
    run: async ({ title }) => tasks.create({ title }),
  },
  ping: () => ({ ok: true, time: Date.now() }),
});

camelCase 的 key 自动转成 snake_case 作为 action name。

4. 流式事件

每个 action 通过 runtime 执行,runtime 在执行过程中实时发出 live events。任何消费者(UI / agent / log shipper)都能通过 runtime.stream() 订阅:

const events = runtime.stream("create_task", { title: "Ship v1" });
 
for await (const event of events) {
  if (event.type === "log") console.log(event.level, event.message);
  if (event.type === "progress") updateProgressBar(event.percent);
  if (event.type === "artifact") attachToUi(event.artifact);
  if (event.type === "result") finalize(event.envelope);
}

事件来自 run() 函数里调用的 ctx.logger.* / ctx.progress.report() / ctx.artifacts.add()。CLI 的 --ndjson 模式和 React useAction hook 都是建在这个原语之上。

5. 类型化 Client + Codegen

类型化 client 包装任意 runtime 或远程 @ageniti HTTP server。成功时返回校验后的 data,失败时抛 AgenitiClientError。远端 HTTP 模式可以传 metadataconfirmidempotencyKey,但可信的 user / auth 需要在服务端解析:

import { createClient } from "@ageniti/core/client";
 
// 进程内
const client = createClient({ runtime });
const task = await client.create_task({ title: "Hello" });
 
// 或者连远程 @ageniti HTTP server
const remote = createClient({ url: "https://api.example.com" });
const tasks = await remote.search_tasks({ query: "today" });

为消费方生成 .d.ts 让 IDE 有完整自动补全:

import { generateClientTypes } from "@ageniti/core/client-gen";
import { writeFile } from "node:fs/promises";
 
await writeFile(".ageniti/client.d.ts", generateClientTypes(actions, {
  interfaceName: "TasksClient",
}));

React Hook(建在流式事件之上)

React UI 用 useAction hook 订阅 runtime.stream,调用过程中 logs / artifacts / progress 实时更新:

import { useAction } from "@ageniti/core/react-hooks";
 
function CreateTaskButton() {
  const { invoke, status, data, error, logs, progress, cancel } =
    useAction(createTask, { runtime });
 
  return (
    <>
      <button onClick={() => invoke({ title: "Hello" })}
              disabled={status === "loading"}>
        {status === "loading" ? `${progress?.percent ?? 0}%` : "Create"}
      </button>
      {status === "loading" && <button onClick={cancel}>Cancel</button>}
      {status === "success" && <p>Created task {data.id}</p>}
      {status === "error" && <p>Error: {error.message}</p>}
    </>
  );
}

状态机:idle → loading → (success | error | cancelled)。组件 unmount 自动 abort,新的 invoke 调用会取消之前的。

测试工具

import {
  createTestRuntime, expectOk, expectError, collectStream,
} from "@ageniti/core/test-utils";
 
test("create_task happy path", async () => {
  const t = createTestRuntime([createTask], { services: { tasks: stubTasks } });
  const env = await t.invoke("create_task", { title: "Hello" });
  const data = expectOk(env);
  expect(data.title).toBe("Hello");
});

框架无关 —— 兼容 node:test、vitest、jest。createTestRuntime 默认 surface: "json" 并跳过 confirmation gate,方便测试 destructive action。

整体如何串起来

上面这 5 个原语是基础。其他每一个 surface —— CLI 生成、MCP server、OpenAI tool spec、AI SDK tools、HTTP server —— 都是同一份 action contract 和同一个流式 runtime 之上的薄适配层。你在它之上构建的任何东西(agent、planner、workflow、自定义 UI)读的是同一份 contract,订阅的是同一组事件。