新闻详情

新闻详情

首页 / 资讯中心 / 详情

Genkit 代理 API 实战:TypeScript 多回合 AI 代理开发指南

发布时间:2026/9/28 22:45:42来源:尧图网络
Genkit 代理 API 实战:TypeScript 多回合 AI 代理开发指南
多回合代理这件事真正上手做过的人都知道难点从来不在让模型回一句话而在于让它在多轮交互里记住上下文、按需调用工具、把中间状态存下来还要在下一轮里接着用。Genkit 的代理 API 就是冲着这个场景来的它把多回合这件事从你自己手写状态机变成了框架层面能托底的能力。这篇内容我打算把用 Genkit 代理 API 搭一个多回合 AI 代理的完整思路拆开讲从它到底解决了什么问题、核心概念怎么理解到 TypeScript 项目里怎么落地、Firestore 怎么接、工具怎么挂、状态怎么续再到实测中容易翻车的地方。适合已经写过简单 LLM 调用、想往能记住事、能干活的代理方向走的人也适合正在用 TypeScript 做 AI 应用、纠结要不要引入框架的开发者。1. 先搞清楚 Genkit 代理 API 到底在解决什么1.1 单次调用和多回合代理的本质差距大部分人第一次接触大模型写的都是这种代码拼一个 prompt发一次请求拿一次回复结束。这种模式在问答、翻译、总结这类一问一答的场景里够用但一旦你要做的是帮用户订一张明天下午的机票如果没票就换一班顺便把行程加到日历里单次调用立刻就不够看了。因为这件事天然需要多步先理解意图再查航班发现没票要重新决策最后执行写入。每一步的结果都要影响下一步这就是多回合。多回合的核心矛盾在于状态。模型本身是无状态的你每次调用它它都当自己是第一次见你。所谓记住上下文本质是你把历史消息重新塞回去。手动做这件事短对话还行轮次一多消息数组越来越长你还得自己判断哪些该留、哪些该丢、工具调用的结果怎么拼回去。Genkit 代理 API 的价值就是把这套消息管理 工具循环 状态持久化的脏活收敛到框架里让你专注在业务逻辑上。我自己的判断标准很简单如果你的交互超过 3 轮或者中间需要调用外部工具或者需要跨会话记住用户偏好那就别硬写裸调用直接上代理框架。省下来的不是几十行代码而是后面无穷无尽的边界 bug。1.2 Genkit 的定位和它跟裸调 SDK 的区别Genkit 是 Google 开源的一套 AI 应用开发框架TypeScript 和 Go 都有支持。它最容易被误解的一点是很多人以为它只是个调模型的封装其实它更像一个编排层。它管的是 flow流程、tool工具、retriever检索、prompt提示模板这些概念之间的关系模型调用只是其中一环。代理 API 是它在这套编排能力上加的一层专门处理带工具的对话循环。跟裸调 SDK 比它多做了几件事一是把工具定义标准化你写一个函数、给个 schema它自动转成模型能理解的工具描述二是自动处理模型要求调用工具 → 执行工具 → 把结果喂回模型 → 模型继续这个循环你不用自己写 while三是把对话状态抽象成可序列化的结构方便你存到 Firestore 这类外部存储里。这里有个认知上的坑要提前说Genkit 不是要替代你的业务代码它是把模型和工具之间的胶水标准化了。你的业务逻辑、数据库操作、权限校验还是得自己写只不过现在它们以工具的形式被代理调用。1.3 什么场景适合用代理 API什么场景别硬上不是所有 AI 功能都值得上代理。我见过有人做一个把这段中文翻译成英文的功能也硬套代理框架结果引入一堆依赖代码反而更复杂。判断标准我总结成三条需要多步决策任务不能一次完成中间要根据结果调整方向。比如客服工单处理、数据分析问答、行程规划。需要调用外部能力要查数据库、调 API、读写文件。工具调用是代理的核心价值。需要跨轮次记忆用户会在多轮里逐步补充信息或者你需要在会话之间保留状态。反过来如果只是单轮生成、不需要外部数据、不需要记忆那直接用 generate 类的单次调用就够了别为了用框架而用框架。我踩过这个坑一个纯文本改写功能套了代理调试成本翻倍最后又拆回单次调用。2. 把核心概念理顺代理、工具、会话状态三件套2.1 代理不是更聪明的模型而是带循环的编排器很多人对代理这个词有误解以为它是某种更强的模型。不是的。代理是一个控制循环它拿着当前对话历史问模型下一步干嘛模型可能直接回答也可能说我要调用某个工具代理就去执行工具把结果追加到历史里再问模型一次直到模型给出最终回答或者达到轮次上限。理解这一点很关键因为它决定了你调试时的思路。代理出问题往往不是模型笨而是循环里的某一环断了工具描述模型没看懂、工具执行报错没被正确处理、历史消息拼错了、轮次上限设太低了。把代理当成一个while 循环 消息数组来看问题就好定位多了。Genkit 里定义代理通常用defineTool定义工具用 flow 或者专门的代理构造来组织循环。工具的定义包含名字、描述、输入 schema、输出 schema 和一个执行函数。描述这块特别重要模型就是靠描述来判断什么时候该用这个工具的写得含糊模型就乱调或者不调。2.2 工具定义的质量直接决定代理的智商我做过一个对比实验同一个代理工具描述写得随便 vs 写得精细任务成功率差了一大截。工具描述要回答三个问题这个工具干什么、什么时候用、输入要什么格式。举个例子一个查订单的工具描述写查询订单就太弱了写成根据订单号查询订单的当前状态和物流信息当用户询问订单进度、物流、是否发货时使用输入为订单号字符串就清楚多了。输入 schema 也不能马虎。Genkit 用 Zod 这类 schema 库来定义工具输入输出好处是模型拿到的工具描述里会带上字段说明而且执行前框架会做校验。我建议每个字段都写清楚含义和格式尤其是枚举值、日期格式这种容易出错的。schema 写得好等于给模型加了一层护栏。还有一个经验工具粒度要适中。太粗一个工具干十件事模型不知道该传什么参数太细几十个工具模型选择困难还容易串。我一般控制在单个代理 5 到 15 个工具之间超过就考虑拆成多个代理或者做工具分组。2.3 会话状态为什么必须外置到 Firestore代理的对话历史会随着轮次增长如果只放在内存里服务一重启就没了多实例部署时还会出现这轮请求打到 A 实例、下轮打到 B 实例历史对不上的问题。所以生产环境里会话状态必须外置。Firestore 是个自然的选择尤其你在用 Google 生态的话。它有几个好处文档模型天然适合存一个会话一个文档、支持实时更新、有现成的 SDK、按量计费对小规模应用友好。存的内容一般包括会话 ID、消息历史数组、当前状态标记、创建和更新时间、以及你自定义的元数据比如用户 ID、代理类型。这里有个设计决策要提前想清楚历史消息是全存还是只存摘要。全存简单但轮次多了文档会变大而且每次都要把全部历史发给模型token 成本高。只存摘要省 token但会丢细节。我的做法是折中保留最近 N 轮完整消息更早的做摘要压缩N 一般取 10 到 20。这个策略后面在状态管理那节会展开讲。3. TypeScript 项目里把代理跑起来3.1 环境准备和依赖安装的取舍先说环境。Node 版本建议 20 以上TypeScript 用 5.x。这里插一句最近社区里在讨论baseUrl和moduleResolutionnode10这些选项被标记弃用、未来版本要移除的事。如果你是新项目别再用node10这种老解析策略了直接上bundler或者nodenext省得以后迁移。baseUrl能不用就不用路径别名用paths配合现代解析策略一样能做。依赖方面核心是 Genkit 本体和它的模型插件。模型插件取决于你用哪家模型Genkit 支持多家。另外要装 Zod 做 schema 定义装 Firestore 的 SDK 做状态存储。开发期建议装 tsx 或者用 Node 的原生 TS 支持来跑别每次都编译。npm install genkit genkit-ai/googleai zod google-cloud/firestore npm install -D typescript tsx types/nodetsconfig 里我一般这么配关键几项target用 ES2022module用 NodeNext 或 ESNextmoduleResolution跟着 module 走strict打开skipLibCheck打开省时间。strict 一定要开代理代码里类型错误往往对应着运行时 bug别偷懒。3.2 初始化 Genkit 和配置模型初始化的代码不长但有几个点容易忽略。第一是 API key 的管理绝对不要硬编码在代码里用环境变量。第二是插件的注册顺序模型插件要在使用前注册好。第三是开发期的调试开关Genkit 有开发者 UI 可以看每次调用的输入输出调代理的时候非常有用生产环境记得关掉。import { genkit } from genkit; import { googleAI } from genkit-ai/googleai; export const ai genkit({ plugins: [googleAI()], model: googleai/gemini-2.0-flash, });模型选择上代理场景我更倾向用响应快、工具调用能力稳的模型而不是一味追求最大最强的。因为代理是多轮循环每轮都调一次模型延迟会累加。一个中等规模但工具调用靠谱的模型体验往往比一个超大但每轮慢好几秒的模型好。这个取舍要根据你的场景实测别照搬别人的推荐。3.3 定义第一个工具并挂到代理上工具定义是代理的核心工作。我拿一个查询订单状态的工具举例把关键点都标出来。import { z } from zod; import { ai } from ./genkit-config; export const queryOrderTool ai.defineTool( { name: queryOrder, description: 根据订单号查询订单的当前状态、物流进度和预计送达时间。当用户询问订单进度、是否发货、物流信息时调用。, inputSchema: z.object({ orderId: z.string().describe(订单号通常是 12 位数字字符串), }), outputSchema: z.object({ status: z.string().describe(订单状态如 pending/shipped/delivered), logistics: z.string().describe(最新物流描述), eta: z.string().describe(预计送达时间ISO 格式), }), }, async ({ orderId }) { const order await fetchOrderFromDB(orderId); if (!order) { return { status: not_found, logistics: , eta: }; } return { status: order.status, logistics: order.latestLogistics, eta: order.eta, }; } );注意几个细节描述里明确写了什么时候调用这是给模型看的输入输出 schema 每个字段都有 describe模型能理解字段含义执行函数里对查不到的情况返回了结构化的结果而不是抛异常因为抛异常会打断代理循环返回结构化结果让模型自己决定怎么跟用户说体验更好。工具挂到代理上一般是在构造代理或者 flow 的时候把工具数组传进去。Genkit 会自动把这些工具转成模型能理解的格式。挂的时候注意工具名要唯一别跟内置的冲突。4. 多回合的关键状态怎么存、怎么续、怎么控4.1 用 Firestore 存会话的文档结构设计会话文档的设计直接影响后面查询和扩展的难易。我一般用这样的结构文档 ID 就是会话 ID字段包括 userId、agentType、messages 数组、summary 字段、createdAt、updatedAt、以及一个 metadata 对象放扩展信息。messages 数组里每条消息包含 roleuser/model/tool、content、timestamp如果是工具调用还要带 toolName 和 toolInput。summary 字段存早期对话的压缩摘要。这样设计的好处是查一个会话就是读一个文档简单直接要按用户查所有会话给 userId 建索引就行。有个坑要提醒Firestore 单个文档有大小限制消息全堆一个文档里长会话迟早会撞上限。所以要么定期归档老消息到子集合要么就用前面说的摘要策略控制文档大小。我一般会在写入时检查消息数量超过阈值就触发压缩。4.2 消息历史的裁剪与摘要策略这是多回合代理里最容易被低估的一环。直接把全部历史发给模型短期没问题长期一定出问题token 成本飙升、模型被无关历史干扰、响应变慢。裁剪策略我实践下来比较稳的是滑动窗口 摘要。具体做法保留最近 N 轮完整消息N 取 10 到 20更早的消息用一次模型调用压缩成一段摘要存到 summary 字段。每次构造请求时把 summary 作为系统消息的一部分加上最近 N 轮完整消息一起发给模型。这样既保留了长期记忆的脉络又控制了 token。摘要的 prompt 也有讲究别简单说总结一下要明确告诉模型保留用户的关键需求、已确认的事实、未完成的任务去掉寒暄和重复内容。摘要质量直接影响代理的长期表现值得多调几次。4.3 轮次上限和循环终止条件代理循环必须有终止条件否则模型可能陷入调工具 → 不满意 → 再调的死循环烧钱又慢。Genkit 的代理一般支持设置最大轮次我建议设 5 到 10 轮。超过上限还没结束就返回一个兜底回复比如这个问题比较复杂我先记录一下稍后给你详细答复。除了轮次上限还要处理几种终止情况模型给出最终回答正常结束、工具连续报错应该中断并告知用户、达到 token 预算上限。这些条件最好在代理配置里显式设置别指望模型自己收敛。我见过没设上限的代理遇到一个模糊问题来回调了二十多次工具账单直接起飞。5. 实测中那些文档不会告诉你的坑5.1 工具描述含糊导致模型乱调工具这是我踩得最狠的一个坑。早期我写工具描述很随意结果模型经常在不该调的时候调或者该调 A 工具却调了 B。排查了半天才发现问题出在描述上。模型判断用哪个工具完全依赖描述文本描述里没写清楚适用场景它就只能猜。解决办法前面提过描述要包含干什么、什么时候用、输入格式。另外一个小技巧如果两个工具功能相近在描述里明确写当 X 情况时用本工具不要用 Y 工具用否定式帮模型区分。实测下来加了这种区分后误调率明显下降。5.2 工具执行抛异常打断整个循环工具执行函数里抛异常如果没被框架捕获会直接中断代理循环用户看到的就是一个报错。更糟的是有些异常是业务上正常的比如查不到数据、参数不合法这些不该当成系统错误。我的做法是工具内部对可预期的失败返回结构化结果比如{ error: not_found, message: ... }让模型自己决定怎么跟用户解释只有真正的系统异常数据库连不上、网络超时才抛出去并且在外层做统一捕获和重试。这样代理的健壮性会好很多。5.3 状态并发写入导致历史错乱多回合代理在并发场景下有个隐蔽的坑同一个会话如果同时来了两个请求两个请求都读到旧历史、各自追加消息、再写回去后写的会覆盖先写的导致丢消息。用户快速连发两条消息时就可能触发。解决办法有两种一是用 Firestore 的事务transaction做读改写保证原子性二是给会话加一个处理中的锁标记同一会话串行处理。事务更通用但要注意事务里不能做太重的操作。我一般用事务配合乐观锁版本号来检测冲突冲突了就重试。5.4 模型返回的工具调用格式不合法偶尔模型会返回格式不对的工具调用比如参数缺字段、类型不对、或者调了一个不存在的工具。这在工具多、schema 复杂的时候更容易出现。Genkit 的 schema 校验能挡掉一部分但挡不住调了不存在的工具这种。我的处理是在循环里对每次工具调用做校验校验不过就把错误信息作为工具结果喂回模型让它重新决策。这相当于给模型一次自我纠正的机会。实测下来大部分格式问题模型能在下一轮自己修好。如果连续几次都修不好就中断并返回兜底回复。6. 让代理真正好用的几个进阶思路6.1 给代理加记忆而不只是历史历史是这次对话说了什么记忆是这个用户是谁、有什么偏好。两者不是一回事。一个真正好用的代理应该能跨会话记住用户的基本信息和偏好比如这个用户偏好简洁回复这个用户是 VIP优先处理。实现上可以在 Firestore 里单独存一份用户档案代理在处理请求时先读用户档案把关键信息注入到系统提示里。这样即使用户开了一个全新会话代理也能认识他。这个能力对客服、助手类应用价值很大做起来也不复杂就是多一次读取和一次提示拼接。6.2 工具结果的二次加工工具返回的原始数据往往不适合直接给模型看。比如数据库返回一个包含几十个字段的对象全塞给模型既浪费 token 又干扰判断。我习惯在工具执行函数里做一层加工只返回模型真正需要的字段并且用自然语言组织一下。举个例子查订单返回的原始数据有十几个字段但模型只需要状态、物流、预计送达。工具就直接返回这三个甚至可以拼成一句话订单已发货最新物流是 XX预计 X 月 X 日送达。这样模型拿到就能直接用不用再解析。加工这层做得好代理的回答质量会明显提升。6.3 用开发者 UI 做代理调试Genkit 带的开发者 UI 是调代理的利器。它能展示每次模型调用的完整输入输出、工具调用的参数和结果、整个循环的步骤。代理行为不符合预期时别靠猜打开 UI 看每一步实际发生了什么问题往往一目了然。我调代理的固定流程是先在 UI 里跑一遍典型场景看循环走了几步、每步模型说了什么、工具返回了什么定位到问题环节后再改代码改完再跑一遍对比。这个流程比盲改代码高效太多。生产环境记得关掉 UI它只适合开发期。6.4 成本控制别让代理悄悄烧钱代理比单次调用贵因为一次用户请求可能触发多次模型调用和多次工具调用。成本控制要从几个地方下手模型选型别一味求大、历史裁剪控制 token、轮次上限防止死循环、工具结果精简减少输入。我还会加一个监控记录每个会话的模型调用次数和 token 消耗异常高的会话单独看往往能发现优化点。有个容易被忽略的点工具调用本身也可能有成本比如调第三方 API 按次收费。所以工具设计上要避免模型反复调同一个工具拿同样的结果可以在工具层加缓存相同输入短时间内直接返回缓存结果。7. 从能跑到好用中间差的是什么把代理跑起来不难难的是让它稳定、可控、可维护。我做完几个代理项目后最大的体会是代理的质量上限由工具设计决定稳定性下限由状态管理和错误处理决定。模型再强工具描述写得烂、状态存得乱、异常没处理代理照样不可用。另一个体会是别过度设计。一开始就想做全能代理挂几十个工具结果调试地狱。正确的做法是从一个明确场景、两三个工具开始跑通、跑稳再逐步加能力。每加一个工具都要重新测一遍典型场景确保没破坏原有行为。最后说个实操建议给代理写测试。不是那种端到端跑模型的测试太慢太贵而是针对工具函数、状态读写、消息裁剪这些纯逻辑部分的单元测试。这些部分才是 bug 高发区测好了代理的稳定性就有底了。模型行为那部分用固定的输入输出做回归对比改动后跑一遍看有没有退化。这套组合下来代理的迭代会踏实很多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Codex CLI 安装与 API Key 登录实战:config.toml 配置与 401 报错排查指南 2026/9/28 23:40:10

Codex CLI 安装与 API Key 登录实战:config.toml 配置与 401 报错排查指南

1. 为什么 2026 年还有人在折腾 Codex 的安装先把话说在前头:Codex 这个命令行工具在 2026 年依然是不少开发者本地跑 AI 编码助手的首选,原因很直接——它轻、快、能直接读写你当前项目的文件,配合终端里的工作流几乎无缝。但它的安装和登录…

阅读更多 →
Ubuntu串口调试实战:cutecom安装与ttyUSB0权限全解 2026/9/28 23:39:51

Ubuntu串口调试实战:cutecom安装与ttyUSB0权限全解

1. 为什么Ubuntu新手总在串口调试上卡住?——从cutecom切入的真实痛点你刚装好Ubuntu,连上STM32开发板、Arduino或者ESP32模块,打开终端敲ls /dev/tty*,一眼看到ttyUSB0,心里一喜——设备识别成功!可当你兴…

阅读更多 →
AI智能体自动剪视频全流程拆解:从工具选型到商业变现 2026/9/28 23:39:51

AI智能体自动剪视频全流程拆解:从工具选型到商业变现

AI自动剪视频这事儿,我劝你别再观望了。去年我在做小说推文,一条28秒的分镜要反复卡点卡一下午,当时打死我也想不到,今年这个活儿能被AI智能体干成流水线。更想不到的是,现在这条赛道上已经挤满了人,有人靠…

阅读更多 →
Alluxio v2.9.4实战:部署、挂载S3/HDFS与缓存调优全解析 2026/9/28 23:39:44

Alluxio v2.9.4实战:部署、挂载S3/HDFS与缓存调优全解析

简介:Alluxio分布式存储系统 v2.9.4 是一套基于内存的分布式存储中间件,面向Hadoop、Spark等大数据生态,旨在屏蔽底层存储系统差异并加速数据访问。该版本提供灵活的文件API,类似于java.io.File,并兼容Hadoop HDFS的文…

阅读更多 →
Agent训练沙箱高并发实践:一天300万沙箱的架构与优化 2026/9/28 23:39:44

Agent训练沙箱高并发实践:一天300万沙箱的架构与优化

1. 从“一天 300 万沙箱”说起:这个数字到底意味着什么第一次看到“一天创建 300 万个沙箱”这个量级,我的反应不是“哇好厉害”,而是下意识开始算账:一天 86400 秒,300 万个沙箱意味着平均每秒要拉起接近 35 个隔离环…

阅读更多 →
OpenAI Agents SDK 构建指南:从单 Agent 到多 Agent 协作与知识库问答 2026/9/28 23:39:44

OpenAI Agents SDK 构建指南:从单 Agent 到多 Agent 协作与知识库问答

1. 从零理解 OpenAI Agents SDK 到底在解决什么问题第一次看到 OpenAI Agents SDK 这个名词,很多人会下意识觉得它又是一个“套壳 API 的封装库”。我一开始也这么想,直到真正把一个多步骤任务拆开、用传统方式写了一遍之后,才发现它要解决的…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉