1. 项目概述为什么我们需要一个“代码优先”的AI智能体开发套件如果你和我一样在过去一两年里尝试过构建基于大语言模型的AI应用大概率经历过这样的场景一开始你兴奋地打开某个低代码平台或聊天界面通过拖拽和自然语言描述快速“组装”出了一个能回答问题的智能体。它看起来很棒能联网搜索、能调用API你甚至把它分享给了同事。但当你试图把它集成到现有的业务系统里或者想给它增加一个复杂的多步骤业务流程时问题就来了。你会发现智能体的行为逻辑像是一个黑盒调试起来异常困难你想用Git来管理它的版本迭代却发现它的“代码”散落在平台的配置文件和数据库里你想为它编写单元测试以确保某个核心工具调用的稳定性却发现无从下手。这正是google/adk-jsAgent Development Kit for TypeScript想要解决的核心痛点。它不是一个试图用自然语言取代一切的平台而是一个回归开发者本源的代码优先Code-First工具包。它的核心理念是构建复杂、可靠、可维护的AI智能体最好的方式仍然是写代码。通过TypeScript这门在大型前端和后端项目中久经考验的语言ADK让你能够像开发一个普通的Node.js服务一样去定义智能体的思考逻辑、工具调用、状态管理和多智能体协作。你获得的是极致的灵活性和控制力以及软件开发领域所有成熟的工程实践——版本控制、单元测试、CI/CD、代码审查——都可以无缝应用到你的AI智能体项目上。简单来说ADK-JS是为那些不满足于“玩具级”智能体希望构建真正能在生产环境中运行、能与现有Google Cloud服务深度集成、并且其行为完全由代码定义和保障的开发者准备的。它把智能体从“配置”变成了“程序”。1.1 核心设计哲学控制权回归开发者ADK-JS的设计哲学非常明确将智能体行为的控制权完全交还给开发者。这与许多可视化编排平台形成了鲜明对比。在那些平台上智能体的“推理过程”和“工具调用”往往被封装在平台内部你只能通过有限的配置项和提示词去影响它一旦出现非预期行为排查成本很高。ADK-JS则采用了另一种思路。它提供了一套清晰的、基于类的API让你用TypeScript代码来显式地声明智能体是谁它的名字、角色描述、使用的底层模型如Gemini系列。智能体能做什么它可以使用哪些工具Tools。这些工具可以是预置的如谷歌搜索也可以是你自己用TypeScript函数编写的任何业务逻辑。智能体如何思考通过结构化的指令Instruction来引导但更重要的是你可以通过代码介入它的决策循环。智能体如何协作如何设计多个智能体之间的交互流程是顺序执行、并行处理还是基于事件驱动。这种代码化的方式带来了几个立竿见影的好处可调试性你可以在任何一步设置断点查看智能体接收到的消息、内部的状态、准备调用的工具及其参数。整个推理链路对你是透明的。可测试性你可以为你的智能体编写单元测试和集成测试。例如模拟一个用户问题断言智能体一定会调用某个特定的工具并返回预期的结果。可版本化你的整个智能体应用就是一个标准的TypeScript项目可以用Git进行版本管理。每一次逻辑变更都对应一次代码提交回滚和代码对比变得轻而易举。可集成性智能体本身就是一段代码你可以轻松地把它封装成一个函数、一个类然后集成到现有的Express.js服务器、Next.js API路由或任何云函数中。2. 核心概念深度解析从零理解ADK-JS的架构在开始动手写代码之前我们需要先厘清ADK-JS中的几个核心抽象。理解这些概念是灵活运用这个工具包的关键。2.1 智能体Agent不仅仅是聊天机器人在ADK-JS中Agent通常通过LlmAgent类来创建是一个具有特定目标、能力和思考逻辑的实体。千万不要把它简单理解为一个聊天对话接口。它更像是一个可编程的、具备认知能力的Worker。一个智能体由以下几个关键部分构成模型Model智能体背后的“大脑”。ADK-JS默认深度集成Google的Gemini模型如gemini-2.0-flash、gemini-2.5-pro但你也可以通过配置连接到其他兼容的模型端点。模型的选择直接决定了智能体的基础认知能力和成本。指令Instruction这是智能体的“角色设定”和“行为准则”。它比普通的系统提示词System Prompt更结构化用于初始化智能体的上下文告诉它“你是谁”、“你的目标是什么”、“你该如何行事”。好的指令是稳定智能体行为输出的基石。工具Tools这是智能体能力的延伸。智能体本身不会写代码、查数据库、发邮件但它可以通过“工具”来做到这些。工具是智能体与外部世界交互的桥梁。2.2 工具Tools赋予智能体“手脚”工具是ADK-JS中非常强大和灵活的概念。一个工具本质上是一个可以被智能体识别和调用的函数。ADK-JS支持多种形式的工具函数工具Function Tool这是最基础、最常用的形式。你可以将任何一个TypeScript异步函数async function包装成一个工具。例如一个查询数据库的函数、一个调用第三方天气API的函数、一个计算税费的函数。import { makeTool } from google/adk; import { queryDatabase } from ./my-database-client; // 1. 定义你的业务函数 async function getUserOrderHistory(userId: string): PromiseOrder[] { // 这里是实际的数据库查询逻辑 return await queryDatabase(SELECT * FROM orders WHERE user_id ${userId}); } // 2. 使用 makeTool 将其包装成ADK能识别的工具 const ORDER_HISTORY_TOOL makeTool({ name: get_user_order_history, description: 根据用户ID查询该用户的历史订单列表。, parameters: { type: object, properties: { userId: { type: string, description: 用户的唯一标识符。 } }, required: [userId] }, // 将工具的执行指向你定义的函数 execute: getUserOrderHistory });关键点makeTool不仅包装了函数更重要的是它利用JSON Schema定义了工具的输入参数。这个Schema会在大语言模型选择工具时被使用模型需要生成符合这个Schema的参数来调用工具。这保证了调用的类型安全和结构正确。预置工具Prebuilt ToolsADK-JS开箱即用提供了一些常用工具最典型的就是GOOGLE_SEARCH。这让你无需自己处理搜索API的认证和结果解析直接赋予智能体联网搜索能力。OpenAPI工具如果你已经有一个遵循OpenAPI/Swagger规范的外部REST APIADK-JS可以自动将其转化为智能体可用的工具集。这对于快速集成企业内部已有服务非常有用。工具的使用哲学你应该遵循“单一职责”原则来设计工具。一个工具最好只做一件事。例如getUserProfile和updateUserProfile应该是两个独立的工具而不是一个庞大的handleUser工具。这样能让智能体更精确地理解和使用它们也便于你单独测试和维护。2.3 多智能体系统Multi-Agent Systems从单兵到军团单个智能体的能力是有限的。复杂的任务往往需要分工协作。ADK-JS的“代码优先”特性在构建多智能体系统时优势尽显。你可以创建多个各司其职的智能体规划者Planner负责分解复杂任务制定步骤。执行者Executor负责调用具体工具完成任务。审核者Reviewer负责检查执行结果的质量。专家Specialist在特定领域如数据分析、文案撰写有专长。然后你通过TypeScript代码来编排它们之间的工作流。这种编排不是通过模糊的自然语言描述而是通过清晰的程序逻辑例如顺序链Sequential Chain智能体A完成任务后将结果传递给智能体B。路由Router根据输入内容的不同决定由哪个智能体来处理。并行与聚合Parallel Aggregate多个智能体同时处理任务的子部分最后汇总结果。由于每个智能体都是代码定义的对象这种编排就像是在组合函数或服务类逻辑清晰易于调试。ADK-JS未来与A2AAgent-to-Agent协议的集成更是为了将这种协作能力扩展到跨网络、跨服务的分布式智能体之间。3. 从零开始构建你的第一个ADK-JS智能体理论说得再多不如动手一试。让我们从一个最简单的例子开始构建一个能够查询天气的智能体。这个过程会清晰地展示ADK-JS“代码优先”的工作流。3.1 环境准备与项目初始化首先确保你有一个Node.js环境建议版本18。然后创建一个新的目录并初始化项目mkdir my-first-adk-agent cd my-first-adk-agent npm init -y接下来安装ADK-JS的核心包。由于我们需要调用一个真实的天气API还需要安装axios或其他你喜欢的HTTP客户端。npm install google/adk axios npm install -D typescript types/node tsx初始化TypeScript配置npx tsc --init你可以在生成的tsconfig.json中确保target是ES2020或更高module是commonjs或nodenext。3.2 定义自定义天气查询工具智能体需要工具才能获取天气信息。我们将创建一个调用公开天气API的工具。创建工具文件src/tools/weatherTool.ts:import { makeTool } from google/adk; import axios from axios; // 这是一个真实的免费天气API示例请在实际使用时查看其条款 const WEATHER_API_URL https://api.open-meteo.com/v1/forecast; async function getCurrentWeather(latitude: number, longitude: number): Promisestring { try { const response await axios.get(WEATHER_API_URL, { params: { latitude, longitude, current_weather: true, timezone: auto } }); const data response.data; const current data.current_weather; // 将API响应格式化成对智能体友好的自然语言描述 return 当前位置当前天气温度 ${current.temperature}°C风速 ${current.windspeed} km/h风向 ${current.winddirection}°天气代码 ${current.weathercode}。; } catch (error) { console.error(获取天气失败:, error); return 抱歉暂时无法获取该位置的天气信息。; } } // 使用 makeTool 创建工具 export const WEATHER_TOOL makeTool({ name: get_current_weather, description: 根据给定的经纬度坐标获取该位置的当前天气情况。, parameters: { type: object, properties: { latitude: { type: number, description: 地点的纬度例如52.52 }, longitude: { type: number, description: 地点的经度例如13.41 } }, required: [latitude, longitude] }, execute: getCurrentWeather });实操心得在定义工具参数Schema时description字段至关重要。大语言模型依赖这个描述来理解每个参数的意义。描述应尽可能清晰例如“纬度”比“lat”更好。此外工具函数的返回值最好是结构化的字符串方便智能体理解和整合到它的回答中。3.3 创建并运行主智能体现在创建主文件src/index.ts在这里实例化智能体并运行一个对话。import { LlmAgent } from google/adk; import { WEATHER_TOOL } from ./tools/weatherTool; // 1. 初始化智能体 const weatherAgent new LlmAgent({ name: weather_expert, description: 一个专业的天气查询助手可以根据用户提供的地点信息查询实时天气。, // 指定使用的模型你需要配置相应的API密钥 model: gemini-2.0-flash, // 定义智能体的行为指令 instruction: 你是一个天气助手。你的核心能力是使用 get_current_weather 工具来查询精确的天气。 当用户询问某个地方的天气时你需要引导用户提供具体的地理位置最好是城市名你能理解常见城市。 如果用户只提供了城市名你需要利用你的知识或上下文将其转换为大致的经纬度坐标例如北京约为 39.9, 116.4。 一旦你获得了或推算出经纬度就调用工具获取天气并将结果用友好、易懂的方式总结给用户。 如果用户的问题与天气无关请礼貌地告知你的能力范围。, // 赋予智能体工具 tools: [WEATHER_TOOL], // 可选配置模型参数如创造力温度 modelConfig: { temperature: 0.2, // 较低的温度使输出更确定、更专注于工具调用 } }); // 2. 运行一个简单的对话 async function main() { console.log(天气助手已启动。输入“退出”或“quit”结束对话。\n); // 在实际应用中这里可能是从HTTP请求或聊天界面获取的用户输入 const userQuery 今天北京的天气怎么样; console.log(用户: ${userQuery}); try { // 调用智能体的 run 方法处理用户输入 const response await weatherAgent.run(userQuery); console.log(助手: ${response.text}); // 如果你想查看更详细的执行过程例如工具调用记录可以访问 response 的其他属性 // console.log(完整响应:, JSON.stringify(response, null, 2)); } catch (error) { console.error(处理请求时出错:, error); } } // 执行主函数 if (require.main module) { main(); }3.4 配置与运行要运行这个智能体你需要一个Gemini API密钥。你可以通过Google AI Studio获取。设置API密钥通常通过环境变量export GOOGLE_API_KEY你的API密钥或者在代码中直接配置不推荐用于生产环境import { configure } from google/adk; configure({ apiKey: process.env.GOOGLE_API_KEY });运行智能体 由于我们使用了tsx来直接运行TypeScript可以使用以下命令npx tsx src/index.ts如果一切正常你会看到控制台输出智能体对“北京天气”的回应。智能体会根据指令将“北京”映射为经纬度调用我们定义的WEATHER_TOOL获取真实天气数据并生成最终回答。注意在开发过程中你可能会频繁调整指令Instruction和工具描述。每次调整后观察智能体行为的变化是优化其表现的关键。ADK-JS提供的开发UI通过google/adk/web包提供是一个强大的可视化工具可以让你实时看到智能体的内部思考过程、工具调用请求和响应极大提升了调试效率。4. 进阶实战构建一个协作型多智能体客服系统单一功能的智能体展示了基础能力。现在让我们设计一个更贴近真实场景的例子一个简易的电商客服系统。这个系统由两个智能体协作完成分类器Classifier Agent负责理解用户意图将问题分类如“查询订单”、“退货申请”、“产品咨询”。专家处理器Specialist Agent根据分类结果由专门的智能体处理具体问题。例如订单查询专家拥有查询数据库的工具。这个例子将展示如何用代码编排智能体间的协作。4.1 定义工具与模拟数据首先创建一些模拟工具和数据源。在src/tools/下创建新文件src/tools/orderTools.ts:import { makeTool } from google/adk; // 模拟一个订单数据库 const mockOrders [ { orderId: ORD-1001, userId: user123, product: 无线耳机, status: 已发货, estimatedDelivery: 2023-10-30 }, { orderId: ORD-1002, userId: user123, product: Type-C数据线, status: 待付款 }, { orderId: ORD-1003, userId: user456, product: 编程书籍, status: 已完成 }, ]; export const GET_ORDER_TOOL makeTool({ name: get_order_status, description: 根据订单号查询订单的详细状态和物流信息。, parameters: { type: object, properties: { orderId: { type: string, description: 订单编号例如 ORD-1001 } }, required: [orderId] }, execute: async ({ orderId }) { const order mockOrders.find(o o.orderId orderId); if (!order) { return 未找到订单号 ${orderId} 的记录。; } return 订单 ${orderId} 状态${order.status}。商品${order.product}。${order.estimatedDelivery ? 预计送达时间${order.estimatedDelivery} : }; } }); export const GET_USER_ORDERS_TOOL makeTool({ name: get_user_orders, description: 根据用户ID查询该用户的所有历史订单概要。, parameters: { type: object, properties: { userId: { type: string, description: 用户ID } }, required: [userId] }, execute: async ({ userId }) { const userOrders mockOrders.filter(o o.userId userId); if (userOrders.length 0) { return 用户 ${userId} 暂无订单记录。; } const summary userOrders.map(o - ${o.orderId}: ${o.product} (${o.status})).join(\n); return 用户 ${userId} 的订单列表\n${summary}; } });src/tools/productTools.ts:import { makeTool } from google/adk; const mockProducts [ { id: P-001, name: 无线耳机, price: 199, stock: 45 }, { id: P-002, name: 机械键盘, price: 399, stock: 12 }, ]; export const GET_PRODUCT_INFO_TOOL makeTool({ name: get_product_info, description: 根据产品名称或ID查询产品详情包括价格和库存。, parameters: { type: object, properties: { productIdentifier: { type: string, description: 产品名称或ID例如“无线耳机”或“P-001” } }, required: [productIdentifier] }, execute: async ({ productIdentifier }) { const product mockProducts.find(p p.id productIdentifier || p.name.includes(productIdentifier)); if (!product) { return 未找到产品“${productIdentifier}”的信息。; } return 产品${product.name}价格${product.price}元库存${product.stock 0 ? 有货(${product.stock}件) : 缺货}。; } });4.2 创建专业化智能体接下来创建两个专家智能体每个都装备了其专属的工具集。src/agents/orderSpecialist.ts:import { LlmAgent } from google/adk; import { GET_ORDER_TOOL, GET_USER_ORDERS_TOOL } from ../tools/orderTools; export const orderSpecialist new LlmAgent({ name: order_specialist, description: 专门处理所有与订单相关查询的专家如查询状态、历史订单等。, model: gemini-2.0-flash, instruction: 你是订单查询专家。你只能处理与订单相关的问题。 你可以通过订单号查询单个订单的详细状态也可以通过用户ID查询其所有订单。 当用户提供信息不足时例如只问“我的订单怎么样了”你需要礼貌地询问具体的订单号或用户ID。 你的回答应清晰、准确、友好。, tools: [GET_ORDER_TOOL, GET_USER_ORDERS_TOOL], modelConfig: { temperature: 0.1 } // 订单查询需要非常准确 });src/agents/productSpecialist.ts:import { LlmAgent } from google/adk; import { GET_PRODUCT_INFO_TOOL } from ../tools/productTools; export const productSpecialist new LlmAgent({ name: product_specialist, description: 专门处理产品信息查询的专家如价格、库存、规格等。, model: gemini-2.0-flash, instruction: 你是产品信息专家。你只能回答关于产品详情的问题例如价格、库存、是否有货等。 如果用户询问的产品不存在或信息不足请如实告知。 不要回答与产品无关的问题。, tools: [GET_PRODUCT_INFO_TOOL], modelConfig: { temperature: 0.1 } });4.3 构建路由分类器智能体分类器智能体本身不处理具体业务它的核心职责是分析用户输入并决定将任务路由给哪个专家。在这个简单示例中我们让分类器直接输出一个决策。src/agents/classifier.ts:import { LlmAgent } from google/adk; export const classifier new LlmAgent({ name: intent_classifier, description: 分析用户意图并将其分类到特定处理类别。, model: gemini-2.0-flash, instruction: 你的任务是对用户的客服请求进行精准分类。 请只输出一个且仅一个分类标签不要输出任何其他解释。 可用的分类标签有 - ORDER_INQUIRY: 当用户的问题涉及订单状态、物流、历史订单查询时。 - PRODUCT_INQUIRY: 当用户的问题涉及产品价格、库存、规格、是否有货时。 - OTHER: 当用户的问题不属于以上任何类别时如投诉、建议、闲聊。 示例 用户“我的订单ORD-1001到哪了” - ORDER_INQUIRY 用户“无线耳机多少钱” - PRODUCT_INQUIRY 用户“你们客服电话是多少” - OTHER , // 分类器不需要具体业务工具 tools: [], modelConfig: { temperature: 0.0 } // 温度设为0让分类结果尽可能确定 });4.4 实现协作编排逻辑最后在src/orchestrator.ts中编写协调所有智能体的“大脑”。这是体现“代码优先”威力的地方——整个工作流由清晰的程序逻辑控制。import { classifier } from ./agents/classifier; import { orderSpecialist } from ./agents/orderSpecialist; import { productSpecialist } from ./agents/productSpecialist; // 定义分类结果类型 type Intent ORDER_INQUIRY | PRODUCT_INQUIRY | OTHER; export class CustomerServiceOrchestrator { /** * 处理用户查询的主流程 * 1. 分类 - 2. 路由 - 3. 专家处理 - 4. 返回结果 */ async handleUserQuery(userInput: string): Promisestring { console.log([Orchestrator] 处理查询: ${userInput}); // 步骤1意图分类 const intent await this.classifyIntent(userInput); console.log([Orchestrator] 分类结果: ${intent}); // 步骤2 3根据分类路由到对应专家 let finalResponse: string; switch (intent) { case ORDER_INQUIRY: finalResponse await orderSpecialist.run(userInput); break; case PRODUCT_INQUIRY: finalResponse await productSpecialist.run(userInput); break; case OTHER: default: finalResponse 您好我主要擅长处理订单查询和产品咨询。您的问题“${userInput}”可能涉及其他客服领域我已为您记录稍后会有专人联系您。您也可以直接拨打客服热线 400-XXX-XXXX。; break; } // 步骤4返回最终响应 return typeof finalResponse string ? finalResponse : finalResponse.text; } /** * 调用分类器智能体分析意图 * 这里对分类器的输出做了简单清洗确保返回预定义的标签。 */ private async classifyIntent(userInput: string): PromiseIntent { const classification await classifier.run(userInput); const rawText classification.text.trim(); // 清洗输出提取标签 if (rawText.includes(ORDER_INQUIRY)) return ORDER_INQUIRY; if (rawText.includes(PRODUCT_INQUIRY)) return PRODUCT_INQUIRY; // 默认归类为 OTHER return OTHER; } }4.5 运行与测试协作系统创建一个src/main.ts来测试整个系统import { CustomerServiceOrchestrator } from ./orchestrator; async function main() { const orchestrator new CustomerServiceOrchestrator(); const testQueries [ 订单ORD-1001现在是什么状态, user123的所有订单能帮我查一下吗, 机械键盘还有货吗多少钱, 我想投诉上周的配送服务。, 无线耳机, ]; for (const query of testQueries) { console.log(\n 用户问题: ${query} ); const response await orchestrator.handleUserQuery(query); console.log(客服回复: ${response}); console.log(---); } } main().catch(console.error);运行npx tsx src/main.ts你会看到系统如何自动将不同问题路由给不同的专家智能体处理。例如“订单ORD-1001”会触发orderSpecialist调用GET_ORDER_TOOL而“机械键盘”会触发productSpecialist调用GET_PRODUCT_INFO_TOOL。这个例子虽然简单但清晰地展示了ADK-JS的核心优势模块化每个智能体和工具都是独立的模块易于开发和测试。透明可控整个决策流程分类 - 路由 - 处理都在你的代码控制之下可以轻松添加日志、监控和错误处理。易于扩展如果需要增加一个“退货处理专家”你只需要创建新的工具和智能体然后在Orchestrator的switch语句中添加一个新的case即可。5. 工程化实践测试、调试与部署构建出可用的智能体只是第一步。要将其用于生产我们必须考虑工程化实践。ADK-JS的代码优先特性让这些实践变得非常自然。5.1 为你的智能体编写单元测试由于智能体和工具都是纯函数或类你可以像测试普通代码一样测试它们。使用Jest、Vitest等测试框架即可。示例测试天气工具 (src/tools/weatherTool.test.ts):import { WEATHER_TOOL } from ./weatherTool; import axios from axios; import MockAdapter from axios-mock-adapter; // 创建axios模拟实例 const mockAxios new MockAdapter(axios); describe(WeatherTool, () { afterEach(() { mockAxios.reset(); }); it(应该使用正确的参数调用天气API并返回格式化的字符串, async () { // 1. 模拟API成功响应 const mockResponse { current_weather: { temperature: 22.5, windspeed: 10.2, winddirection: 180, weathercode: 0 } }; mockAxios.onGet(https://api.open-meteo.com/v1/forecast).reply(200, mockResponse); // 2. 执行工具函数 const result await WEATHER_TOOL.execute({ latitude: 52.52, longitude: 13.41 }); // 3. 断言 expect(mockAxios.history.get[0].params).toEqual({ latitude: 52.52, longitude: 13.41, current_weather: true, timezone: auto }); expect(result).toContain(22.5); expect(result).toContain(10.2); }); it(应该在API调用失败时返回友好的错误信息, async () { // 模拟API失败 mockAxios.onGet(https://api.open-meteo.com/v1/forecast).reply(500); const result await WEATHER_TOOL.execute({ latitude: 0, longitude: 0 }); expect(result).toBe(抱歉暂时无法获取该位置的天气信息。); }); });测试心得对于工具重点测试其边界条件如无效输入、错误处理如网络异常和输出格式。对于智能体可以模拟工具调用测试其在特定指令下对于给定输入是否会产生预期的工具调用序列和最终回复。5.2 利用开发UI进行可视化调试ADK-JS提供了一个本地开发UI这是调试智能体行为的利器。它允许你以Web界面的形式与智能体交互并实时查看其完整的“思考链”Chain-of-Thought包括模型接收到的完整提示词。模型决定调用哪个工具及其理由。工具被调用时的具体参数。工具返回的原始结果。模型根据工具结果生成的最终回复。要启动开发UI通常需要安装google/adk/web包并运行一个本地服务器。这让你能像使用ChatGPT界面一样与你的智能体对话但背后是所有可审查的详细日志极大降低了调试复杂度。5.3 部署策略从本地到云你的ADK-JS智能体项目本质上是一个Node.js应用因此所有标准的Node.js部署方式都适用。打包使用npm run build将TypeScript编译为JavaScript。容器化推荐创建Dockerfile将构建产物和依赖打包成镜像。这确保了环境一致性。FROM node:18-slim WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY dist ./dist CMD [node, dist/index.js]部署目标Google Cloud Run这是与Google生态集成最紧密的无服务器方案。你可以将容器镜像部署到Cloud Run它会自动处理扩缩容。ADK智能体可以轻松调用其他Google Cloud服务如Cloud SQL, Vertex AI等。Google Cloud Functions对于更轻量级的、事件驱动的场景可以将智能体逻辑封装成云函数。任何Node.js托管环境你也可以将应用部署在Google Compute Engine、GKE甚至其他云服务商或你自己的服务器上。环境配置务必通过环境变量来管理敏感信息如GOOGLE_API_KEY、数据库连接字符串等。在Cloud Run或Cloud Functions中这可以很方便地完成。5.4 监控与日志在生产环境中你需要监控智能体的性能、成本和错误。日志在工具函数和智能体编排逻辑的关键节点添加结构化日志例如使用Winston或Pino。记录用户输入、工具调用参数、模型使用token数、最终输出以及任何错误。指标考虑收集一些业务指标如“用户问题分类分布”、“各工具调用成功率”、“平均响应延迟”。这些数据可以帮助你优化智能体指令和工具设计。成本控制监控模型API的调用量和费用。可以为智能体设置预算告警或者在代码层面实现限流和降级策略。6. 常见问题、排查技巧与最佳实践实录在实际使用ADK-JS的过程中你肯定会遇到各种问题。以下是我从项目实践中总结的一些常见坑点和解决思路。6.1 智能体不调用工具或调用错误工具这是最常见的问题之一。可能原因1工具描述不清晰。排查检查makeTool中的description和每个参数的description。这些描述是模型理解工具用途的唯一依据。描述要具体、无歧义说明工具做什么以及何时使用。技巧在描述中可以使用“Use this tool when the user asks about...”这样的句式来引导模型。对比一下不佳description: Gets weather.优秀description: Use this tool to get the current temperature, wind speed, and conditions for a specific geographic location. Always call this when the user asks whats the weather like in [city]? or similar.可能原因2指令Instruction与工具能力不匹配。排查智能体的instruction必须明确告知它拥有这些工具并鼓励或指导它在合适场景下使用。如果指令过于笼统或完全没提工具模型可能不会主动调用。技巧在指令中明确列出可用的工具名并给出使用示例。例如“你可以使用get_current_weather工具来查询任何地点的天气。当用户询问天气时你应该主动调用这个工具。”可能原因3模型温度Temperature设置过高。排查modelConfig.temperature控制输出的随机性。值越高如0.8回答越有创意但也越不稳定值越低如0.1回答越确定、可预测。对于需要精确工具调用的任务建议设置为较低的值0.1-0.3。技巧在开发调试阶段可以暂时将温度设为0让模型输出最可能的确定性结果这有助于判断是逻辑问题还是随机性问题。6.2 工具调用参数不符合预期模型生成了工具调用请求但参数格式错误或值不对。可能原因1参数Schema定义太宽松。排查JSON Schema定义是否足够严格例如一个要求是“数字”的参数是否被定义成了type: string技巧充分利用JSON Schema的约束。使用enum来限定可选值使用minimum/maximum限制数字范围。更严格的Schema能引导模型生成更准确的参数。可能原因2用户输入信息模糊模型在“猜”。场景用户问“我的订单怎么样了”模型需要调用get_order_status但参数orderId未知。解决方案这不是工具的错而是对话逻辑问题。你有两个选择在工具层处理让工具函数能处理部分缺失的参数例如查询当前会话用户的默认订单。但这可能增加工具复杂度。在智能体指令层处理推荐在指令中明确要求智能体当参数不足时必须向用户追问。例如“如果用户没有提供订单号你必须先询问‘请问您的订单号是多少’在获得订单号之前不要调用任何工具。”6.3 处理复杂、多轮对话的状态管理基础的agent.run()是单次调用。对于多轮对话你需要自己管理对话历史上下文。核心概念将之前的对话历史{ role: user/model, parts: [...] }作为下一次run方法的输入的一部分。实现模式class ConversationSession { private history: Array{role: string, parts: any[]} []; async chat(userInput: string): Promisestring { // 将用户输入加入历史 this.history.push({ role: user, parts: [{ text: userInput }] }); // 调用智能体传入完整历史作为上下文 const response await myAgent.run({ messages: this.history // 或者使用更简洁的 history 参数取决于ADK的具体API }); // 将智能体回复加入历史 this.history.push({ role: model, parts: [{ text: response.text }] }); // 可选限制历史长度避免token超限 if (this.history.length 20) { this.history this.history.slice(-20); } return response.text; } }注意事项上下文越长消耗的Token越多成本越高且模型处理远端历史的能力会下降。需要根据场景设计合理的上下文窗口大小和摘要策略。6.4 性能与成本优化缓存对于频繁查询且结果变化不频繁的工具如产品信息可以在工具函数内部实现缓存层例如使用内存缓存node-cache或Redis避免重复调用外部API或查询数据库。批量处理如果业务场景允许可以设计支持批量查询的工具减少模型调用工具的频次。选择合适的模型对于简单的分类、路由、信息提取任务使用gemini-2.0-flash这类更小、更快的模型足以胜任成本远低于gemini-2.5-pro。将最强大的模型用在最需要复杂推理的环节。6.5 安全性与可靠性工具权限不是所有工具都应暴露给所有智能体。在设计多智能体系统时应根据智能体的职责分配最小必要的工具集。用户输入净化在将用户输入传递给模型或工具之前应进行基本的清理和验证防止提示词注入攻击。工具调用的副作用对于执行写操作的工具如创建订单、发送邮件务必在工具内部实现严格的业务验证和确认机制。可以考虑让智能体先生成一个“待执行操作”的摘要经用户确认后再触发实际执行。ADK-JS将AI智能体开发从“魔术”变成了“工程”。它要求开发者投入更多前期设计但回报是前所未有的可控性、可测试性和可维护性。当你习惯了用代码来定义智能体的每一个行为边界后就很难再回到那个靠猜测和反复调整提示词来碰运气的时代了。