1. Strands Agents TypeScript SDK一个模型驱动的AI智能体开发框架深度解析最近在探索如何用TypeScript构建更可靠、更易维护的AI智能体时我深度体验了Strands Agents的TypeScript SDK。作为一个长期在Node.js和前端领域耕耘的开发者我对市面上各种AI工具链的“玩具感”一直不太满意——要么封装过度失去了灵活性要么过于底层写起来像是在重复造轮子。Strands Agents的出现让我感觉找到了一个不错的平衡点。它提出的“模型驱动”理念本质上是通过强类型和模式化的方式把智能体开发从“提示词工程”的玄学拉回到了我们熟悉的软件工程轨道上。今天我就结合自己近一个月的实际项目踩坑经验来聊聊这个框架的核心设计、最佳实践以及那些官方文档里没写的细节。简单来说Strands Agents TypeScript SDK是一个用于在TypeScript/JavaScript环境中构建、运行和编排AI智能体的框架。它最大的特点是模型驱动和类型安全。所谓模型驱动是指它用Zod模式Schema来定义工具Tools的输入输出、结构化响应Structured Output的格式甚至多智能体之间的协作流程。这带来的直接好处是你的智能体行为可以被清晰地定义和验证而不是依赖大语言模型LLM的自由发挥。类型安全则贯穿始终从工具回调函数的参数到最终响应的解析结果你都能获得完整的TypeScript类型推断和编译时检查这在大中型项目中简直是救命稻草。这套框架适合谁呢如果你正在用Node.js后端或浏览器环境开发需要集成AI能力的应用比如智能客服、自动化工作流、数据分析助手或者想实验多智能体协作Multi-Agent的复杂场景那么Strands Agents值得你花时间研究。它尤其适合那些对代码质量、可测试性和系统可观测性有要求的团队。接下来我会从设计思路拆解开始带你一步步深入这个框架的肌理。1.1 核心设计思路为什么是“模型驱动”初次接触“模型驱动”这个词可能会觉得有点抽象。在Strands Agents的语境里它指的不仅仅是使用大语言模型LLM更是用数据模式Schema来驱动整个智能体的交互逻辑。这与传统的、主要依靠精心设计的提示词Prompt来引导LLM的方式有本质区别。传统的智能体开发我们往往写一个很长的系统提示词告诉LLM“你可以使用A、B、C这些工具它们的用法是……”然后在代码里解析LLM返回的文本判断它想调用哪个工具再手动拼接参数、调用函数、把结果塞回上下文。这个过程充满了不确定性LLM可能不按你期望的格式返回参数解析可能出错工具执行结果的结构也可能五花八门。Strands Agents的做法是把这一切都“契约化”。你用Zod定义一个工具包括它的名字、描述、输入参数的模式。框架在生成给LLM的提示词时会将这些模式转换成模型能理解的结构化描述比如OpenAI的function calling格式或Anthropic的tool use格式。当LLM决定调用工具时它返回的是一个结构化的调用请求框架能直接、安全地将其解析成强类型的JavaScript对象然后调用你定义的回调函数。同样回调函数的返回值也会被框架包装成结构化的结果送回给LLM作为下一步思考的上下文。这种设计带来的几个关键优势可靠性提升输入输出验证从“运行时字符串解析”变成了“模式验证”。Zod会在调用前验证参数是否符合模式不符合的直接抛出清晰的错误避免了因格式错误导致的工具调用失败或副作用。开发体验飞跃全程类型安全。你在写工具回调函数时input参数的类型就是Zod模式推断出来的类型IDE能提供完美的自动补全和类型检查。这极大地减少了低级错误。意图理解更精准LLM对于结构化的工具描述理解得更好、更一致。相比于用自然语言描述工具用法结构化的模式减少了歧义提高了工具调用的准确率。组合与复用性增强工具成为了一个个拥有明确定义接口的“组件”可以像乐高一样在不同的智能体之间轻松组合和复用。实操心得从“提示词驱动”转向“模型驱动”需要一点思维转变。一开始你可能会不习惯为每个工具都写Zod模式觉得有点繁琐。但坚持下来会发现这前期的一点投入在后续的调试、扩展和维护阶段会带来巨大的回报。尤其是在团队协作中一个定义清晰的工具模式就是最好的文档。2. 环境准备与核心概念落地理论说再多不如动手跑一遍。我们从一个最基础的智能体开始逐步添加功能来感受Strands Agents的工作方式。首先确保你的环境是Node.js 20或更高版本这是SDK的硬性要求因为它使用了一些较新的ES模块特性。2.1 基础安装与智能体初始化创建一个新的项目目录初始化并安装SDKmkdir my-strands-agent cd my-strands-agent npm init -y npm install strands-agents/sdk zod这里我们同时安装了zod因为定义工具和结构化输出离不开它。接下来创建一个index.ts文件写入最基本的智能体调用代码import { Agent } from strands-agents/sdk; // 创建一个最简单的智能体使用默认配置 const agent new Agent(); async function main() { try { const result await agent.invoke(What is the capital of France?); console.log(Agent Response:, result.content); } catch (error) { console.error(Error:, error); } } main();运行前你需要配置模型访问。SDK默认使用Amazon Bedrock作为模型提供商并且默认尝试调用Claude 3.5 Sonnet模型。因此你需要确保拥有一个AWS账户并在目标区域如us-east-1启用了Bedrock服务且对Claude模型有访问权限。本地配置了有效的AWS凭证。通常可以通过aws configure命令设置或设置AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY环境变量。如果你更习惯使用OpenAI切换起来也非常简单import { Agent } from strands-agents/sdk; import { OpenAIModel } from strands-agents/sdk/models/openai; // 设置你的OpenAI API Key process.env.OPENAI_API_KEY your-api-key-here; const model new OpenAIModel({ api: chat, // 使用Chat Completions API model: gpt-4o, // 指定模型默认为gpt-4 }); const agent new Agent({ model });注意事项关于模型提供商的选择这里有个细节。BedrockModel在初始化时modelId参数需要严格按照Bedrock控制台提供的模型ID来填写例如anthropic.claude-3-5-sonnet-20241022-v2:0。不同区域、不同版本的模型ID可能不同填错了会直接导致调用失败。而OpenAIModel则相对简单使用通用的模型名称即可。2.2 理解智能体的核心循环与配置Agent类是这个框架的核心。当你调用agent.invoke(prompt)时背后触发了一个标准的“思考-行动”循环ReAct模式的一种实现思考将用户输入、系统提示词、对话历史如果启用和可用工具的描述组合成一个提示发送给LLM。决策LLM返回一个包含“最终答案”或“工具调用请求”的响应。行动如果是工具调用框架会解析参数、执行对应的工具函数并将结果作为新的上下文附加到对话中。循环带着工具执行的结果回到第1步让LLM继续思考直到它决定给出最终答案。你可以通过配置项来精细控制这个循环import { Agent } from strands-agents/sdk; const agent new Agent({ // 系统提示词定义智能体的角色和行为准则 systemPrompt: 你是一个专业、简洁的助手。回答要准确如果不知道就说不知道。, // 最大迭代次数防止智能体陷入无限的工具调用循环 maxSteps: 10, // 是否启用对话历史管理 enableConversationHistory: true, // 对话历史的Token数量上限用于控制上下文窗口 maxConversationTokens: 4000, });maxSteps是一个非常重要的安全阀。在复杂任务中智能体可能会在多个工具之间来回调用或者陷入“我再查一下”的循环。设置一个合理的上限比如10-20次可以防止单个请求消耗过多的Token和API费用并在智能体“卡住”时及时抛出错误。3. 构建类型安全的工具Tools工具是智能体与外部世界交互的桥梁。Strands Agents让创建类型安全的工具变得异常优雅。3.1 创建你的第一个工具假设我们要构建一个查询城市天气的智能体。首先我们定义一个getWeather工具import { Agent, tool } from strands-agents/sdk; import { z } from zod; // 使用 tool() 高阶函数创建工具 const getWeatherTool tool({ name: get_weather, // 工具名称LLM通过这个名称来调用 description: 获取指定城市的当前天气信息。, // 清晰的描述帮助LLM理解工具用途 // 使用Zod定义输入参数的模式 inputSchema: z.object({ location: z.string().describe(城市名称例如北京、San Francisco, CA), unit: z.enum([celsius, fahrenheit]).optional().default(celsius).describe(温度单位), }), // 回调函数input的类型会自动推断为 { location: string; unit?: celsius | fahrenheit } callback: async (input) { console.log([Tool Called] get_weather with input:, input); // 这里应该是真实的API调用例如调用OpenWeatherMap // 为了示例我们模拟一个返回 const mockTemperatures: Recordstring, number { 北京: 22, San Francisco, CA: 18, 纽约: 25, }; const temp mockTemperatures[input.location] || 20; const finalTemp input.unit fahrenheit ? (temp * 9/5) 32 : temp; const unitSymbol input.unit fahrenheit ? °F : °C; return 当前${input.location}的天气为晴气温${finalTemp}${unitSymbol}湿度65%风速10km/h。; }, }); // 将工具装配给智能体 const agent new Agent({ systemPrompt: 你是一个天气助手可以帮助用户查询全球城市的天气。, tools: [getWeatherTool], }); // 测试调用 async function test() { const result await agent.invoke(上海今天天气怎么样); console.log(result.content); // 输出可能为当前上海的天气为晴气温20°C湿度65%风速10km/h。 // 注意智能体可能会先调用get_weather工具然后将工具返回的结果整合成最终回答。 } test();当你运行这段代码时观察控制台日志你会看到[Tool Called]的打印信息。这证实了智能体确实解析了用户问题中的“上海”生成了符合inputSchema的调用参数{location: ‘上海’ unit: ‘celsius’}并执行了你的回调函数。避坑技巧工具的描述description至关重要。它不仅是给后续维护者看的更是LLM决定是否以及如何调用该工具的主要依据。描述要简洁、准确、无歧义最好能包含一两个调用示例。模糊的描述会导致工具被误用或弃用。3.2 处理异步操作与错误现实中的工具大多是异步的比如网络请求、数据库查询。你的工具回调函数完全可以是一个async函数。框架会妥善处理异步操作。更重要的是错误处理如果工具执行中抛出错误框架会捕获这个错误并将其作为“工具执行失败”的信息反馈给LLMLLM可以据此决定重试或调整策略。const riskyTool tool({ name: fetch_data, description: 从一个可能不稳定的API获取数据。, inputSchema: z.object({ url: z.string().url() }), callback: async (input) { const response await fetch(input.url); if (!response.ok) { // 抛出错误智能体会收到“工具调用失败”的信号 throw new Error(HTTP ${response.status}: ${await response.text()}); } return response.json(); }, });3.3 使用内置的“已供应工具”Vended Tools为了提升开发效率SDK预置了一些常用工具可以通过单独的包引入。例如文件编辑工具和HTTP请求工具npm install strands-agents/toolsimport { FileEditorTool, HttpRequestTool } from strands-agents/tools; const fileTool new FileEditorTool({ // 可以限制文件操作的根目录增强安全性 basePath: ./workspace, }); const httpTool new HttpRequestTool(); const agent new Agent({ tools: [fileTool, httpTool], systemPrompt: 你可以帮我读写文件或者从网上获取信息。, }); // 现在智能体可以理解“请创建一个名为note.txt的文件内容为Hello World”或“获取https://api.example.com/data的信息”这样的指令。安全警告像FileEditorTool和HttpRequestTool这类拥有系统级或网络访问权限的工具必须谨慎使用。在生产环境中至少应该通过basePath限制文件访问范围或者为HTTP工具设置允许列表allowlist来限制可访问的域名避免智能体被恶意提示词诱导执行危险操作。4. 实现结构化输出Structured Output让LLM返回自由的文本固然灵活但在很多自动化场景下我们需要它返回结构化的数据以便后续的程序处理。例如从一段文本中提取联系人信息、生成JSON配置、或者格式化一个任务列表。Strands Agents的结构化输出功能通过结合Zod模式和自动重试机制优雅地解决了这个问题。4.1 定义输出模式与基础使用假设我们需要从一个自由格式的文本中提取会议信息import { Agent } from strands-agents/sdk; import { z } from zod; // 1. 用Zod定义我们期望的输出结构 const MeetingSchema z.object({ title: z.string().describe(会议主题), participants: z.array(z.string()).describe(参会人名单), scheduledTime: z.string().datetime().describe(会议时间ISO 8601格式), durationMinutes: z.number().int().positive().describe(会议时长分钟), location: z.string().optional().describe(会议地点可选), }); // 2. 在创建智能体时指定结构化输出模式 const agent new Agent({ systemPrompt: 你是一个信息提取助手请从文本中提取结构化的会议信息。, structuredOutputSchema: MeetingSchema, // 关键配置 }); async function extractMeeting() { const text 明天下午两点2024-06-15T14:00:00Z我们团队要开一个季度复盘会预计开90分钟。参加的人有张三、李四和王五。地点在301会议室。; const result await agent.invoke(请从以下文本中提取会议信息\n${text}); // 3. 访问结构化结果类型是 MeetingSchema 推断出的类型 const meeting result.structuredOutput; console.log(提取的会议信息:); console.log(主题: ${meeting.title}); // 季度复盘会 console.log(时间: ${meeting.scheduledTime}); // 2024-06-15T14:00:00.000Z console.log(时长: ${meeting.durationMinutes}分钟); // 90 console.log(参会人: ${meeting.participants.join(, )}); // 张三, 李四, 王五 console.log(地点: ${meeting.location}); // 301会议室 // result.content 仍然包含模型的原始文本回复 console.log(\n模型原始回复:, result.content); } extractMeeting();这个过程的核心是验证与重试。当LLM第一次返回文本时框架会尝试将其解析为JSON然后用MeetingSchema进行验证。如果验证失败比如缺少必填字段、时间格式不对框架不会直接抛出错误给用户而是会自动构造一个新的提示词其中包含验证错误的具体信息例如“scheduledTime字段必须是有效的ISO 8601日期时间字符串”然后重新调用LLM。这个循环会持续进行直到返回有效的结构化数据或者达到重试上限默认3次。4.2 错误处理与重试控制你可以捕获结构化输出失败的错误并获取详细的验证信息import { StructuredOutputError } from strands-agents/sdk; try { const result await agent.invoke(一些模糊的文本...); // 处理成功结果 } catch (error) { if (error instanceof StructuredOutputError) { console.error(结构化输出失败); console.error(错误信息:, error.message); console.error(验证详情:, error.cause?.errors); // Zod的详细错误数组 console.error(LLM最后一次返回的原始内容:, error.rawResponse); console.error(已重试次数:, error.retryCount); } else { // 处理其他类型的错误如网络错误、模型错误等 console.error(其他错误:, error); } }你还可以在创建智能体时配置重试行为const agent new Agent({ structuredOutputSchema: MeetingSchema, structuredOutput: { maxRetries: 5, // 最大重试次数 retryDelayMs: 1000, // 每次重试的延迟毫秒 }, });实操心得结构化输出功能极大地提升了AI集成到生产流水线中的可靠性。但要注意复杂的模式如嵌套对象、联合类型可能会增加LLM的理解难度和重试概率。我的经验是模式设计要尽量简单、扁平。如果确实需要复杂结构可以考虑拆分成多个步骤先让LLM提取原始文本片段再用专门的解析逻辑进行处理。5. 集成模型上下文协议MCP模型上下文协议Model Context Protocol, MCP是一个新兴的开放协议旨在标准化AI应用与外部工具、数据源之间的连接方式。你可以把它想象成AI世界的“驱动程序”标准。Strands Agents内置了MCP客户端支持让你能轻松地将任何MCP服务器提供的工具集成到你的智能体中。5.1 连接本地MCP服务器假设你本地运行了一个提供数据库查询工具的MCP服务器。集成步骤如下import { Agent, McpClient } from strands-agents/sdk; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; // 1. 创建MCP客户端通过标准输入输出stdio连接到本地服务器进程 const dbClient new McpClient({ transport: new StdioClientTransport({ command: node, // 启动服务器的命令 args: [./path/to/your-mcp-server.js], // 服务器脚本参数 }), }); // 2. 在连接客户端之前通常需要初始化例如交换密钥 await dbClient.connect(); // 3. 将MCP客户端作为一个工具源传递给智能体 const agent new Agent({ systemPrompt: 你是一个数据分析助手可以使用数据库工具。, tools: [dbClient], // 直接传入客户端SDK会自动发现其提供的所有工具 }); // 4. 现在智能体就可以使用数据库工具了 const result await agent.invoke(查询用户表中最近10条记录); console.log(result.content); // 5. 任务完成后断开连接 await dbClient.disconnect();5.2 使用现成的MCP服务器社区已经有很多优秀的MCP服务器。例如aws-documentation-mcp-server可以提供AWS服务的官方文档搜索工具。你可以通过uvx一个Python工具运行器快速启动它# 首先确保安装了 uv pip install uvimport { Agent, McpClient } from strands-agents/sdk; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; const awsDocsClient new McpClient({ transport: new StdioClientTransport({ command: uvx, args: [awslabs.aws-documentation-mcp-serverlatest], }), }); const agent new Agent({ tools: [awsDocsClient], systemPrompt: 你是一个AWS专家可以查阅最新的AWS官方文档来回答问题。, }); // 智能体现在可以回答“S3的PutObject API最近有什么更新”这类问题 // 它会自动调用MCP服务器提供的文档搜索工具注意事项MCP服务器通常运行在独立的进程中。你需要管理好这些进程的生命周期确保在智能体不再需要时正确关闭它们避免资源泄漏。另外MCP工具的动态性很强智能体在运行时才能知道具体有哪些工具可用这为构建高度可扩展的插件化系统提供了可能但也增加了对提示词设计的挑战——你需要让智能体学会“探索”和“选择”可用的工具。6. 多智能体编排实战Graph与Swarm模式当单个智能体无法胜任复杂任务时我们就需要多个智能体协同工作。Strands Agents提供了两种内置的编排模式Graph图和Swarm蜂群。这是框架最强大的功能之一。6.1 Graph模式确定性的工作流Graph模式允许你定义一个确定性的执行流程图。智能体作为图中的节点Node节点之间的边Edge定义了执行顺序。一个节点只有在它的所有前置依赖节点都执行完毕后才会开始运行。这种模式适合步骤清晰、流程固定的任务比如“研究 - 分析 - 撰写报告”。import { Agent, BedrockModel, Graph } from strands-agents/sdk; // 使用同一个模型实例节省成本并保持上下文一致性 const sharedModel new BedrockModel({ maxTokens: 2048 }); // 定义三个各司其职的智能体 const researcher new Agent({ model: sharedModel, id: researcher, // 节点必须有唯一ID systemPrompt: 你是一个研究专员。根据用户问题从提供的上下文或知识中提炼关键事实和要点。输出要简洁、客观。, }); const analyst new Agent({ model: sharedModel, id: analyst, systemPrompt: 你是一个分析师。接收研究员提供的事实进行深入分析识别趋势、矛盾或深层含义。输出你的分析结论。, }); const writer new Agent({ model: sharedModel, id: writer, systemPrompt: 你是一个文案写手。接收研究员的事实和分析师的结论将其整合成一篇结构清晰、语言流畅的简短报告。输出最终报告。, }); // 构建执行图researcher - analyst - writer const workflow new Graph({ nodes: [researcher, analyst, writer], edges: [ [researcher, analyst], // researcher 完成后analyst 开始 [analyst, writer], // analyst 完成后writer 开始 ], }); async function runGraphWorkflow() { const topic 人工智能在气候变化应对中的作用; console.log(开始处理主题: ${topic}); // invoke 方法会将初始提示传给第一个节点researcher // 每个节点的输出会自动成为下一个节点的输入的一部分 const finalResult await workflow.invoke(topic); // finalResult 包含了整个图的最终输出即writer节点的输出 console.log(\n 最终报告 ); console.log(finalResult.content); // 你还可以访问每个节点的独立输出 // console.log(workflow.getNodeOutput(researcher)); // console.log(workflow.getNodeOutput(analyst)); } runGraphWorkflow();在这个例子中执行流程是线性的、确定的。researcher先工作它的输出被传递给analystanalyst的输出再传递给writer。Graph模式也支持并行执行只需将没有依赖关系的节点同时作为起点即可。6.2 Swarm模式动态的、模型驱动的路由Swarm模式则把执行路径的决定权交给了智能体本身。每个智能体在完成任务后可以自主决定是“移交”handoff给另一个智能体还是直接给出最终响应。这模拟了人类团队协作的场景一个同事处理完他的部分后根据情况决定把任务转给更合适的另一位同事。import { Agent, BedrockModel, Swarm } from strands-agents/sdk; const sharedModel new BedrockModel({ maxTokens: 1024 }); // 定义一群智能体每个都有明确的职责描述 const receptionist new Agent({ model: sharedModel, id: receptionist, description: 接待员负责接收用户请求并进行初步分类和分流。, systemPrompt: 你是前台接待员。请分析用户请求 - 如果是简单的信息查询如天气、时间、定义请直接回答。 - 如果是需要复杂分析或创作的任务如写文章、分析数据请说“我将为您转接给专家”然后移交handoff给 specialist。 - 如果是技术性非常强的编程问题请移交handoff给 technician。 你的回答必须非常简短。, }); const specialist new Agent({ model: sharedModel, id: specialist, description: 通用专家处理复杂的分析和创作任务。, systemPrompt: 你是通用问题专家。请深入处理接收到的任务提供详细、专业的回答。这是最终步骤完成後不要移交。, }); const technician new Agent({ model: sharedModel, id: technician, description: 技术专家解决编程和深度技术问题。, systemPrompt: 你是技术专家。请解决编程或技术架构问题提供代码示例或详细方案。这是最终步骤完成後不要移交。, }); // 创建蜂群指定起始节点和最大步数防止无限循环 const swarm new Swarm({ nodes: [receptionist, specialist, technician], start: receptionist, // 所有请求都先发给接待员 maxSteps: 6, // 最多允许6次“思考-行动”步骤包括移交 }); async function runSwarm() { const queries [ 今天天气怎么样, 写一篇关于海洋塑料污染的短评。, 如何在TypeScript中实现一个安全的深拷贝函数, ]; for (const query of queries) { console.log(\n 用户提问: ${query}); const result await swarm.invoke(query); console.log( 最终回答 (来自 ${result.lastNodeId}):\n${result.content}\n); } } runSwarm();运行这段代码你会看到对于“今天天气怎么样”receptionist可能直接回答。对于“写短评”receptionist会将其移交给specialist由specialist生成最终回答。对于“TypeScript深拷贝”receptionist会将其移交给technician。Swarm模式的强大之处在于其动态性。你无需预先定义完整的流程智能体们会根据对任务的理解实时决定协作路径。当然这也对每个智能体的提示词设计提出了更高要求必须清晰地定义其职责和移交规则。编排模式选择指南选择Graph模式当任务流程固定、步骤清晰、需要严格保证执行顺序和阶段产出物。例如数据处理流水线、审核流程、多阶段内容生成。选择Swarm模式当任务类型多变、难以预先规划所有路径、需要智能体自主协作。例如智能客服路由、开放式问题解决、创意头脑风暴。混合使用在复杂系统中可以外层用Graph定义几个大的阶段每个阶段内部再用Swarm进行动态任务分配。7. 高级特性与生产实践掌握了核心功能后我们来看看那些能让你的智能体更健壮、更易观测的高级特性和实践。7.1 流式响应与实时交互对于需要长时间运行或希望提供实时反馈的应用流式响应Streaming至关重要。Strands Agents的agent.stream()方法返回一个异步迭代器让你可以实时接收处理过程中的各种事件。const agent new Agent({ systemPrompt: 你是一个讲故事的人。, }); async function streamStory() { console.log(开始生成故事...\n); for await (const event of agent.stream(讲一个关于星辰与猫的短故事。)) { switch (event.type) { case llm_start: console.log([LLM] 开始思考...); break; case llm_token: // 逐词输出模型生成的内容实现打字机效果 process.stdout.write(event.token); break; case tool_call_start: console.log(\n[工具调用] 开始调用工具: ${event.toolName}); break; case tool_call_end: console.log([工具调用] 工具“${event.toolName}”调用完成。); break; case agent_step: console.log(\n[步骤] 第${event.step}步完成。); break; case agent_end: console.log(\n\n[完成] 故事生成完毕。); break; case error: console.error(\n[错误], event.error); break; } } } streamStory();流式响应不仅提升了用户体验也是调试复杂智能体行为的利器。你可以清晰地看到智能体在每一步做了什么决定调用了什么工具。7.2 生命周期钩子与监控SDK提供了丰富的生命周期钩子Hooks让你能在智能体运行的关键节点注入自定义逻辑用于日志记录、性能监控、修改中间结果等。import { Agent } from strands-agents/sdk; const agent new Agent({ systemPrompt: ..., }); // 注册钩子 agent.hooks.on(llmStart, (context) { console.log([监控] 开始调用LLM提示词长度: ${context.messages.length}); }); agent.hooks.on(llmEnd, (context, response) { console.log([监控] LLM调用结束消耗Token数: ${response.usage?.totalTokens}); console.log([监控] 模型回复首句: ${response.content?.substring(0, 50)}...); }); agent.hooks.on(toolCallStart, (context, toolCall) { console.log([监控] 即将调用工具: ${toolCall.name}参数:, JSON.stringify(toolCall.arguments)); }); agent.hooks.on(toolCallEnd, (context, toolCall, result) { console.log([监控] 工具 ${toolCall.name} 执行完毕结果长度: ${result?.length || 0}); }); agent.hooks.on(agentEnd, (context, finalResult) { console.log([监控] 智能体运行结束总步数: ${context.step}最终结果长度: ${finalResult.content.length}); });通过这些钩子你可以轻松集成像OpenTelemetry这样的分布式追踪系统为每个智能体调用生成详细的追踪链路便于在生产环境中定位性能瓶颈和异常。7.3 对话历史管理与上下文窗口控制智能体的记忆力来自对话历史。SDK提供了灵活的对话历史管理策略。import { Agent, TokenBufferConversationHistory } from strands-agents/sdk; const agent new Agent({ systemPrompt: ..., conversationHistory: new TokenBufferConversationHistory({ maxTokens: 3000, // 历史记录的最大Token数 strategy: lru, // 当超出限制时采用LRU最近最少使用策略移除最早的消息 }), }); // 进行多轮对话 await agent.invoke(你好我叫小明。); await agent.invoke(你还记得我的名字吗); // 智能体会记得“小明”TokenBufferConversationHistory会自动计算每条消息的Token消耗使用近似算法并确保总历史不超过maxTokens限制。这对于控制API成本和使用长上下文模型至关重要。你也可以实现自己的ConversationHistory类来实现更复杂的逻辑比如将历史存储到数据库。8. 常见问题、排查技巧与性能优化在实际使用中你肯定会遇到各种问题。下面是我总结的一些常见坑点和解决思路。8.1 智能体不调用工具问题你明明定义了工具但智能体总是直接回答而不去调用工具。排查步骤检查工具描述这是最常见的原因。工具的描述是否清晰、无歧义是否准确说明了工具的用途和输入用更具体、更具操作性的语言重写描述。检查系统提示词你的系统提示词是否明确指示智能体“可以使用工具”尝试在提示词中加入“你可以使用我提供的工具来获取信息或执行操作。如果你需要的信息不在你的知识范围内请优先考虑使用工具。”降低温度Temperature将模型配置中的temperature参数调低如从0.7调到0.2。较低的温度使模型输出更确定、更遵循指令可能更倾向于调用工具。查看完整日志启用调试日志或使用流式API查看llmEnd事件中的完整模型响应。有时模型在思考过程中提到了工具但最终选择了不调用。这能帮你理解模型的“决策过程”。8.2 结构化输出验证持续失败问题配置了结构化输出但LLM始终无法返回有效的格式达到重试上限后抛出StructuredOutputError。解决思路简化模式你的Zod模式是否太复杂包含太多嵌套、联合类型或高级校验尝试先用一个极其简单的模式如z.object({ answer: z.string() })测试确认基础功能正常。增强提示在系统提示词中明确强调输出格式。例如“你必须严格按照指定的JSON格式回应。只输出JSON不要有任何额外的解释或标记。”提供示例在用户提示词中提供一个输出示例。例如“请提取信息并以如下JSON格式输出{\”name\”: \”示例人名\”, \”age\”: 30}”更换模型不同的LLM在遵循结构化指令方面能力有差异。Claude系列和GPT-4通常比一些小型或旧模型表现更好。8.3 多智能体协作陷入循环或停滞问题在Swarm模式中智能体之间来回移交无法产生最终答案或者在Graph模式中某个节点卡住。调试方法设置maxSteps这是最重要的安全网。为Agent和Swarm都设置一个合理的maxSteps如10-15防止无限循环。分析节点输出使用Graph的getNodeOutput方法或监听Swarm的流式事件查看每个节点的输入和输出。问题可能出在某个节点的输出质量不高导致下一个节点无法处理。优化节点提示词在Swarm中确保每个节点的systemPrompt和description清晰定义了其边界和移交条件。例如明确写明“这是最终步骤完成后不要移交handoff”。引入“裁判”或“终结者”在Swarm中设置一个专用的coordinator或finalizer智能体其职责就是分析当前状态并决定是否应该结束流程。8.4 性能优化与成本控制复用模型实例在创建多个Agent或编排多个智能体时尽可能复用同一个BedrockModel或OpenAIModel实例。这有助于内部连接的复用和潜在的性能优化。精细控制Token设置合理的maxTokens参数防止单次请求生成过长的内容。使用TokenBufferConversationHistory并设置合适的maxTokens自动修剪旧历史。在工具描述和系统提示词中力求简洁减少不必要的Token消耗。异步与并行对于Graph中可并行执行的节点确保它们之间没有不必要的依赖边以充分利用计算资源。实现缓存层对于频繁查询且结果不变的工具如某些数据查询可以在工具回调函数中实现简单的内存缓存或使用外部缓存如Redis避免重复调用昂贵的外部API或模型。经过上面八个部分的拆解你应该对Strands Agents TypeScript SDK有了一个从入门到进阶的全面认识。从我个人的使用体验来看它最大的价值在于将AI智能体开发从“脚本小子”的玩具级别提升到了“软件工程”的严肃级别。类型安全、模式验证、清晰的架构这些特性使得构建可靠、可维护的AI应用成为可能。当然框架目前还处于公开预览阶段API可能会有变动但核心的设计理念已经非常稳固。如果你正在寻找一个既强大又务实的TypeScript智能体框架Strands Agents绝对值得你投入时间深入探索。