TypeScript Agent框架OneRingAI:用图记忆与工具集成构建智能体

发布时间:2026/8/30 8:13:25
TypeScript Agent框架OneRingAI:用图记忆与工具集成构建智能体 最近在一个内部 AI 项目中尝试用 TypeScript 搭建 Agent 流程时最困扰我的并不是模型调用本身而是“记忆怎么存”和“工具怎么接”。会话上下文稍长就丢失关键信息外部 API 一多编排代码也开始失控。所以当我看到 OneRingAI v1 这个以 TypeScript 为核心、主打 integrations 与 graph memory 的 Agent 框架时第一反应是这正是很多团队在工程化落地时缺的那一层。这篇文章会围绕 OneRingAI v1 展开梳理它要解决的问题、核心概念、安装配置、完整的 Agent 实战示例以及我在设计带记忆的 Agent 时总结的常见问题与工程建议。由于项目仍处于 v1 阶段具体 API 建议以你安装版本的官方文档为准但这里的实现思路和代码结构可以复用到大多数 TypeScript Agent 项目中。1. 背景与核心概念1.1 从 Agent 开发说起大语言模型LLM本身只能做“单轮推理”它没有手无法主动操作外部系统它也没有持久记忆每次对话结束后对话历史就归零了。为了让模型真正完成一个任务我们通常会在它外面包一层“Agent 运行时”接收用户任务让模型规划下一步动作调用工具搜索、数据库、HTTP API把工具结果返回给模型重复以上流程直到任务完成。这套模式在 Python 生态里已经很成熟但 TypeScript 生态中的 Agent 框架相对分散。有的只做简单的工具调用有的只有对话记忆缺少统一的状态管理和持久化方案。OneRingAI 的定位恰好是补上这一块用 TypeScript 写 Agent统一管理外部集成并用图结构存储记忆。1.2 OneRingAI 是什么从命名来看“One Ring”想表达的是一站式控制一个框架把模型、工具、记忆、编排统一起来。根据项目标题OneRingAI v1 的关键能力可以概括为三点TypeScript 原生类型安全、代码提示完善适合前后端共用一个语言生态的团队integrations把各种外部服务封装成 Agent 可调用的工具比如 GitHub、数据库、邮件、内部 API 等graph memory用图结构组织长期记忆实体是节点关系是边Agent 可以按关系检索历史信息而不是简单地把所有上下文塞进 prompt。也就是说OneRingAI 不是又一个“模型调用封装库”而是一个带有记忆层和集成层的 Agent 运行时。1.3 integrations 与 graph memory 解决什么问题先说 integrations。实际业务中Agent 不可能只靠模型“想”它必须“做”。做就需要连接外部系统。如果每个系统都手动写一遍 HTTP 调用、鉴权、错误重试代码会迅速膨胀。integration 层把这块做成可复用模块Agent 只需要声明需要哪些工具运行时负责注入和调度。再说 graph memory。传统的对话记忆是把历史消息拼接成文本塞进模型上下文。这样做有两个问题上下文窗口有限历史一长就放不下关键实体与关系容易被淹没在冗余文本里。图记忆的做法是把用户、项目、需求、文件、事件等建模成实体把“属于”“修改了”“关联”等建模成关系。查询记忆时按关系路径检索相关子图只把最相关的内容带进 prompt。信息密度更高成本也更低。1.4 为什么选择 TypeScriptTypeScript 不是 AI 领域最主流的语言但在工程化层面有明显优势类型系统可以在编译期拦截工具入参错误Node.js 生态有大量现成 SDK对接外部服务方便前后端语言统一团队协作成本低在 Electron、服务端、脚本工具中都能复用同一套 Agent 逻辑。如果你是 Python 出身的 AI 工程师不必担心 TypeScript 的学习成本。只要理解类型、异步、模块化这三个核心概念基本可以顺畅上手。2. 环境准备与版本说明在写代码之前先确认本机环境。OneRingAI 基于 TypeScript 运行在 Node.js 上因此需要准备以下环境。2.1 环境要求Node.js 18 及以上推荐 20 LTSnpm 或 pnpm 包管理器TypeScript 5.x具体版本见项目安装说明一个可用的 OpenAI 兼容 API 或本地模型服务如果使用图记忆持久化还需要准备一个图数据库或嵌入式存储。版本需要根据你的项目实际情况调整。本文示例以常见环境为例重点演示配置思路而不是绑定某个固定版本。检查 Node.js 版本node -v npm -v如果你还没有安装 Node.js建议直接安装当前 LTS 版本避免部分新语法和 API 不可用。2.2 初始化项目创建一个新目录并初始化项目mkdir oneringai-demo cd oneringai-demo npm init -y然后安装 OneRingAI 核心包和 TypeScript 开发依赖npm install oneringai npm install -D typescript tsx types/node注意由于 OneRingAI 还处于 v1 快速迭代阶段包名和导出方式在未来版本中可能调整。安装后建议先执行一次npm ls oneringai确认实际安装的版本。2.3 tsconfig 配置在项目根目录创建tsconfig.json配置如下{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, outDir: dist }, include: [src/**/*], exclude: [node_modules, dist] }这里有几点需要说明module使用NodeNext是为了适配 Node.js 原生 ESM 模块解析规则strict开启后TypeScript 会对null、undefined、隐式any做严格检查这对 Agent 工具入参校验非常有帮助esModuleInterop解决默认导入兼容问题skipLibCheck跳过第三方声明文件类型检查提升编译速度。如果你使用的是 CommonJS 模块体系可以把module改为CommonJS但建议新项目直接用 ESM长远来看更符合 Node.js 生态趋势。3. 核心概念拆解OneRingAI 的核心概念可以用四个词概括Agent、Integration、Graph Memory、Runtime。下面逐个拆开讲。3.1 Agent任务执行主体Agent 是用户与框架交互的主要入口。它接收一个任务内部循环执行“推理 - 调用工具 - 观察结果 - 再次推理”的流程直到任务完成或达到最大迭代次数。在设计中Agent 至少需要关注三个部分模型配置指定使用哪个模型、参数温度、最大 token 数工具列表声明这个 Agent 可以使用哪些工具记忆空间Agent 专属的记忆引用支持读写。一个最小化的 OneRingAI Agent 示例思路如下// 文件路径src/agent.ts import { Agent } from oneringai; const agent new Agent({ name: demo-agent, model: { provider: openai-compatible, model: gpt-4o-mini, apiKey: process.env.OPENAI_API_KEY, }, tools: [], memory: { type: graph, }, }); const result await agent.run(帮我梳理这个项目的核心模块); console.log(result.output);需要注意上面的model配置只是一个示例思路实际字段名取决于 OneRingAI 当前版本的定义。你可以在安装后运行npx tsc --noEmit通过类型提示查看准确的参数结构。3.2 Integration连接外部系统Integration 是 Agent 能力的延伸。一个 integration 通常是一个“工具工厂”负责处理鉴权、请求、解析、错误重试并暴露给 Agent 一个清晰的调用接口。如果你对接 GitHub可以这样理解定义工具的入参 schema比如owner、repo、since定义工具的执行函数把结果转成纯文本或 JSON 返回给模型。示例如下// 文件路径src/tools/github.ts import { defineTool } from oneringai; export const githubListIssuesTool defineTool({ name: github_list_issues, description: 列出指定仓库的 issue 列表, parameters: { type: object, properties: { owner: { type: string, description: 仓库拥有者 }, repo: { type: string, description: 仓库名称 }, }, required: [owner, repo], }, async execute(params) { const res await fetch( https://api.github.com/repos/${params.owner}/${params.repo}/issues ); if (!res.ok) { throw new Error(GitHub API 请求失败: ${res.status}); } return await res.json(); }, });核心设计点在于工具描述要给模型“看懂”参数定义要给运行时“校验”执行函数则要保持纯粹方便测试和替换。3.3 Graph Memory图记忆这是 OneRingAI 最有区分度的模块。图记忆不是简单地把字符串存进数据库而是维护一张图节点代表实体比如用户、任务、代码文件、团队边代表实体之间的关系每个节点/边可以带属性比如创建时间、更新时间、重要度。以“用户 A 修改了代码文件 B”为例图里会存在节点用户A 节点代码文件B 边A -[修改了]- B当模型需要回答“A 最近改过哪些文件”时从用户 A 出发沿“修改了”关系检索邻居节点比遍历整段历史文本效率高得多。实际代码中你可能会看到类似下面的抽象// 文件路径src/memory/graphMemory.ts import { GraphMemory } from oneringai; const memory new GraphMemory({ store: local }); await memory.addNode(userA, { type: user, name: 张三 }); await memory.addNode(fileB, { type: code, path: src/index.ts }); await memory.addEdge(userA, modified, fileB, { at: new Date().toISOString() }); const related await memory.query( MATCH (u:user)-[r:modified]-(f:code) WHERE u.id $id RETURN f, { id: userA } ); console.log(related);如果你的数据量较大可以考虑接入正式的图数据库后端把store指向对应的连接实例。当前阶段使用本地存储做原型验证已经足够。3.4 运行时编排Runtime 是连接 Agent、工具和记忆的调度中心。它的职责包括维持 Agent 循环控制最大迭代次数管理工具调用的并发与超时在记忆与模型上下文之间做数据转换监控每一次调用的 token 消耗和错误日志。在 OneRingAI 的架构里你可以把它理解为“引擎盖下面”的部分。大多数情况下你只需要创建 Agent 并调用run()运行时负责其余工作。但如果你想做生产级部署必须理解 Runtime 的执行流程否则很难排查“为什么 Agent 卡在某一步”。整体流程可以用一个简单的列表描述接收用户任务从记忆库加载相关上下文组装 messages 和工具列表调用模型得到回复或工具调用请求如果请求工具调用则执行对应工具把工具结果写回消息序列从工具结果中提取实体与关系更新图记忆重复步骤 4-7直到模型输出最终答案或达到迭代上限。4. 完整实战案例接下来我们构建一个可运行的实战项目一个带图记忆的“项目助手 Agent”。它可以学习用户提交的项目信息并把“用户参与了某个项目”的关系存储到图里之后可以按用户查询项目。4.1 创建项目结构项目结构如下oneringai-demo/ ├── src/ │ ├── agent.ts │ ├── memory/ │ │ └── graphMemory.ts │ ├── tools/ │ │ ├── addProject.ts │ │ └── listProjects.ts │ └── index.ts ├── .env ├── package.json └── tsconfig.json4.2 安装依赖在项目根目录执行npm install oneringai dotenv npm install -D typescript tsx types/nodedotenv用于加载环境变量避免把 API Key 硬编码在代码里。4.3 定义项目工具首先定义addProject工具用户可以通过自然语言让 Agent 记录“谁在做什么项目”。// 文件路径src/tools/addProject.ts import { defineTool } from oneringai; export const addProjectTool defineTool({ name: add_project, description: 记录某个用户参与了一个项目, parameters: { type: object, properties: { userName: { type: string, description: 用户名 }, projectName: { type: string, description: 项目名 }, role: { type: string, description: 用户在项目中的角色如开发、测试、产品 }, }, required: [userName, projectName], }, async execute(params, context) { const memory context.memory as GraphMemoryLike; const userId user:${params.userName}; const projectId project:${params.projectName}; await memory.upsertNode(userId, { type: user, name: params.userName }); await memory.upsertNode(projectId, { type: project, name: params.projectName }); await memory.upsertEdge(userId, participates_in, projectId, { role: params.role ?? member, since: new Date().toISOString(), }); return 已记录${params.userName} 参与项目 ${params.projectName}角色是 ${params.role ?? member}; }, });这里用了一个GraphMemoryLike类型来表示必要的记忆接口。在实际项目中你应该导入 OneRingAI 提供的正式类型。// 文件路径src/tools/listProjects.ts import { defineTool } from oneringai; export const listProjectsTool defineTool({ name: list_projects, description: 查询某个用户参与的所有项目, parameters: { type: object, properties: { userName: { type: string, description: 用户名 }, }, required: [userName], }, async execute(params, context) { const memory context.memory as GraphMemoryLike; const userId user:${params.userName}; const rows await memory.queryRelated(userId, participates_in); if (rows.length 0) { return 用户 ${params.userName} 暂无项目记录; } return rows .map((row) - ${row.targetName}角色${row.properties.role ?? member}) .join(\n); }, });这两个工具一个负责写记忆一个负责读记忆。模型会在需要时自动选择合适的工具。4.4 配置图记忆图记忆的底层实现可以有多种比如本地 JSON 文件、SQLite、Neo4j、Memgraph。为了便于演示这里使用一个本地存储示例核心是实现节点和边的读写接口。// 文件路径src/memory/graphMemory.ts interface NodeRecord { id: string; type: string; name: string; } interface EdgeRecord { from: string; relation: string; to: string; properties: Recordstring, unknown; } export class GraphMemoryLike { private nodes new Mapstring, NodeRecord(); private edges: EdgeRecord[] []; async upsertNode(id: string, data: NodeRecord): Promisevoid { this.nodes.set(id, data); } async upsertEdge( from: string, relation: string, to: string, properties: Recordstring, unknown ): Promisevoid { this.edges this.edges.filter( (e) !(e.from from e.relation relation e.to to) ); this.edges.push({ from, relation, to, properties }); } async queryRelated( from: string, relation: string ): PromiseArray{ targetName: string; properties: Recordstring, unknown } { return this.edges .filter((e) e.from from e.relation relation) .map((e) { const target this.nodes.get(e.to); return { targetName: target?.name ?? e.to, properties: e.properties, }; }); } }在真正的 OneRingAI 配置中你只需要把GraphMemoryLike换成框架提供的GraphMemory实例import { GraphMemory } from oneringai; const memory new GraphMemory({ store: local, path: ./data/memory.json, });这里的核心思想是记忆的读写必须有明确的数据结构让模型可以通过工具访问而不是随意拼接字符串。4.5 创建 Agent 并运行// 文件路径src/agent.ts import { Agent } from oneringai; import { addProjectTool } from ./tools/addProject; import { listProjectsTool } from ./tools/listProjects; import { GraphMemoryLike } from ./memory/graphMemory; import dotenv/config; const memory new GraphMemoryLike(); export const agent new Agent({ name: project-assistant, model: { provider: openai-compatible, model: process.env.MODEL_NAME ?? gpt-4o-mini, apiKey: process.env.OPENAI_API_KEY, }, tools: [addProjectTool, listProjectsTool], memory, maxIterations: 6, });然后写入口文件// 文件路径src/index.ts import { agent } from ./agent; async function main() { const task 1. 张三参与了一个叫智能客服平台的项目角色是后端开发。 2. 李四参与了一个叫数据看板的项目角色是前端开发。 3. 查询张三参与的所有项目。 ; const result await agent.run(task); console.log(最终输出); console.log(result.output); } main().catch((err) { console.error(err); process.exit(1); });4.6 运行与验证在.env文件中配置OPENAI_API_KEY你的密钥 MODEL_NAMEgpt-4o-mini运行项目npx tsx src/index.ts如果一切正常模型会在内部执行两次add_project和一次list_projects最终输出类似最终输出 张三参与的项目 - 智能客服平台角色后端开发这个例子虽然简单但它已经把 Agent、集成工具、图记忆三层都打通了。后续你可以把GraphMemoryLike替换成真实图数据库再把工具换成 GitHub、数据库、邮件等实际业务接口。5. 常见问题与排查思路在实际使用 OneRingAI 或类似 TypeScript Agent 框架时下面这些问题几乎一定会遇到。问题现象常见原因解决思路编译报错找不到模块包名错误或版本未安装执行npm ls检查依赖确认导入路径运行时报global is not definedNode.js 环境缺少 polyfill在入口文件顶部引入globalThis说明或启用 Node 专用构建配置模型不调用工具工具描述不够清晰或模型不支持 function calling简化描述对比参数 schema换用支持工具调用的模型记忆查询结果为空写入与读取的 key 不一致统一使用user:xxx和project:xxx的 ID 规则上下文过长历史消息未裁剪启用图记忆摘要按相关度截取子图TypeScript 7.0 弃用baseurl旧项目配置了baseurl新版编译器不再支持移除baseurl改用paths配合相对路径或exports映射下面挑三个重点展开。5.1 关于 TypeScript 7.0 中baseurl弃用最近很多 TypeScript 用户注意到baseurl选项在 TS 7.0 中将被弃用编译时会出现类似提示Option baseurl is deprecated and will stop functioning in TypeScript 7.0.这会影响部分 OneRingAI 示例项目因为早期脚手架经常使用baseurl来简化相对导入。解决方案很简单删除tsconfig.json里的baseurl使用 Node.js 原生exports字段别名或者用imports字段映射。示例package.json中的别名映射{ imports: { #src/*: ./src/* } }然后代码里这样导入import { agent } from #src/agent.js;这样可以绕开baseurl依赖同时代码更规范。5.2 Agent 卡在循环中不结束如果你的 Agent 持续调用工具但始终不输出最终答案通常是下面两个原因工具执行结果没有发生状态变化模型反复做同样的事最大迭代次数设置过大又没有停止条件。排查方法const result await agent.run(task, { maxIterations: 5, onStep: (step) { console.log(第 ${step.index} 步, step.toolName); }, });在每次工具调用后打印解析状态观察是哪一步出现了重复。如果确认是“工具结果没有变化”可以在工具执行结果里加入must_not_repeat标记提醒模型当前结果已经被看到过。5.3 图记忆查询变慢当节点和边的数量增长后本地遍历查询会越来越慢。解决方案是在入口处维护一个邻接索引或者直接迁移到原生图数据库。生产环境中图数据库通常支持索引和递归查询性能稳定得多。对于 OneRingAI 来说记忆模块设计成可插拔的。你可以在本地开发阶段使用local存储部署时通过环境变量切换到图数据库连接const memory new GraphMemory({ store: process.env.MEMORY_STORE neo4j ? neo4j : local, url: process.env.NEO4J_URL, username: process.env.NEO4J_USERNAME, password: process.env.NEO4J_PASSWORD, });6. 最佳实践与工程建议OneRingAI 这类框架给了我们很灵活的组装能力但工程化落地仍然需要自己把握边界。下面是我在实践后认为最重要的几条建议。6.1 工具入参必须严格校验模型虽然能理解自然语言但生成的参数不一定合法。比如数字可能写成字符串日期可能缺时区。每个工具内部都要对入参做二次校验。推荐使用zod或框架内置的 schema 检查import { z } from zod; const AddProjectParams z.object({ userName: z.string().min(1), projectName: z.string().min(1), role: z.string().optional(), }); export const addProjectTool defineTool({ name: add_project, parameters: AddProjectParams, async execute(params) { const parsed AddProjectParams.parse(params); // 后续逻辑使用 parsed }, });这样即使模型传了多余字段或错误类型也能在运行时及时拦截。6.2 记忆写入要“按需提炼”不是所有对话内容都值得写入图记忆。建议设计一个“是否写入”的判断逻辑包含新的实体信息时写入包含新的关系变更时写入纯粹的寒暄、猜测、临时推理不写入。可以让模型在输出工具调用结果时额外返回一个memory_updates字段由运行时决定是否落库。避免把图数据库变成“垃圾堆”。6.3 错误处理要分层Agent 执行链路上有多个容易出错的环节模型 API 调用失败工具内部业务失败记忆读写失败超时和限流。建议分层处理网络层设置超时与重试工具层捕获业务异常并返回给模型让模型知道“这个工具不能用”记忆层失败时降级为不写记忆不影响主流程顶层捕获未预期异常输出友好提示并记录日志。6.4 可观测性设计生产环境中Agent 的“黑盒”问题很突出。你很难判断它是思考太慢、工具报错还是模型抽风。建议在一开始就接入结构化日志或调用链追踪。每个步骤至少记录任务 ID步骤序号调用的工具名入参与出参摘要token 消耗耗时是否成功。示例日志格式{ taskId: task_001, step: 3, tool: list_projects, input: 张三, outputSummary: 2 个项目, tokens: 1250, durationMs: 312, success: true }这能大幅降低排错成本也能帮你分析 Agent 在哪些步骤上效率最低。6.5 安全边界与权限Agent 能调用工具就意味着它能访问外部系统。必须坚持最小权限原则每个 Agent 只注册它必要的工具工具内部的凭证使用只读令牌避免写操作权限扩散对危险操作删除、变更、转账做二次确认对不可信输入做内容过滤防止提示注入。例如一个查询 Agent 的令牌应该只拥有只读权限不能在某个环节把它升级为可写。涉及数据库变更或删除操作的 Agent必须有审批流程或手动确认通道。6.6 版本锁定与升级策略OneRingAI 处于 v1 阶段API 变动可能会比较频繁。建议在package.json中锁定精确版本{ dependencies: { oneringai: 1.0.0 } }或者使用~前缀只允许补丁版本升级{ dependencies: { oneringai: ~1.0.0 } }升级前先查看 changelog并且在测试环境跑一遍完整回归重点检查工具入参格式和记忆存储结构是否有变化。6.7 面向生产环境的 Agent 设计最后一点经验写 Agent 时一定要区分“原型验证”和“生产可用”。原型验证阶段可以本地存储、单模型、固定 prompt。生产阶段则需要考虑模型降级主模型不可用时切换备用模型队列与限流多个用户同时调用时避免打爆外部 API记忆隔离不同租户的数据不能混在一起日志脱敏工具入参中可能包含用户隐私记录日志时要打码。把思维从“写一个 demo”切换到“设计一个服务”你的 Agent 项目才能真正上线。7. 总结与学习路线OneRingAI v1 展示了 TypeScript 在 Agent 开发中的一个清晰组合方式用 integrations 归拢外部系统调用用 graph memory 解决上下文与长期记忆问题再用 TypeScript 的类型系统兜住工程安全性。这篇文章从概念、环境、核心原理讲到完整案例再给出常见问题和最佳实践。你亲手跑通之后建议按下面的路径继续深入把项目中的GraphMemoryLike替换成正式图数据库理解真实图查询和索引设计增加一个实际业务工具比如查询订单、创建任务、搜索文档观察模型如何选择工具给 Agent 增加权限控制明确哪些操作需要人工确认研究一下当前热门的 self-improving agents 方向思考如何让 Agent 从历史经验中自动总结出更优的工具调用策略。TypeScript Agent 生态还在快速变化OneRingAI 只是其中一个方向。但“工具集成 图记忆 类型安全”这套组合大概率是未来 Agent 框架的基础模板。建议你先从一个小场景跑通闭环再逐步扩展踩过的坑多了自然能找到最适合自己团队的那套方案。