# Lighter > 轻量的 TypeScript AI Agent 框架。当前为 Alpha 版本。 --- 来源:/zh/docs/ # Lighter Lighter 是一个用于构建 AI agent 的轻量 TypeScript 框架。它负责模型调用与工具执行之间的循环,让你用少量代码搭建自己的 agent。 Lighter 的设计受到 [Pi](https://pi.dev/) 的启发:保持核心精简,同时保留灵活的自定义能力。易用性同样是设计重点,API 简单直观,让不同经验水平的开发者都能轻松上手。 你可以用它: - **调用模型**:通过统一接口接入 OpenAI、Claude 或 DeepSeek。 - **执行工具**:把自己的函数交给模型调用,完成查询、计算等任务。 - **构建交互**:接收流式回复,用应用维护的历史继续对话。 - **定制行为**:通过生命周期钩子记录请求、修改输入或处理回复。 ## 开始使用 从[快速开始](/zh/docs/getting-started/quickstart/)入手,完成安装、连接模型并运行第一个 Agent。你只需要准备 Node.js、一个模型服务的 API key,以及可用的模型 ID。 ## 核心组成 Lighter 的最小组合是 `Provider → Model → Agent`: | 组件 | 职责 | | --- | --- | | Provider | 与模型服务通信,转换请求和响应 | | Model | 封装模型调用,执行模型调用前后的钩子 | | Agent | 组织消息,执行模型与工具之间的循环 | 工具是可选的。没有工具时,Agent 获取模型回复后结束;模型请求工具时,Agent 执行工具、回传结果,再次调用模型,直到模型不再请求工具。 ## 文档导航 | 分类 | 内容 | | --- | --- | | 开始使用 | [快速开始](/zh/docs/getting-started/quickstart/) · [核心概念](/zh/docs/getting-started/concepts/) | | 使用指南 | [配置 Agent](/zh/docs/guides/configuration/) · [工具](/zh/docs/guides/tools/) · [对话历史](/zh/docs/guides/conversations/) · [流式输出](/zh/docs/guides/streaming/) · [钩子](/zh/docs/guides/hooks/) · [错误处理](/zh/docs/guides/errors/) | | 模型接入 | [内置 Provider](/zh/docs/providers/) · [自定义 Provider](/zh/docs/providers/custom/) | | 进阶用法 | [直接调用 Model](/zh/docs/advanced/model/) · [运行边界](/zh/docs/advanced/limits/) | | API 参考 | [Agent](/zh/docs/reference/agent/) · [Model 与 Provider](/zh/docs/reference/model-and-providers/) · [Messages](/zh/docs/reference/messages/) · [Tool](/zh/docs/reference/tools/) · [Hooks](/zh/docs/reference/hooks/) · [Errors](/zh/docs/reference/errors/) | AI agent 可以从 [llms.txt](/zh/docs/llms.txt) 按需查找文档,也可以读取[完整合并文档](/zh/docs/llms-full.txt)。 ## 版本与边界 本文档对应 **Lighter 0.1.0**。当前为 Alpha 版本,API 可能发生破坏性变化。 Lighter 提供核心循环,对话存储、最大运行轮数、业务权限和工具隔离由应用负责。 [开始:运行第一个 Agent →](/zh/docs/getting-started/quickstart/) --- 来源:/zh/docs/getting-started/quickstart/ # 快速开始 用一个最小示例,连接模型并获得第一条回复。以下使用 OpenAI,需要 Node.js 22+ 和一个使用 ESM 的 TypeScript 项目。 ## 安装依赖 ```sh npm install @yong_xie/lighter npm install --save-dev tsx ``` 示例使用 `tsx` 运行 TypeScript;已有运行方式的项目可以省略它。 ## 设置 API key 在终端设置 API key 和该账号可用的模型 ID: ```sh export OPENAI_API_KEY="your-api-key" export OPENAI_MODEL="your-model-id" ``` ## 创建并运行 Agent 创建 `hello.ts`: ```ts import { Agent, Model, OpenAIProvider, UserMessage } from "@yong_xie/lighter"; // 使用前面设置的 API key 和模型 ID 连接 OpenAI。 const provider = new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, ); // 将 Provider 包装为 Model,再用它创建 Agent。 const model = new Model(provider); const agent = new Agent({ model }); // 用 UserMessage 表示用户输入,run() 会等待本次运行结束。 const result = await agent.run({ messages: [new UserMessage("给一家开在月球上的书店起个名字,只返回店名。")], }); if (result.status === "failed") { // 运行失败时读取 error,并让进程以失败状态退出。 console.error("运行失败:", result.error); process.exitCode = 1; } else { // 本例没有工具,最后一条新增消息就是助手回复。 const reply = result.messages.at(-1); if (reply?.role === "assistant") { console.log(reply.content); } } ``` `Provider` 连接模型服务,`Model` 封装模型调用,`Agent` 接收消息并执行。调用 `run()` 即可获得结果。 在同一终端运行: ```sh node --import tsx hello.ts ``` 输出类似下面这样,具体内容由模型生成: ```text 月光书局 ``` ## 理解返回结果 - `status` 为 `"completed"` 时运行完成,为 `"failed"` 时通过 `error` 查看原因。 - `messages` 只包含本次新增的消息,不包含传入的历史。 - 这个示例没有添加工具,成功时最后一条消息就是助手回复,文本在 `content` 中。 [← 返回文档首页](/zh/docs/) · [下一步:核心概念 →](/zh/docs/getting-started/concepts/) --- 来源:/zh/docs/getting-started/concepts/ # 核心概念 理解六个组件,就能掌握 Lighter 的主要用法:`Provider`、`Model`、`Agent`、`Message`、`Tool` 和 `Hook`。 ## 从模型到 Agent | 组件 | 负责什么 | 什么时候使用 | | --- | --- | --- | | Provider | 与具体模型服务通信,转换请求、响应和错误 | 选择 OpenAI、Claude、DeepSeek,或接入其他服务 | | Model | 封装 Provider,执行模型调用前后的钩子 | 创建 Agent,或直接调用一次模型 | | Agent | 组织消息,执行模型和工具之间的循环 | 让模型借助工具完成任务 | 一个 Agent 使用一个 Model,一个 Model 使用一个 Provider。内置 Provider 接收 API key 和模型 ID,再交给 Model: ```ts import { Agent, Model, OpenAIProvider } from "@yong_xie/lighter"; // 三个组件依次组合;此时只创建对象,还没有发送请求。 const provider = new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, ); const model = new Model(provider); const agent = new Agent({ model }); ``` ## 一次运行发生了什么 调用 `agent.run({ messages })` 后,Agent 会: 1. 将系统提示词与输入消息组成上下文。 2. 调用模型,获得助手消息。 3. 如果消息包含工具调用,按顺序执行工具,把结果加入上下文,再次调用模型。 4. 如果消息没有工具调用,结束循环,返回本次新增的消息。 ```mermaid flowchart TD A[系统提示词与输入消息] --> B[调用模型] B --> C{是否请求工具?} C -->|是| D[按顺序执行工具] D --> E[将工具结果加入上下文] E --> B C -->|否| F[返回本次运行结果] ``` 没有配置工具时,正常情况下只会调用一次模型。模型持续请求工具时,循环会继续;当前没有内置最大轮数。 ## 消息、工具与钩子 **Message** 承载上下文,包括系统指令、用户输入、助手回复和工具结果。助手消息也可能包含工具调用,并不总是一段面向用户的文本。 **Tool** 是交给模型调用的函数。你定义名称、描述、参数格式和执行逻辑,Agent 负责处理调用与结果回传。 **Hook** 在运行开始、模型调用前后或运行结束时执行,适合记录日志或修改请求与回复。 ## 一次运行与多轮对话 一次 `run()` 可以包含多次模型调用,但不会自动记住上一次运行。多轮对话需要应用保存历史,在下一次请求时重新传入。 `run()` 与 `stream()` 使用相同的核心循环:前者等待最终结果,后者在运行中提供增量文本,最后再提供完整结果。 [← 快速开始](/zh/docs/getting-started/quickstart/) · [下一步:配置 Agent →](/zh/docs/guides/configuration/) --- 来源:/zh/docs/guides/configuration/ # 配置 Agent 创建 Agent 时只有 `model` 必填。按需添加工具、系统提示词和生命周期钩子。 ## 设置系统提示词 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Agent, Model, OpenAIProvider, SystemMessage, UserMessage, } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); const agent = new Agent({ model, // 用 SystemMessage 设置角色、任务范围和回复风格。 systemPrompt: new SystemMessage("你是一位书店顾问。用简短的中文回答问题。"), }); const result = await agent.run({ // 每次运行的具体问题放在 messages 中。 messages: [new UserMessage("推荐三种适合月球书店的活动。")], }); console.log(result); ``` 省略 `systemPrompt` 时,默认内容为 `You are a helpful assistant.`。传入自定义值会替换默认提示词。 Agent 会自动把系统提示词放在本次上下文前面,维护对话历史时不需要重复添加它。 ## 配置放在哪里 | 内容 | 设置位置 | | --- | --- | | API key、模型 ID、Provider 特有选项 | Provider 构造参数 | | Model、工具、系统提示词、钩子 | `new Agent({ ... })` | | 用户消息和已有对话历史 | `run({ messages })` 或 `stream({ messages })` | 当前不能通过单次 Agent 请求覆盖模型或工具。需要不同配置时,创建另一个 Agent。 ## 按需扩展 - 用 [Tool](/zh/docs/guides/tools/) 让 Agent 执行自己的函数。 - 用 [Hook](/zh/docs/guides/hooks/) 记录或修改运行过程。 - 在 [Provider 配置](/zh/docs/providers/)中调整输出长度、推理等选项。 完整参数见 [Agent API](/zh/docs/reference/agent/)。 [← 核心概念](/zh/docs/getting-started/concepts/) · [下一步:添加工具 →](/zh/docs/guides/tools/) --- 来源:/zh/docs/guides/tools/ # 添加工具 工具让模型调用你定义的函数。你负责描述工具并实现逻辑,Agent 负责校验参数、执行函数和回传结果。 ## 定义并注册工具 先安装用于定义参数格式的 Zod: ```sh npm install zod@4 ``` 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Agent, Model, OpenAIProvider, Tool, UserMessage } from "@yong_xie/lighter"; import * as z from "zod"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); const add = new Tool({ name: "add", description: "计算两个数的和。", // 参数格式会提供给模型,也会在执行前进行本地校验。 parametersZodSchema: z.strictObject({ a: z.number(), b: z.number() }), // a、b 的 TypeScript 类型由参数格式推断。 func: ({ a, b }) => a + b, }); const agent = new Agent({ model, tools: [add] }); const result = await agent.run({ messages: [new UserMessage("请调用 add 工具计算 19 + 23。")], }); // 查看完整过程,包括助手的工具调用、工具结果和后续回复。 console.log(result); ``` 模型请求 `add` 时,Agent 执行函数,得到 `42`,再把结果发给模型生成回复。模型是否调用工具由模型决定。 ## 异步工具 `func` 也可以是异步函数。下面是一个模拟查询,不会访问真实库存: ```ts import { Tool } from "@yong_xie/lighter"; import * as z from "zod"; const getStock = new Tool({ name: "get_stock", description: "查询指定图书的库存。", parametersZodSchema: z.strictObject({ bookId: z.string() }), func: async ({ bookId }) => { // 在实际应用中,替换成数据库查询或外部 API 请求。 const stock = await Promise.resolve({ bookId, available: 12 }); return stock; }, }); ``` 把 `getStock` 加入 Agent 的 `tools` 即可使用。多个工具调用按顺序等待执行,当前不会并行执行。 ## 参数、结果与失败 - 名称应唯一,描述应说明工具的用途和适用条件。 - 参数必须使用 Zod 对象格式;校验通过后,`func` 收到的是 Zod 解析后的值。 - 返回可 JSON 序列化的值。成功结果通过 `JSON.stringify()` 转为 `toolOutput`,字符串也会带上 JSON 引号。 - 工具不存在、参数 JSON 无效、参数校验失败或函数抛出异常时,Agent 会向模型提供 `isError: true` 的工具消息,并继续循环。 工具失败不等于整个运行失败:模型可能调整参数再次尝试,也可能直接回复用户。工具以应用权限执行,业务权限检查需要写在工具内部。 完整参数和方法见 [Tool API](/zh/docs/reference/tools/)。 [← 配置 Agent](/zh/docs/guides/configuration/) · [下一步:管理对话历史 →](/zh/docs/guides/conversations/) --- 来源:/zh/docs/guides/conversations/ # 管理消息与对话历史 Lighter 不自动保存对话。每次运行传入已有历史,再把返回的新增消息追加进去,就能继续对话。 ## 继续上一轮对话 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Agent, Model, OpenAIProvider, UserMessage, type BaseMessages, } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); const agent = new Agent({ model }); const history: BaseMessages = [new UserMessage("给一家月球书店起个名字。")]; const first = await agent.run({ messages: history }); if (first.status === "failed") throw first.error; // 返回值只包含本次新增消息,手动追加以保留上下文。 history.push(...first.messages); history.push(new UserMessage("再给我三个风格相近的名字。")); const second = await agent.run({ messages: history }); if (second.status === "failed") throw second.error; history.push(...second.messages); console.log(second.messages); ``` Agent 不会向传入的数组追加消息。只保存最后一段回复会丢失工具调用与结果,因此有工具时也应保留完整的新增消息。 ## 按角色读取消息 | `role` | 消息类型 | 主要内容 | | --- | --- | --- | | `system` | `SystemMessage` | 系统指令,文本在 `content` | | `user` | `UserMessage` | 用户输入,文本在 `content` | | `assistant` | `AssistantMessage` | 回复 `content`、工具调用 `toolCalls`、可选的 `reasoning` | | `tool` | `ToolMessage` | 工具输出 `toolOutput`、调用标识 `callId`、错误标记 `isError` | 工具调用可能没有助手文本,读取消息前应先判断 `role`。完整字段见 [Messages API](/zh/docs/reference/messages/)。 ## 多个对话 同一个 Agent 可以处理多次独立运行,但每个对话应使用各自的历史数组。工具、钩子或应用状态中的共享数据,仍需由应用处理并发访问。 ## 保存与恢复的边界 历史可以由应用写入数据库。OpenAI 和 Claude 返回的消息还可能携带后续请求需要的原生上下文;普通 JSON 序列化、克隆或重新创建消息不会完整保留这些信息。 对于依赖推理上下文的工具往返,优先保留 Provider 返回的原始消息实例。持久化恢复需要应用额外设计,不能仅凭保存 `content` 和 `reasoning` 就保证恢复原生上下文。 [← 添加工具](/zh/docs/guides/tools/) · [下一步:使用流式输出 →](/zh/docs/guides/streaming/) --- 来源:/zh/docs/guides/streaming/ # 使用流式输出 用 `stream()` 代替 `run()`,可以在模型生成过程中逐步接收文本。两者执行相同的工具循环。 ## 接收文本和最终结果 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Agent, Model, OpenAIProvider, UserMessage } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); const agent = new Agent({ model }); for await (const event of agent.stream({ messages: [new UserMessage("用三句话介绍一家月球书店。")], })) { // 最终结果带有 status,与增量事件的结构不同。 if ("status" in event) { process.stdout.write("\n"); if (event.status === "failed") { console.error("运行失败:", event.error); process.exitCode = 1; } // 要保存历史时,使用最终结果中的完整消息。 console.log(event.messages); } else if (event.type === "text_delta") { process.stdout.write(event.text); } } ``` ## 会收到什么 | 内容 | 结构 | 用途 | | --- | --- | --- | | 文本增量 | `{ type: "text_delta", text }` | 逐步展示回复 | | 推理增量 | `{ type: "reasoning_delta", text }` | 展示 Provider 提供的推理内容或摘要;不一定存在 | | 最终结果 | `{ status, messages, error? }` | 判断完成状态,读取完整消息或失败原因 | 最终结果通过 `yield` 提供,正常消费到结束时可以在 `for await` 中收到。当前 Agent 在内部消费 Provider 的 `message` 事件,不把它作为单独事件传出。 有工具时,增量文本可能来自不同的模型轮次。工具消息和完整助手消息统一放在最终结果里;不要再把最终回复文本重复追加到已经展示的增量文本中。 ## 失败与提前停止 流中断时,Agent 返回失败结果,并保留本次已经完成的消息。只收到部分文本、尚未形成完整消息的内容,不会放进 `messages`。 可以用 `break` 停止消费流,这会关闭迭代器,并阻止之后的工具与模型调用。提前停止后不会收到最终结果,也不能据此取消已经在运行的工具。 `afterModel` 钩子处理完整助手消息。如果它修改回复,最终消息可能与之前展示的增量文本不同。 [← 管理对话历史](/zh/docs/guides/conversations/) · [下一步:使用钩子 →](/zh/docs/guides/hooks/) --- 来源:/zh/docs/guides/hooks/ # 使用生命周期钩子 钩子让你在特定时机读取或修改请求与结果,适合日志、输入预处理和回复检查。 ## 记录模型请求 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Agent, BeforeModelHook, Model, OpenAIProvider, UserMessage, } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); const logRequest = new BeforeModelHook({ hookName: "log-model-request", hookFunction: (request) => { // 每次调用模型前都会执行,包括工具返回后的后续轮次。 console.log("消息数:", request.messages.length); // 不返回值,继续使用当前请求。 }, }); const agent = new Agent({ model, hooks: { beforeModel: logRequest } }); const result = await agent.run({ messages: [new UserMessage("你好!")] }); console.log(result); ``` ## 执行时机 | 配置项 | 钩子类 | 接收的值 | 返回替换值时影响什么 | | --- | --- | --- | --- | | `beforeAgent` | `BeforeAgentHook` | `AgentRequest` | 本次运行的输入消息 | | `beforeModel` | `BeforeModelHook` | `ModelRequest` | 本次模型请求,包括消息和工具定义 | | `afterModel` | `AfterModelHook` | `AssistantMessage` | 加入上下文并用于工具执行的助手消息 | | `afterAgent` | `AfterAgentHook` | `BaseMessages` | 最终返回的新增消息数组 | 一次运行先调用 `beforeAgent`,每个模型轮次执行 `beforeModel → Provider → afterModel`,循环成功结束后再执行 `afterAgent`。 `beforeAgent` 收到的请求尚未加入 Agent 的系统提示词;`beforeModel` 收到的上下文已经包含系统提示词。 ## 修改请求 下面的钩子可以替换前面示例中的 `logRequest`: ```ts import { BeforeModelHook, SystemMessage } from "@yong_xie/lighter"; const requireChinese = new BeforeModelHook({ hookName: "require-chinese", hookFunction: (request) => ({ ...request, // 返回新请求,仅为这次模型调用增加一条指令。 messages: [...request.messages, new SystemMessage("请使用中文回答。")], }), }); ``` 所有钩子都支持同步或异步函数。返回 `undefined` 时保留当前值;返回新值时使用新值。每个配置项接收一个钩子实例,需要多个操作时在回调内组合。 ## 注意事项 - 钩子抛出的异常会使 Agent 返回失败结果。直接调用 Model 时,模型钩子异常会向调用方抛出。 - 当前实现中,模型调用失败或流被提前停止时,不会执行 `afterAgent`。必须执行的资源清理应放在应用的 `finally` 中。 - `beforeModel` 修改的是发给模型的工具定义,不会给 Agent 注册新的工具函数。 - 请求数组独立不代表消息对象被深拷贝,优先返回新数组,避免原地修改共享消息。 - OpenAI 和 Claude 的原始助手消息携带原生上下文。替换、克隆或修改它们可能丢失推理重放信息;只读取日志时应保留原消息。 完整回调类型见 [Hooks API](/zh/docs/reference/hooks/)。 [← 使用流式输出](/zh/docs/guides/streaming/) · [下一步:处理错误 →](/zh/docs/guides/errors/) --- 来源:/zh/docs/guides/errors/ # 处理错误 Agent 用 `status` 表示运行是否完成。模型或钩子失败时,先检查结果,再读取 `error`。 ## 判断运行结果 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Agent, Model, ModelError, OpenAIProvider, UserMessage } from "@yong_xie/lighter"; const agent = new Agent({ model: new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )), }); const result = await agent.run({ messages: [new UserMessage("你好!")] }); if (result.status === "failed") { // error 是 unknown,先判断类型再读取错误字段。 if (result.error instanceof ModelError) { console.error(result.error.code, result.error.message); } else { console.error("运行失败:", result.error); } // 如果之前执行过工具,这里可能仍有已完成的消息。 console.log(result.messages); process.exitCode = 1; } else { console.log(result.messages); } ``` 内置 Provider 将识别到的 SDK 错误归类为 `ModelError`。应用或钩子抛出的其他值会按原样保留,不保证一定是 `Error`,也可能是 `undefined`。 ## 工具失败与运行失败 工具名不存在、参数无效或工具函数抛出异常时,Agent 会生成 `isError: true` 的 `ToolMessage`,交给模型继续处理。这不会直接将运行状态改为 `failed`。 因此,`completed` 表示循环正常结束,并不保证所有工具都执行成功,也不代表答案一定正确。 ## 保留已经完成的进度 模型在后续轮次失败时,结果中的 `messages` 仍保留本次已经完成的助手与工具消息。应用可以保存这些消息,再决定是否继续。 继续时应保留完整的工具调用与结果配对。不能仅凭失败状态就重跑整个任务,否则可能重复执行已经产生副作用的工具。 ## 常见排查 | 情况 | 检查项 | | --- | --- | | `auth_error` | API key、账号权限 | | `invalid_request` | 模型 ID、参数、消息和工具定义 | | `rate_limit` | 请求频率、并发量,以及服务返回的限流信息 | | `network_error` | 网络、代理与服务连通性 | | `bad_response` | 是否生成不完整、流中断或出现不支持的响应 | | 模块加载失败 | ESM 配置与 TypeScript 运行方式 | Lighter 没有独立的重试策略;底层 SDK 可能有自己的重试行为。完整错误码见 [Errors API](/zh/docs/reference/errors/)。 直接调用 `Model` 或 Provider 时,错误会抛出;构造 Provider 时的配置错误也可能立即抛出。应用可用 `try/catch` 处理这些情况。 [← 使用钩子](/zh/docs/guides/hooks/) · [下一步:选择 Provider →](/zh/docs/providers/) --- 来源:/zh/docs/providers/ # 选择和配置 Provider Provider 负责连接模型服务。更换 Provider 后,仍然通过 `Model` 和 `Agent` 使用相同的消息与工具接口。 ## 内置 Provider | 类 | `name` | 当前接入方式 | 可选配置 | | --- | --- | --- | --- | | `OpenAIProvider` | `"openai"` | OpenAI Responses API | `reasoning`、`maxOutputTokens` | | `ClaudeProvider` | `"claude"` | Anthropic Messages API | `thinking`、`maxOutputTokens` | | `DeepSeekProvider` | `"deepseek"` | 使用 OpenAI SDK 调用 DeepSeek Responses 接口 | 无第三个配置参数 | 以下示例只创建 Model。将选用的 `model` 传给 `new Agent({ model })` 即可运行。 ## OpenAI ```ts import { Model, OpenAIProvider } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, // 可选:限制本次模型生成的输出长度。 { maxOutputTokens: 2048 }, )); ``` `maxOutputTokens` 映射为 `max_output_tokens`,省略时由服务决定。Provider 固定发送 `store: false`。 `reasoning` 使用底层 SDK 的配置类型。需要推理摘要时,可以在支持该能力的模型上设置 `{ reasoning: { summary: "auto" } }`。当前适配器从摘要提取 `reasoning` 与 `reasoning_delta`,未配置摘要时不一定有推理内容可读。 ## Claude 先设置 `ANTHROPIC_API_KEY` 和 `ANTHROPIC_MODEL` 环境变量,再创建 Provider: ```ts import { ClaudeProvider, Model } from "@yong_xie/lighter"; const model = new Model(new ClaudeProvider( process.env.ANTHROPIC_API_KEY!, process.env.ANTHROPIC_MODEL!, { maxOutputTokens: 8192, // 仅用于支持此配置的模型;预算必须小于输出上限。 thinking: { type: "enabled", budget_tokens: 2048 }, }, )); ``` `maxOutputTokens` 默认 `8192`,必须是正的安全整数。显式启用 `thinking` 时,`budget_tokens` 必须是至少 `1024` 且小于输出上限的安全整数;不满足时,构造函数立即抛出 `ModelError`。 不需要思考配置时,省略 `thinking`。具体选项是否可用取决于所选模型。 ## DeepSeek 先设置 `DEEPSEEK_API_KEY` 环境变量: ```ts import { DeepSeekProvider, Model } from "@yong_xie/lighter"; const model = new Model(new DeepSeekProvider( process.env.DEEPSEEK_API_KEY!, // 当前构造参数的类型接受此 ID 和 deepseek-v4-pro。 "deepseek-flash", )); ``` 当前实现将地址固定为 `https://api.deepseek.com`,通过 SDK 的 `responses.create()` 发起请求。构造参数的模型 ID 限定为 `"deepseek-flash" | "deepseek-v4-pro"`。 以上描述的是当前代码适配方式与类型限制,不代表所有账号或真实服务请求都已通过兼容性验证。内置 Provider 的测试使用模拟 HTTP,不访问真实模型服务。 ## 历史与推理上下文 OpenAI 和 Claude 会在返回的消息中保留原生响应信息,供后续请求重放。只有原始消息及相关内容未被修改时,才能沿用这条路径。 克隆、替换、序列化后重建或修改助手消息,会改为根据公开字段重建请求,可能丢失原生推理上下文。尤其是 Claude 的签名思考块,不能从 `reasoning` 文本重建,后续工具结果请求可能因此失败。 切换 Provider 时,统一接口可以继续使用;原有历史中的 Provider 特有上下文不保证跨服务兼容。 完整签名见 [Model 与 Provider API](/zh/docs/reference/model-and-providers/)。接入其他服务见[自定义 Provider](/zh/docs/providers/custom/)。 [← 处理错误](/zh/docs/guides/errors/) · [下一步:自定义 Provider →](/zh/docs/providers/custom/) --- 来源:/zh/docs/providers/custom/ # 自定义 Provider 接入其他模型服务时,实现 `Provider` 接口,再传给 `new Model(provider)`。Agent 不需要了解服务的具体 API。 ## 最小实现 下面的 `EchoProvider` 直接复述最后一条用户消息,不访问网络。可以保存为 `echo.ts`,按[快速开始](/zh/docs/getting-started/quickstart/)中的方式运行,也不需要 API key。 ```ts import { Agent, AssistantMessage, Model, ModelError, UserMessage, type Provider, type ProviderRequest, type ProviderStreamEvent, } from "@yong_xie/lighter"; export class EchoProvider implements Provider { readonly name = "echo"; async invoke(request: ProviderRequest): Promise { // 此示例只支持文本;不支持的能力应明确报错。 if (request.toolSpecs?.length) { throw new ModelError("EchoProvider 不支持工具。", "invalid_request"); } const lastUser = [...request.messages].reverse() .find((message) => message.role === "user"); return new AssistantMessage(`收到:${lastUser?.content ?? ""}`); } async *stream(request: ProviderRequest): AsyncGenerator { const message = await this.invoke(request); // 模拟增量输出;真实接入时应逐步转发服务返回的文本。 yield { type: "text_delta", text: message.content }; // 最后必须提供完整助手消息,供 Agent 决定是否继续循环。 yield { type: "message", message }; } } const agent = new Agent({ model: new Model(new EchoProvider()) }); const result = await agent.run({ messages: [new UserMessage("月球书店开业了!")] }); console.log(result.messages); ``` ## 接入真实服务 | 部分 | 需要完成的工作 | | --- | --- | | `name` | 提供稳定的服务标识 | | `invoke()` | 转换输入消息和工具定义,返回完整 `AssistantMessage` | | `stream()` | 转发文本与可选推理增量,最后返回一次完整 `message` 事件 | | 工具调用 | 将服务返回的调用转换为 `callId`、`toolName`、JSON 字符串 `arguments` | | 工具结果 | 将 `ToolMessage` 转换为对应调用的结果,保留调用标识与错误信息 | | 错误 | 识别失败或不完整响应,抛出合适的 `ModelError` | Provider 只负责通信和格式转换,工具执行由 Agent 完成。不要把单个对话的可变历史保存在共享 Provider 中。 ## 流式结束规则 `text_delta` 和 `reasoning_delta` 只是增量,不能替代最终的 `message`。流结束前没有完整消息时,Agent 会返回 `bad_response` 失败结果。 直接调用 Model 的流不会额外校验这个规则,因此自定义 Provider 自身也应检查服务是否正常结束,不要把断流当成成功。 ## 验证接入 至少检查普通回复、工具调用与结果往返、流式增量与结束事件,以及服务失败和中途断流。支持工具时,还应检查一次回复中多个工具调用的标识与参数是否完整。 接口签名见 [Model 与 Provider API](/zh/docs/reference/model-and-providers/)。 [← 内置 Provider](/zh/docs/providers/) · [下一步:直接调用 Model →](/zh/docs/advanced/model/) --- 来源:/zh/docs/advanced/model/ # 直接调用 Model 如果只需要一次模型调用,或希望自己控制工具执行过程,可以直接使用 `Model`。 ## 获取完整回复 以下示例沿用[快速开始](/zh/docs/getting-started/quickstart/)中的环境变量和运行方式: ```ts import { Model, OpenAIProvider, SystemMessage, UserMessage } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); try { const message = await model.invoke({ // Model 不会自动添加系统提示词,需要时自行传入。 messages: [ new SystemMessage("你是一位书店顾问,简短回答。"), new UserMessage("给月球书店起个名字。"), ], }); console.log(message.content); } catch (error) { // Model 直接抛出错误,不返回带 status 的 AgentResponse。 console.error("模型调用失败:", error); process.exitCode = 1; } ``` `invoke()` 返回单条 `AssistantMessage`。如果模型请求工具,调用信息在 `toolCalls` 中,Model 不会自动执行工具或发起后续轮次。 ## 接收流式回复 ```ts import { Model, OpenAIProvider, UserMessage } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); try { for await (const event of model.stream({ messages: [new UserMessage("你好!")] })) { if (event.type === "text_delta") { process.stdout.write(event.text); } else if (event.type === "message") { // 与 Agent 不同,Model 会直接提供完整 message 事件。 console.log("\n完整消息:", event.message); } } } catch (error) { console.error("流式调用失败:", error); process.exitCode = 1; } ``` Model 的流式事件包含 `text_delta`、`reasoning_delta` 和 `message`,没有最终的 `AgentResponse`。 ## 工具与钩子 将 `tool.getSpec()` 放入请求的 `toolSpecs`,可以让模型了解工具;收到调用后,参数解析、执行和再次请求都由应用负责。希望自动处理这些步骤时,使用 Agent。 `invoke(request, hooks?)` 和 `stream(request, hooks?)` 的第二个参数可传入 `beforeModel`、`afterModel`,用法与 [Agent 的模型钩子](/zh/docs/guides/hooks/)相同。直接调用 Provider 会绕过这层钩子。 [← 自定义 Provider](/zh/docs/providers/custom/) · [下一步:运行边界 →](/zh/docs/advanced/limits/) --- 来源:/zh/docs/advanced/limits/ # 运行边界与应用责任 Lighter 负责模型与工具之间的核心循环。应用需要根据任务补充存储、运行限制、权限与监控。 ## 当前能力边界 | 需求 | 当前行为 | | --- | --- | | 对话持久化、历史裁剪 | 由应用维护和传入历史 | | 最大模型调用次数 | 没有内置上限,可通过应用逻辑或钩子限制 | | 请求超时、取消 | 没有额外的统一控制接口,受底层 SDK 行为影响 | | 自动重试 | 没有 Lighter 自己的重试策略,底层 SDK 可能重试 | | 工具并行执行 | 当前按顺序等待执行 | | 工具隔离与权限 | 工具使用应用权限,没有内置沙箱 | | 日志、追踪与用量统计 | 没有内置监控面板或统一用量字段,可用钩子接入应用逻辑 | ## 限制模型调用次数 下面是应用层的限制方式。每次调用函数时创建独立计数器,避免多个请求相互影响: ```ts import { Agent, BeforeModelHook, Model, OpenAIProvider, type BaseMessages, type Tool, } from "@yong_xie/lighter"; const model = new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )); export async function runWithLimit(messages: BaseMessages, tools: Tool[] = []) { let calls = 0; const beforeModel = new BeforeModelHook({ hookName: "limit-model-calls", hookFunction: () => { // 第九次调用前抛出异常,Agent 会将本次运行标记为 failed。 if (++calls > 8) throw new Error("已达到 8 次模型调用上限。"); }, }); const agent = new Agent({ model, tools, hooks: { beforeModel } }); return agent.run({ messages }); } ``` 这只限制模型调用次数,不限制单次请求或单个工具的执行时间。工具已经产生的副作用不会因达到上限而撤销。 ## 工具的权限与副作用 Zod 校验只检查参数格式。访问数据库、文件系统或外部服务前,工具还需要检查当前用户的业务权限。 需要隔离时,在独立进程或服务中执行工具。涉及付款、发送消息等副作用时,由应用设计确认流程、幂等控制和审计记录。 ## 并发、停止与恢复 Agent 每次运行都有自己的消息数组,但工具、钩子和应用状态可能被共享。共享计数器、数据库连接或可变数据需要应用自行协调。 停止消费流可以阻止后续执行,不能保证中止正在运行的工具。失败结果能保留已完成的消息,但不提供崩溃恢复或副作用恰好执行一次的保证。 持久化也需要考虑 Provider 原生消息上下文,见[管理对话历史](/zh/docs/guides/conversations/)。 [← 直接调用 Model](/zh/docs/advanced/model/) · [下一步:Agent API →](/zh/docs/reference/agent/) --- 来源:/zh/docs/reference/agent/ # Agent API 本页对应 Lighter 0.1.0。用法见[配置 Agent](/zh/docs/guides/configuration/)与[流式输出](/zh/docs/guides/streaming/)。 ## 构造函数 `new Agent(config: AgentConfig)` | 配置项 | 类型 | 必填 | 默认值 | | --- | --- | --- | --- | | `model` | `Model` | 是 | 无 | | `tools` | `Tool[]` | 否 | `[]` | | `systemPrompt` | `SystemMessage` | 否 | `new SystemMessage("You are a helpful assistant.")` | | `hooks` | 钩子配置对象,见下表 | 否 | `{}` | | `hooks` 字段 | 类型 | | --- | --- | | `beforeAgent` | `BeforeAgentHook` | | `beforeModel` | `BeforeModelHook` | | `afterModel` | `AfterModelHook` | | `afterAgent` | `AfterAgentHook` | 四个钩子字段均可省略,每个字段接收一个实例。构造函数只保存配置,不发送模型请求。 ## `run()` ```text run(request: AgentRequest): Promise ``` `AgentRequest` 只有一个必填字段:`messages: BaseMessages`。Agent 将系统提示词与这些消息组成上下文,然后执行循环。 `AgentResponse` 的公开类型为: ```ts import type { BaseMessages } from "@yong_xie/lighter"; type AgentResponse = { status: "completed" | "failed"; messages: BaseMessages; error?: unknown; }; ``` | 字段 | 行为 | | --- | --- | | `status` | 循环及结束钩子成功完成时为 `completed`;捕获到模型或钩子异常时为 `failed` | | `messages` | 本次新增消息,不包含输入历史及自动加入的系统提示词;`afterAgent` 可以替换它 | | `error` | 失败时保留捕获的原始值,类型为 `unknown`,也可能为 `undefined` | 这个类型不是按状态区分字段的判别联合;检查 `status` 后,仍需判断 `error` 的具体类型。 输入消息数组不会被 Agent 追加,但消息对象并未深拷贝。工具错误以 `ToolMessage` 回传模型,不会直接使状态变为 `failed`。 ## `stream()` ```text stream(request: AgentRequest): AsyncGenerator ``` 通过异步迭代消费后才开始运行。正常消费到结束时,依次产生增量事件,最后 `yield` 一个 `AgentResponse`。 公开类型定义为 `AgentStreamEvent = ProviderStreamEvent | AgentResponse`。其中 `ProviderStreamEvent` 包含 `message` 分支,但当前 Agent 会在内部消费这一分支,实际向外提供的是: - `{ type: "text_delta", text: string }` - `{ type: "reasoning_delta", text: string }`,是否存在取决于 Provider 与模型配置。 - `{ status, messages, error? }`,本次运行的最终结果。 使用 `"status" in event` 区分最终结果。完整助手与工具消息放在最终结果中,部分增量文本不会作为完整消息保存。 提前停止迭代不会提供最终结果,也不会保证取消已经在执行的工具。 ## 最小示例 ```ts import { Agent, Model, OpenAIProvider, UserMessage } from "@yong_xie/lighter"; const agent = new Agent({ model: new Model(new OpenAIProvider( process.env.OPENAI_API_KEY!, process.env.OPENAI_MODEL!, )), }); const result = await agent.run({ messages: [new UserMessage("你好!")] }); // 先检查状态,再使用新增消息。 if (result.status === "completed") console.log(result.messages); else console.error(result.error); ``` [← 运行边界](/zh/docs/advanced/limits/) · [下一页:Model 与 Provider API →](/zh/docs/reference/model-and-providers/) --- 来源:/zh/docs/reference/model-and-providers/ # Model 与 Provider API 用法见[直接调用 Model](/zh/docs/advanced/model/)、[内置 Provider](/zh/docs/providers/)与[自定义 Provider](/zh/docs/providers/custom/)。 ## Model `new Model(provider: Provider)` | 成员 | 类型或签名 | 说明 | | --- | --- | --- | | `provider` | `readonly Provider` | 构造时传入的 Provider | | `invoke()` | `invoke(request: ModelRequest, hooks?): Promise` | 一次调用,返回完整助手消息 | | `stream()` | `stream(request: ModelRequest, hooks?): AsyncGenerator` | 转发 Provider 流式事件 | `hooks` 是可选对象,可含 `beforeModel?: BeforeModelHook` 和 `afterModel?: AfterModelHook`。两个方法都会等待相应钩子执行;流式 `afterModel` 仅处理完整的 `message` 事件。 Model 不添加默认系统提示词,不执行工具,不维护循环,也不把异常包装为 Agent 结果。Provider 或钩子异常会向调用方抛出。 ## 请求类型 `ModelRequest` 是 `ProviderRequest` 的类型别名: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `messages` | `BaseMessages` | 是 | 本次发送的完整上下文 | | `toolSpecs` | `ToolSpec[]` | 否 | 供模型了解的工具定义;不包含执行函数 | ## Provider 接口 ```ts import type { AssistantMessage, ProviderRequest, ProviderStreamEvent, } from "@yong_xie/lighter"; interface Provider { readonly name: string; invoke(request: ProviderRequest): Promise; stream(request: ProviderRequest): AsyncGenerator; } ``` 调用 Provider 的方法会绕过 Model 钩子。Provider 自身负责识别不完整或失败响应并抛出错误。 ## ProviderStreamEvent | `type` | 其他字段 | 含义 | | --- | --- | --- | | `text_delta` | `text: string` | 文本增量 | | `reasoning_delta` | `text: string` | 推理或摘要增量,按能力与配置提供 | | `message` | `message: AssistantMessage` | 完整助手消息,包含完整文本及工具调用 | Provider 的流在正常结束前应产生一次完整 `message` 事件。Agent 会用它推进循环;Model 直接转发,不额外验证是否缺失。 ## OpenAIProvider ```text new OpenAIProvider(apiKey, modelID, options?: OpenAIProviderOptions) ``` | 参数 | 类型 | 说明 | | --- | --- | --- | | `apiKey` | `string` | API key | | `modelID` | OpenAI SDK 的 `ResponseCreateParamsNonStreaming["model"]` 去除空值后的类型 | 模型 ID | | `options.reasoning` | OpenAI SDK 的 `ResponseCreateParamsNonStreaming["reasoning"]` | 推理选项,可省略 | | `options.maxOutputTokens` | `number` | 映射为 `max_output_tokens`,可省略 | `options` 默认 `{}`,`name` 为 `"openai"`,使用 Responses API,固定发送 `store: false`。推理文本来自摘要,原生上下文的保留规则见 [Provider 指南](/zh/docs/providers/)。 ## ClaudeProvider ```text new ClaudeProvider(apiKey: string, modelID: string, options?: ClaudeProviderOptions) ``` | 选项 | 类型 | 默认值或约束 | | --- | --- | --- | | `maxOutputTokens` | `number` | 默认 `8192`,必须是正的安全整数 | | `thinking` | Anthropic SDK 的 `MessageCreateParamsNonStreaming["thinking"]` | 默认不传入;能力取决于模型 | `options` 默认 `{}`,`name` 为 `"claude"`。`thinking.type` 为 `"enabled"` 时,`budget_tokens` 必须是至少 `1024` 且小于 `maxOutputTokens` 的安全整数,否则构造时抛出 `ModelError("...", "invalid_request")`。 系统消息被合并到请求的 `system` 字段。当前接受 `end_turn`、`tool_use`、`stop_sequence`、`refusal` 结束原因;其他结束原因作为 `bad_response` 抛出,并在 `responseStatus` 中保留,包括达到输出上限、`pause_turn` 等情况。 ## DeepSeekProvider ```text new DeepSeekProvider(apiKey: string, modelID: "deepseek-flash" | "deepseek-v4-pro") ``` `name` 为 `"deepseek"`。地址固定为 `https://api.deepseek.com`,使用 OpenAI SDK 的 Responses 接口,没有公开的第三个选项参数。 模型 ID 类型描述的是当前代码接受的值,不能作为真实服务兼容性的证明。 三个内置 Provider 均实现相同的 `invoke()` 与 `stream()`。`OpenAIProviderOptions` 和 `ClaudeProviderOptions` 是公开导出类型;内部模型 ID 类型别名不单独导出。 [← Agent API](/zh/docs/reference/agent/) · [下一页:Messages API →](/zh/docs/reference/messages/) --- 来源:/zh/docs/reference/messages/ # Messages API 消息通过 `role` 区分用途。用法见[管理对话历史](/zh/docs/guides/conversations/)。 ## SystemMessage 与 UserMessage ```text new SystemMessage(content: string) new UserMessage(content: string) ``` | 类 | `role` | 其他字段 | | --- | --- | --- | | `SystemMessage` | `"system"` | `content: string` | | `UserMessage` | `"user"` | `content: string` | `role` 和 `content` 均为只读属性。 ## AssistantMessage ```text new AssistantMessage(content: string, options?: { reasoning?: string; toolCalls?: ToolCall[]; }) ``` | 字段 | 类型 | 默认值或说明 | | --- | --- | --- | | `role` | `"assistant"` | 固定角色 | | `content` | `string` | 回复文本;工具调用时可以为空字符串 | | `reasoning` | `string \| undefined` | 可选推理内容或摘要,由 Provider 提供 | | `toolCalls` | `ToolCall[]` | 默认 `[]`,模型请求的工具调用 | 这些属性均声明为只读,但数组及其中的对象没有因此被深度冻结。原始消息可能还携带 Provider 特有的上下文,应避免随意修改或克隆。 ## ToolCall | 字段 | 类型 | 说明 | | --- | --- | --- | | `callId` | `string` | 本次调用的标识,用于匹配工具结果 | | `toolName` | `string` | 工具名称 | | `arguments` | `string` | JSON 字符串,尚未解析或校验的参数 | 三个字段均为只读。`arguments` 不是参数对象,Agent 会先 `JSON.parse()`,再交给工具校验。 ## ToolMessage ```text new ToolMessage({ callId: string, toolName: string, toolOutput: string, isError?: boolean, }) ``` | 字段 | 类型 | 默认值或说明 | | --- | --- | --- | | `role` | `"tool"` | 固定角色 | | `callId` | `string` | 对应 `ToolCall.callId` | | `toolName` | `string` | 工具名称 | | `toolOutput` | `string` | 工具输出文本,成功执行工具时由 JSON 序列化得到 | | `isError` | `boolean` | 默认 `false`;`true` 表示工具执行失败 | 这些属性均为只读。 ## BaseMessage 与 BaseMessages ```ts import type { SystemMessage, UserMessage, AssistantMessage, ToolMessage, } from "@yong_xie/lighter"; type BaseMessage = SystemMessage | UserMessage | AssistantMessage | ToolMessage; type BaseMessages = BaseMessage[]; ``` `BaseMessage` 是类型联合,不是可实例化的类。 ## 构造消息 下面手工构造一个工具往返,仅用于展示消息结构;实际运行时,助手与工具消息通常由 Provider 和 Agent 生成。 ```ts import { AssistantMessage, ToolMessage, UserMessage, type BaseMessages, } from "@yong_xie/lighter"; const messages: BaseMessages = [ new UserMessage("计算 19 + 23。"), new AssistantMessage("", { toolCalls: [{ callId: "call_add_1", toolName: "add", arguments: JSON.stringify({ a: 19, b: 23 }), }], }), // 工具结果通过 callId 与前面的调用对应。 new ToolMessage({ callId: "call_add_1", toolName: "add", toolOutput: "42" }), new AssistantMessage("19 + 23 = 42。"), ]; for (const message of messages) { // 工具消息读取 toolOutput,其他消息读取 content。 console.log(message.role, message.role === "tool" ? message.toolOutput : message.content); } ``` [← Model 与 Provider API](/zh/docs/reference/model-and-providers/) · [下一页:Tool API →](/zh/docs/reference/tools/) --- 来源:/zh/docs/reference/tools/ # Tool API `Tool` 封装参数校验、函数执行和工具结果。用法见[添加工具](/zh/docs/guides/tools/)。 ## 构造参数 `new Tool({ name, description, parametersZodSchema, func })` | 字段 | 类型 | 说明 | | --- | --- | --- | | `name` | `string` | 工具名称,建议在一个 Agent 中保持唯一 | | `description` | `string` | 向模型说明用途与适用条件 | | `parametersZodSchema` | `ZSchema extends z.ZodObject` | Zod 对象格式,用于生成 JSON Schema 与本地参数校验 | | `func` | `(args: z.infer) => unknown \| Promise` | 同步或异步执行函数 | 四项均必填。`Tool` 通过泛型推断 `func` 的参数类型;构造参数使用的内部 `ToolConfig` 类型没有单独导出。 实例的 `name`、`description`、`parametersZodSchema` 和 `func` 均是公开的只读属性。 ## `execute()` ```text execute(args: unknown, options: { callId: string }): Promise ``` 执行顺序为:使用 Zod 的同步 `parse()` 校验参数 → 等待 `func()` → 使用 `JSON.stringify()` 序列化结果。 - 成功时返回 `isError: false` 的 `ToolMessage`。 - 参数校验、函数执行或序列化抛出异常时,返回 `isError: true` 的 `ToolMessage`,`toolOutput` 为 `String(error)`。 - `callId` 由调用方提供,保留在结果中,用于关联工具调用。 请返回能被 `JSON.stringify()` 转成字符串的值。避免 `undefined`、函数、Symbol 等不能产生有效输出文本的值;BigInt、循环引用等可能导致序列化失败。 Agent 通常会自动调用这个方法。直接执行工具的示例: ```ts import { Tool } from "@yong_xie/lighter"; import * as z from "zod"; const add = new Tool({ name: "add", description: "计算两个数的和。", parametersZodSchema: z.strictObject({ a: z.number(), b: z.number() }), func: ({ a, b }) => a + b, }); // 参数先校验再执行;结果中的 toolOutput 是字符串 "42"。 const result = await add.execute({ a: 19, b: 23 }, { callId: "call_add_1" }); console.log(result); ``` ## `getSpec()` ```text getSpec(): ToolSpec ``` 将参数格式转换成 JSON Schema,返回可提供给模型的工具定义,不包含执行函数。转换使用 Zod 的输入模式;无法转换的格式可能在此处抛出异常。 `ToolSpec` 字段如下: | 字段 | 类型 | `getSpec()` 当前返回值 | | --- | --- | --- | | `type` | `"function"` | `"function"` | | `name` | `string` | 工具名称 | | `description` | `string` | 工具描述 | | `parameters` | `Record` | Zod 生成的 JSON Schema | | `strict` | `boolean` | 固定为 `false` | `strict: false` 是发送给 Provider 的定义选项,不会关闭工具执行前的本地 Zod 校验。 [← Messages API](/zh/docs/reference/messages/) · [下一页:Hooks API →](/zh/docs/reference/hooks/) --- 来源:/zh/docs/reference/hooks/ # Hooks API 四种钩子使用相同的构造方式。运行示例见[使用生命周期钩子](/zh/docs/guides/hooks/)。 ## 公共构造参数 `new BeforeModelHook({ hookName, description?, hookFunction })` 其他钩子类同样接收这三个字段: | 字段 | 必填 | 说明 | | --- | --- | --- | | `hookName: string` | 是 | 钩子标识,也是实例的公开只读属性 | | `description?: string` | 否 | 描述,也是实例的公开只读属性 | | `hookFunction` | 是 | 回调函数,具体类型见下表 | ## 回调类型 | 类 | 输入 | 允许的返回值 | | --- | --- | --- | | `BeforeAgentHook` | `AgentRequest` | `AgentRequest \| void \| Promise` | | `BeforeModelHook` | `ModelRequest` | `ModelRequest \| void \| Promise` | | `AfterModelHook` | `AssistantMessage` | `AssistantMessage \| void \| Promise` | | `AfterAgentHook` | `BaseMessages` | `BaseMessages \| void \| Promise` | 所有回调都会被等待。返回 `undefined` 时保留当前值;返回替换值时采用替换值。当前实现使用空值合并,但类型不允许返回 `null`。 `AfterAgentHook` 的输入与返回值是消息数组,不是完整的 `AgentResponse`。 ## `execute()` | 类 | 公开方法 | | --- | --- | | `BeforeAgentHook` | `execute(request: AgentRequest): Promise` | | `BeforeModelHook` | `execute(request: ModelRequest): Promise` | | `AfterModelHook` | `execute(message: AssistantMessage): Promise` | | `AfterAgentHook` | `execute(messages: BaseMessages): Promise` | 这些方法由 Agent 或 Model 自动调用。直接调用时,回调异常会向调用方抛出。 ## 调用时机与失败 - `beforeAgent`:每次运行开始时一次,尚未加入 Agent 的系统提示词。 - `beforeModel`:每次模型请求前,包括工具结果返回后的下一次调用。 - `afterModel`:每次获得完整助手消息后,早于 Agent 执行其中的工具调用。流式增量不经过此钩子。 - `afterAgent`:当前在循环成功结束后一次,早于 Agent 包装最终结果。 Agent 捕获这些钩子的异常后返回或产生失败结果;直接调用 Model 时,模型钩子异常直接抛出。当前模型失败或提前结束流时跳过 `afterAgent`,不能将它用作保证执行的资源清理钩子。 ## 示例 ```ts import { AfterAgentHook, AssistantMessage } from "@yong_xie/lighter"; const appendNotice = new AfterAgentHook({ hookName: "append-notice", hookFunction: (messages) => [ ...messages, // 返回消息数组,Agent 会将它放入最终响应的 messages。 new AssistantMessage("本次回复仅供参考。"), ], }); ``` 将此实例传给 `new Agent({ model, hooks: { afterAgent: appendNotice } })`。此钩子只影响最终返回的消息,不会追加一次模型调用,也不会将内容作为流式增量发送。 [← Tool API](/zh/docs/reference/tools/) · [下一页:Errors API →](/zh/docs/reference/errors/) --- 来源:/zh/docs/reference/errors/ # Errors API 用法见[处理错误](/zh/docs/guides/errors/)。内置 Provider 用 `ModelError` 表示可识别的模型调用错误。 ## ModelError ```text new ModelError(message: string, code: ModelErrorCode, options?: { httpStatus?: number; responseStatus?: string; }) ``` `ModelError` 继承 `Error`,并提供以下只读字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `name` | `"ModelError"` | 固定名称 | | `code` | `ModelErrorCode` | 统一错误分类 | | `httpStatus` | `number \| undefined` | 服务的 HTTP 错误状态码 | | `responseStatus` | `string \| undefined` | 生成状态或结束原因,例如 `incomplete`、`max_tokens` | `message` 和 `stack` 等字段继承自 `Error`。`httpStatus` 与 `responseStatus` 相互独立,网络错误可能两者都没有。 ## ModelErrorCode `ModelErrorCode` 是以下字符串的类型联合: | 错误码 | 含义 | | --- | --- | | `auth_error` | 认证或访问权限失败 | | `rate_limit` | 服务限流 | | `invalid_request` | 请求格式、参数或模型等不合法 | | `network_error` | 连接或网络错误 | | `server_error` | 模型服务端失败 | | `bad_response` | 响应不完整、失败、无法解析或存在不支持的内容 | | `aborted` | 请求取消或服务返回已取消状态 | | `unknown` | 未归入其他类别的 SDK 错误 | ## 在自定义 Provider 中使用 ```ts import { ModelError } from "@yong_xie/lighter"; // 示例:构造并检查一个生成不完整的错误。 const error = new ModelError("模型输出达到上限,生成未完成。", "bad_response", { responseStatus: "incomplete", }); console.log(error.code, error.responseStatus, error.message); ``` 自定义 Provider 应在服务出错时抛出适当错误。使用统一分类后,应用可以按 `code` 处理不同服务的常见失败。 ## 与 AgentResponse 的关系 `AgentResponse.error` 的类型仍是可选的 `unknown`。只有确认 `error instanceof ModelError` 后,才能直接读取 `code`、`httpStatus` 等字段。 直接调用 Model 或 Provider 时,错误向调用方抛出;通过 Agent 运行时,被捕获的模型和钩子错误放入失败结果。工具函数内部的错误则通过 `ToolMessage.isError` 表示。 [← Hooks API](/zh/docs/reference/hooks/) · [返回文档首页](/zh/docs/)