- 人工智能
- AI Agent
- Agent 框架
- 后端
- 多智能体
- RAG
- 工具调用
- Agent 记忆
【免费下载链接】voltagent
AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework
导读
@voltagent/internal是 VoltAgent(一个基于 TypeScript 的开源 AI Agent 工程平台)monorepo 中为所有包提供共享基础能力的内部工具集。本文以 packages/internal/README.md 为骨架,结合源码逐层拆解其导出结构、入口与子路径导入机制、isObject/deepClone/safeStringify/createAsyncIterableStream等核心工具的实现原理,以及 Agent 开发中高频使用的流与异步迭代器转换函数。读完本文,你将能够在自己构建 Agent 应用时直接复用这些工具,理解 VoltAgent 各包之间共享代码的组织方式,并能正确使用按需导入的模块化路径。
一、包定位:多包架构中的“内部公共层”
VoltAgent 采用 pnpm workspace 组织的 monorepo 结构,packages目录下存在core、sdk、server-core、mcp-server、rag、logger、internal等众多独立包。其中@voltagent/internal扮演的是“内部共享工具”角色——它不直接面向终端用户暴露业务能力,而是为其他 VoltAgent 包提供语言判断、对象操作、安全序列化、流处理等通用基础函数与类型定义。
这一点可以从 packages/internal/package.json 的元数据得到印证:包描述为 "VoltAgent internal - an internal set of tools for the VoltAgent packages",仅依赖type-fest(提供Merge、SetRequired、EmptyObject等类型工具),开发依赖也只有@vitest/coverage-v8——依赖面极窄,符合“内部工具”的轻量定位。
包内目录结构如下(packages/internal/src):
a2a/:Agent-to-Agent 相关类型定义logger/:日志类型(仅导出类型)mcp/:MCP Server 元数据与工厂相关类型定义test/:流、数组、Response 之间的转换工具types/:包级通用类型(PlainObject、Nil、AnyFunction等)utils/:语言判断、对象操作、安全序列化、异步可迭代流等运行时工具
入口文件 packages/internal/src/index.ts 以export *方式聚合了test、utils、a2a、mcp四个子模块,并以export type *暴露logger/types中的类型。
二、Quick Start:安装与基础使用
README 给出的安装与使用方式如下:
pnpm add @voltagent/internalimport { isObject, isString } from "@voltagent/internal"; // Use utility functions if (isObject(data)) { console.log("Data is an object"); }由于项目本身使用 pnpm workspace,在多包仓库内直接安装即可;外部项目同样可通过 npm/pnpm/yarn 安装使用。
需要指出两点与源码对照的细节:
- README 示例中的
isString在 packages/internal/src/utils/lang.ts 中实际并未实现(当前导出的是isNil、isObject、isFunction、isPlainObject、isEmptyObject),示例仅用于演示“导入并使用工具函数”的写法。真实可用的语言判断函数见下文第三节。 isObject的实现如下:
export function isObject<T extends object>(obj: unknown): obj is T { return (typeof obj === "object" || typeof obj === "function") && !isNil(obj); }它并非仅判断“字面量对象”,而是把typeof === "object"与typeof === "function"都算作对象,同时排除null与undefined(isNil的定义见 packages/internal/src/types/index.ts:type Nil = null | undefined)。
三、Imports:子路径导入机制与源码对应
README 强调了“按需导入子集”的能力:
import { convertArrayToAsyncIterable } from "@voltagent/internal/test"; import { deepClone, hasKey } from "@voltagent/internal/utils";这一能力由 packages/internal/package.json 的exports字段定义,共暴露了 6 个入口:
| 子路径 | 类型入口 | 运行时入口 | 源码对应 |
|---|---|---|---|
.(根) | dist/main/index.d.mts/dist/main/index.d.ts | dist/main/index.mjs/dist/main/index.js | src/index.ts |
./test | dist/test/index.d.mts/dist/test/index.d.ts | dist/test/index.mjs/dist/test/index.js | src/test/index.ts |
./utils | dist/utils/index.d.mts/dist/utils/index.d.ts | dist/utils/index.mjs/dist/utils/index.js | src/utils/index.ts |
./a2a | dist/main/index.d.mts | dist/main/index.mjs | src/a2a/index.ts |
./mcp | dist/main/index.d.mts | dist/main/index.mjs | src/mcp/index.ts |
./types | dist/types/index.d.ts(仅类型) | — | src/types/index.ts |
要点解读:
- 每个入口同时提供了 ESM(
import条件下的.mjs+.d.mts)与 CJS(require条件下的.js+.d.ts)两套构建产物,保证不同模块体系的项目都能消费。 a2a与mcp子路径复用了dist/main的产物,这与其源码仅export * from "./types"的轻量定位一致(类型在打包时被并入主产物)。./types入口只有类型声明,没有运行时文件,适合纯类型导入场景。- 此外
package.json还提供了typesVersions映射,为不支持exports字段的旧版 TypeScript 提供等价的子路径类型解析兜底。
子路径内容一览:
./test导出convertArrayToAsyncIterable、convertArrayToReadableStream、convertAsyncIterableToArray、convertReadableStreamToArray、convertResponseStreamToArray(见 packages/internal/src/test/index.ts)。./utils导出deepClone、hasKey、isNil、isObject、isEmptyObject、isFunction、isPlainObject、createAsyncIterableStream、safeStringify及其选项类型(见 packages/internal/src/utils/index.ts)。
这种“根入口全量导出 + 子路径按需导入”的设计,让包内共享代码可以精细控制引入面,减少无副作用模块的打包体积,也便于工具函数按用途分组维护。
四、utils 工具集:类型守卫、对象操作与安全序列化
4.1 类型守卫函数(lang.ts)
packages/internal/src/utils/lang.ts 提供一组 TypeScript 类型谓词(type predicate):
isNil(obj) // obj === null || obj === undefined isObject(obj) // (typeof obj === "object" || typeof obj === "function") && !isNil(obj) isFunction(obj) // typeof obj === "function" isPlainObject(obj) // 原型为 Object.prototype 或 null 的普通对象(排除数组、函数、类实例) isEmptyObject(obj) // 无自有字符串属性且无自有 Symbol 属性值得注意的实现细节:
isPlainObject通过Object.getPrototypeOf(obj)判断原型链,只有原型为Object.prototype或null(如Object.create(null))时才返回true,因此数组、函数、Map、类实例都会被正确排除。isEmptyObject同时检查Object.getOwnPropertyNames(obj)与Object.getOwnPropertySymbols(obj),即使属性不可枚举或为 Symbol 键,也能被检测出来,不会误判为空对象。
4.2 对象操作(objects.ts)
packages/internal/src/utils/objects.ts 提供deepClone与hasKey:
deepClone<T>(obj): T:优先使用 Web 平台标准structuredClone(Node.js 17+ 与现代浏览器可用),实现对函数以外任意值(含嵌套对象、Map、Set、TypedArray、Date 等)的结构化深拷贝;若环境不支持structuredClone,则回退为浅拷贝({ ...obj }),并保留对原始值与null的直接返回。hasKey<T extends PlainObject, K extends string>(obj, key): obj is T & SetRequired<T, K>:结合isObject与in操作符判断键是否存在,并通过type-fest的SetRequired做类型收窄,让后续代码访问该键时无需再判空。这是典型的“运行时判断 + 编译期类型提升”组合用法。
4.3 安全序列化(safe-stringify.ts)
Agent 场景下,对象常常包含循环引用(如 Agent 上下文回环、图结构的状态)、BigInt、toJSON自定义逻辑等,直接JSON.stringify会抛错或产生不可预期结果。safeStringify(packages/internal/src/utils/safe-stringify.ts)正是为此设计:
export function safeStringify( input: DangerouslyAllowAny, { indentation }: SafeStringifyOptions = {}, ) { try { const seen = new WeakSet(); return JSON.stringify(input, safeStringifyReplacer(seen), indentation); } catch (error) { return `SAFE_STRINGIFY_ERROR: Error stringifying object: ${error instanceof Error ? error.message : "Unknown error"}`; } }关键行为:
SafeStringifyOptions.indentation支持字符串或数字缩进,直接透传给JSON.stringify的第三个参数。- 内部 replacer 维护一个
stack与WeakSet:遍历过程中遇到已在栈中的对象时返回"[Circular]"标记,从而把循环引用替换为可读占位文本而不是抛错。 - 若对象定义了
toJSON(),会先调用其转换结果再继续遍历。 - 任何序列化异常都会落入
catch,返回带SAFE_STRINGIFY_ERROR:前缀的错误信息字符串,而不是让异常冒泡中断整个流程——这对日志系统、调试输出、遥测上报等“绝不能挂”的路径尤其重要。 - 该实现有对应的快照测试 packages/internal/src/utils/snapshots/safe-stringify.spec.ts.snap,可验证循环引用场景下的输出形态。
4.4 异步可迭代流(async-iterable-stream.ts)
AsyncIterableStream<T>类型(packages/internal/src/utils/async-iterable-stream.ts)通过type-fest的Merge把AsyncIterable<T>与ReadableStream<T>合并为一个类型,即一个对象既是可迭代流又是 Web ReadableStream:
const stream: AsyncIterableStream<string> = getStream(); for await (const chunk of stream) { console.log(chunk); }createAsyncIterableStream(source)接收一个ReadableStream<T>,通过source.pipeThrough(new TransformStream<T, T>())创建可复用流,并手动为流挂载[Symbol.asyncIterator],内部用reader.read()逐块产出数据。这样下游代码既可以用for await...of消费,也可以继续pipeTo/pipeThrough给其他流环节,为 Agent 的流式响应(token 级输出)提供统一抽象。
五、test 工具集:数组、流与 Response 的互转
Agent 工程中,数据形态在“数组(批量输入)→ 异步迭代器(逐条处理)→ ReadableStream(流式输出)→ HTTP Response(传输)→ 数组(收集结果)”之间频繁切换。packages/internal/src/test/conversions.ts 提供了一组双向转换函数,并有配套测试 packages/internal/src/test/conversions.spec.ts:
| 函数 | 方向 | 说明 |
|---|---|---|
convertArrayToAsyncIterable(values) | 数组 → 异步迭代器 | 以async *[Symbol.asyncIterator]()逐个yield,惰性消费 |
convertArrayToReadableStream(values) | 数组 → ReadableStream | 在start(controller)中逐个enqueue,finally中close() |
convertAsyncIterableToArray(iterable) | 异步迭代器 → 数组 | for await...of收集全部元素 |
convertReadableStreamToArray(stream) | ReadableStream → 数组 | 循环reader.read()直到done |
convertResponseStreamToArray(response) | Response →string[] | 取response.body后先经TextDecoderStream解码为文本,再收集 |
其中convertResponseStreamToArray的实现特别适合流式 HTTP 响应(如 LLM 的 SSE 输出)的调试与断言:它将response.body用pipeThrough(new TextDecoderStream())转成 UTF-8 文本流,再复用convertReadableStreamToArray收集为字符串数组,方便逐 chunk 检查内容。这组工具在packages/e2e、packages/evals等包的集成测试与离线评估场景中被广泛使用。
六、a2a、mcp 与 types:类型层的内部契约
这三个模块偏向“类型契约”而非运行时逻辑:
- a2a(packages/internal/src/a2a/index.ts 与 types.ts):Agent-to-Agent 相关类型,供
packages/a2a-server等包引用。 - mcp(packages/internal/src/mcp/types.ts):定义 MCP Server 的元数据结构,包括:
MCPServerPackageInfo:name、version、可选description、installCommand、homepage;MCPServerRemoteInfo:environment、url、可选headers与description;MCPServerMetadata:id、name、version、protocols、capabilities、packages、remotes;MCPServerDeps:描述一个 MCP Server 可依赖的 Agent 注册表(agentRegistry)、工作流注册表(workflowRegistry)、父 Agent 查询(getParentAgentIds)、日志/提示词/资源/elicitation 等能力;MCPServerLike与MCPServerFactory:定义 Server 的initialize(deps)、可选getMetadata()、startConfiguredTransports()、close()接口以及工厂函数类型。
- types(packages/internal/src/types/index.ts):包级基础类型
DangerouslyAllowAny(显式“危险放行”标记)、PlainObject、Nil、AnyAsyncFunction、AnySyncFunction、AnyFunction。
七、工程化与质量保障
- 构建:使用
tsup(pnpm build),产物按exports拆分到dist/main、dist/utils、dist/test等目录,开发时可pnpm dev开启 watch 模式。 - 测试:基于 Vitest(
pnpm test),核心工具均有对应.spec.ts单测(如 lang.spec.ts、objects.spec.ts、safe-stringify.spec.ts、async-iterable-stream.spec.ts、conversions.spec.ts),可用pnpm test:coverage查看覆盖率。 - 代码规范与发布校验:
pnpm lint使用 Biome 检查;pnpm attw(Are The Types Wrong)与pnpm publint --strict分别校验类型声明与发布包结构是否合规,确保子路径导出在真实生态下可被正确解析。 - License:MIT,见仓库根目录 LICENCE。
八、在 VoltAgent 包内实际消费的示例
@voltagent/internal的消费方覆盖 VoltAgent 的多个运行时包。从源码结构看,core、sdk、server-core、mcp-server、logger、evals等包都会以 workspace 依赖方式引用它,典型用途包括:
- 用
isObject/isPlainObject守卫来自 LLM 输出或 MCP 响应的不可信数据; - 用
safeStringify在日志与遥测管线中序列化含循环引用的 Agent 状态; - 用
createAsyncIterableStream包装底层 ReadableStream,向 Agent 运行时暴露统一的流式接口; - 用
convertResponseStreamToArray在评估与测试中把流式响应收集为可断言的数组。
这种“内部工具包 + 子路径导出”的实践,为多包 AI 框架提供了可复用的基础层:既避免了各包重复实现同样的小工具,又通过精细的导出面控制降低了耦合。
九、小结
@voltagent/internal是 VoltAgent 的内部共享工具包,聚合了类型守卫、对象操作、安全序列化、异步流与数据形态互转等能力;- 根入口
@voltagent/internal全量导出,/test、/utils、/a2a、/mcp、/types子路径支持按需导入,且 ESM/CJS 双产物齐备; deepClone优先使用structuredClone并提供浅拷贝回退;safeStringify可容忍循环引用与序列化异常;createAsyncIterableStream让 ReadableStream 同时具备异步迭代能力;- 每个工具都配有 Vitest 单测与 Biome 规范检查,构建发布链路(tsup + attw + publint)保证了子路径导出的可靠性。
对于希望在自身 Agent 应用中复用这些能力的开发者,可直接pnpm add @voltagent/internal并按需从子路径导入;对于希望深入理解 VoltAgent 内部协作机制的读者,建议从 packages/internal/src/index.ts 出发,沿utils与test两个子模块逐文件阅读其单测,即可快速掌握这套内部契约的完整面貌。
- 人工智能
- AI Agent
- Agent 框架
- 后端
- 多智能体
- RAG
- 工具调用
- Agent 记忆
【免费下载链接】voltagent
AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework
相关推荐
@voltagent/rag 1.0.2 发布与 VoltAgent 2.x 迁移实践:Chunking 与 RAG 工具链完全指南
@voltagent/rag 1.0.2 发布与 VoltAgent 2.x 迁移实践:Chunking 与 RAG 工具链完全指南 Voltagent 是构建
人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音VoltAgent 工程工具链指南:基于 Nx 插件的包生成器与 Provider 开发工作流
VoltAgent 工程工具链指南:基于 Nx 插件的包生成器与 Provider 开发工作流 VoltAgent 是一个基于开源 TypeScript AI
人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音Apollo Client 内部测试工具包 `@apollo/client/testing/internal` API 全面解析
Apollo Client 内部测试工具包 @apollo/client/testing/internal API 全面解析 @apollo/client/te
前端GraphQL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考