aesyiu
v0.2.0
Published
Stateless, high-concurrency AI Agent framework with onion-model middleware and dynamic LLM switching
Readme
特性
- 统一 ReAct 循环 —
engine.run()阻塞返回;engine.runStream()产出增量事件,共享同一套 middleware / memory 逻辑 - 三层 middleware
use(mw)洋葱模型,包裹整个runuseLLM(mw)拦截每次 LLM 调用(retry / timeout / logging)useTool(mw)拦截每次工具调用(可改写args)
- 内建 middleware —
loggingMiddleware/retryMiddleware/timeoutMiddleware(基于AbortSignal.any真正传递取消) - AbortSignal —
RunOptions.signal贯穿到 provider SDK;超时 / 用户中止即时退出 - Skill + MCP — 文件系统加载 Skill,stdio MCP 服务器注册 / 卸载 / 状态查询
- 动态模型 — provider 可注册多 model;
generate(ModelDefinition | string, ...)按需切换 - 类型严格 —
Tool<TArgs, TResult>泛型、AgentContext<TState>泛型、ToolParameters = ZodType | JSONSchema可判别 - 资源管理 —
Symbol.asyncDispose原生支持await using
环境要求
- Node.js ≥ 20(用到
AbortSignal.any/node:timers/promises) - TypeScript ≥ 5.6 使用方(如需
await using语法)
安装
npm install aesyiu快速开始
import {
AesyiuEngine,
AgentContext,
AnthropicProvider,
ANTHROPIC_MODELS,
} from 'aesyiu';
const provider = new AnthropicProvider(
{ apiKey: process.env.ANTHROPIC_API_KEY! },
ANTHROPIC_MODELS,
);
const ctx = new AgentContext({ provider, modelId: 'claude-sonnet-4-6' });
const engine = new AesyiuEngine({ maxSteps: 10 });
const result = await engine.run(
{ role: 'user', content: '你好' },
ctx,
);
if (result.status === 'error') {
console.error(result.error?.source, result.error?.message);
} else {
console.log(result.visibleMessages.at(-1)?.content);
}核心概念
Provider
import {
AnthropicProvider, ANTHROPIC_MODELS,
OpenAIResponsesProvider, OPENAI_RESPONSES_MODELS,
OpenAICompletionProvider, OPENAI_COMPLETION_MODELS,
} from 'aesyiu';
// Anthropic Claude — 包含 Opus 4.7 / Sonnet 4.6 / Haiku 4.5
const claude = new AnthropicProvider({ apiKey }, ANTHROPIC_MODELS);
// OpenAI Responses API(新接口,支持 reasoning)
const oa = new OpenAIResponsesProvider({ apiKey }, OPENAI_RESPONSES_MODELS);
// OpenAI Chat Completions(经典接口)
const oac = new OpenAICompletionProvider({ apiKey }, OPENAI_COMPLETION_MODELS);动态注册 model / 传 inline definition:
provider.registerModel({
id: 'custom-model-id',
contextWindow: 200000,
maxOutputTokens: 8192,
});
// 或者不走 supportedModels,直接传 ModelDefinition 给 generate
await provider.generate(
{ id: 'custom-id', contextWindow: 128000, maxOutputTokens: 4096 },
messages,
);Context
// 基本用法
const ctx = new AgentContext({ provider, modelId: 'gpt-4o' });
// 带类型化 state(泛型)
interface MyState { chatId: string; traceId: string }
const ctx = new AgentContext<MyState>({
provider,
initialState: { chatId: 'c-1', traceId: 't-1' },
});
ctx.state.chatId; // 有类型
// 动态切换 LLM
ctx.switchLLM(anotherProvider, 'claude-haiku-4-5-20251001');
// 消息读取(返回 readonly,不可外部改写)
const all: readonly Message[] = ctx.messages;
const visible = ctx.getVisibleMessages(); // 过滤内部 promptEngine
const engine = new AesyiuEngine({
maxSteps: 10,
compatibilityMode: false, // 多个 system message 合并成一条(第三方兼容 provider)
memoryConfig: { // 上下文压缩阈值
compressThresholdRatio: 0.8,
retainLatestMessages: 5,
},
});EngineResult
interface EngineResult {
status: 'completed' | 'max_steps_reached' | 'error';
messages: Message[]; // 完整历史,含内部 prompt
visibleMessages: Message[]; // 对外消息,过滤 _meta.internal
usage: TokenUsage;
error?: {
message: string;
source: 'provider' | 'memory' | 'tool' | 'engine' | 'aborted' | 'unknown';
cause?: string; // stack or String(original)
};
}工具
defineTool(推荐)
import { defineTool } from 'aesyiu';
import { z } from 'zod';
const calculator = defineTool({
name: 'calculator',
description: '计算数学表达式',
parameters: z.object({ expr: z.string() }), // Zod 自动校验
async execute({ expr }) { // args 已经是 { expr: string }
return Function('"use strict";return (' + expr + ')')();
},
});
engine.registerTool(calculator);Zod vs JSONSchema
Tool.parameters 接受 ZodType | JSONSchema:
- Zod:运行时自动
safeParse校验,失败返回错误信封 - JSONSchema:透传给 provider,不做运行时校验(ToolExecutor 会
console.warn一次)
过滤运行时工具
await engine.run(input, ctx, {
tools: ['calculator', 'search'], // 仅启用这些
});中间件
三类中间件职责明确:
1. User middleware — use(mw)
包裹整个 run(),适合鉴权 / 计时 / 全局日志:
engine.use(async (ctx, next) => {
console.time('run');
await next();
console.timeEnd('run');
});2. LLM middleware — useLLM(mw)
拦截每次 LLM 调用(包含 memory summarize),支持修改 options、重试、超时:
import { loggingMiddleware, retryMiddleware, timeoutMiddleware } from 'aesyiu';
engine
.useLLM(loggingMiddleware({ label: 'main' }))
.useLLM(retryMiddleware({ maxRetries: 3, initialDelayMs: 500 }))
.useLLM(timeoutMiddleware({ ms: 30_000 })); // AbortSignal 真正传递到 SDK自定义:
engine.useLLM(async (ctx, next) => {
// ctx.model / ctx.messages / ctx.tools / ctx.options / ctx.agentContext
// 可覆盖 options:
ctx.options = { ...ctx.options, signal: newSignal };
return next();
});3. Tool middleware — useTool(mw)
拦截每次工具调用,可改写 args:
engine.useTool(async (ctx, next) => {
// 注入 tenant 上下文
ctx.args = { ...(ctx.args as object), tenantId: 'abc' };
const raw = await next(); // raw 是 tool.execute 的原值
return raw;
});流式运行(runStream)
for await (const event of engine.runStream(input, ctx, { signal })) {
switch (event.type) {
case 'text_delta':
process.stdout.write(event.delta); // 增量字符
break;
case 'tool_call':
console.log('\n[tool]', event.toolCall.name);
break;
case 'tool_result':
// event.message 是工具结果消息
break;
case 'step_end':
console.log('\n--- step', event.step);
break;
}
}
// 生成器 return 值就是最终 EngineResultrunStream 与 run 共享 LLM / Tool middleware(包括 retry / timeout),只在输出形态上不同。
中止(AbortSignal)
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
const result = await engine.run(input, ctx, { signal: controller.signal });
// result.status === 'error' && result.error.source === 'aborted'工具内、provider 内、memory summarize 都会收到 signal 并立即退出。
Skill 系统
文件系统目录结构:
skills/
code-reviewer/
SKILL.md # YAML frontmatter + markdown body
scripts/ # 可选
references/ # 可选import { loadSkills } from 'aesyiu';
const skills = await loadSkills('./skills');
engine.registerSkills(skills);
// 单次运行限定
await engine.run(input, ctx, { skills: ['code-reviewer'] });Skill 注册时会自动注入 loadskill 工具 + 一条可见 skill prompt(标记为 _meta.internal = true,默认不出现在 visibleMessages 中)。
MCP(Model Context Protocol)
await engine.registerMCPServer({
name: 'filesystem',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'],
});
// 运行时动态卸载
await engine.unregisterMCPServer('filesystem');
// 状态查询
const mcp = engine.getMCPManager();
mcp.listServers(); // MCPServerStatus[]
mcp.getServer('name'); // MCPServerStatus | undefined
mcp.isRegistered('name'); // booleanPrompt Sections
在 ctx 上注册可替换的系统提示段(框架内部 skill 也用此机制):
ctx.registerPromptSection('app:role', {
content: '你是代码审查员',
pinned: true,
});
// 再次注册同名会替换而非追加
ctx.registerPromptSection('app:role', { content: '你是安全审计员' });
// 移除
ctx.removePromptSection('app:role');注册的消息带 _meta.internal = true,不出现在 result.visibleMessages 中。
Memory(上下文压缩)
import { MemoryManager } from 'aesyiu';
const memory = new MemoryManager({
compressThresholdRatio: 0.75, // 占满 75% 上下文时压缩
retainLatestMessages: 8, // 保留最近 8 条
});
const engine = new AesyiuEngine({ memoryManager: memory });
// 或短手
const engine = new AesyiuEngine({ memoryConfig: { retainLatestMessages: 8 } });压缩过程会通过 useLLM middleware chain — retry / timeout 对 summarize 同样生效。
资源管理
await using engine = new AesyiuEngine();
await engine.registerMCPServer(...);
// 作用域结束自动 dispose:关闭所有 MCP server
// 等价:
const engine = new AesyiuEngine();
try {
/* ... */
} finally {
await engine.dispose();
}工具函数
import { isAbortError, filterVisibleMessages } from 'aesyiu';
// 判断错误是否由 AbortSignal 触发
try {
await engine.run(...);
} catch (err) {
if (isAbortError(err, signal)) {
// 用户中止 or 超时
}
}
// 过滤 _meta.internal 的对外消息
const visible = filterVisibleMessages(ctx.messages);类型速查
| 类型 | 说明 |
|---|---|
| Message | { role, content, tool_calls?, tool_call_id?, _meta? } |
| Tool<TArgs, TResult> | 泛型工具定义 |
| ToolParameters | ZodType \| JSONSchema |
| ToolResultEnvelope<T> | { success, result?, error? } |
| EngineResult | { status, messages, visibleMessages, usage, error? } |
| RunOptions | { tools?, skills?, signal? } |
| RunStreamEvent | step_start \| text_delta \| assistant_message \| tool_call \| tool_result \| step_end |
| Middleware | (ctx, next) => Promise<void> |
| LLMMiddleware | (ctx, next) => Promise<{ message, usage }> |
| ToolMiddleware | (ctx, next) => Promise<unknown> |
License
MIT
