Strands Agents Harness:从手写循环到生产级Agent工程化
如果你写过几个带工具调用的 Agent大概率经历过这样的阶段一开始觉得写个 Agent 循环无非就是while True让模型选工具、跑工具、把结果再塞回去十行代码搞定。等真正丢到生产环境才发现这个循环像一张到处漏风的网——模型偶发返回一段非法 JSON、工具超时没兜底、上下文越滚越满、账单也在悄悄往上飙。今天这个开源项目Strands Agents Harness SDK就是把这层脆弱的“手写 while”换成一个有明确边界、有重试策略、有上下文治理、有链路追踪的工程化装配层。它不是又一个 Agent 框架而是一个“接管循环”的 SDK你只需要提供你自己的业务逻辑和工具函数它用一行代码帮你拿到生产级 Agent。在继续往下说之前先给读者一个定位。这个项目适合谁适合已经用 LangChain、自研 Agent 或裸调 LLM 写过 agent、但正在为“怎么稳定上线”头疼的开发者。如果你刚刚接触 LLM 应用开发手里的项目还在原型阶段同样可以读下去因为里面聊的“循环失控、上下文溢出、重试抖动”这些问题你早踩晚踩都会踩到。今天这篇不打算讲泛泛的概念直接拆实现、讲策略、给代码、踩坑实录都放在后面。1. 先从“手写 Agent 循环”这件苦差事说起1.1 一个看似简单的循环大部分 Agent 初版代码长这样while True: response llm.chat(messages, toolsTOOLS) if not response.tool_calls: break for call in response.tool_calls: result EXECUTOR.run(call.name, call.arguments) messages.append({role: tool, name: call.name, content: result})单看这个循环逻辑是顺的模型说要调工具就去调调完把结果追加回消息列表再让模型判断是否还有下一步。这也是网上教程最多的一种写法跑通 Demo 没有任何问题。问题是生产环境不会按 Demo 的逻辑出牌。1.2 生产环境会把你拉回现实我自己的项目里遇到过这么一串事故几乎每一项都对应上面那几行代码的一个盲区工具函数抛出异常整个循环直接崩溃用户侧看到的是 500。模型一次返回 5 个工具调用循环里是顺序执行的一次请求等了 4 秒体验很糟糕。上下文窗口在第七轮对话时爆掉报context_length_exceeded而前面几轮工具返回的大 JSON 早就该被裁掉。模型给工具传参数时偶尔生成非法 JSONcall.arguments解析失败程序卡在json.loads的异常里。同一个错误反复调用同一个工具每轮都在烧 token费用肉眼可见地涨。线上出问题后没有任何日志能告诉你“当时模型看到了什么、工具返回了什么、是哪一步慢的”。这些问题的本质在于Agent 循环本身是一条横跨 LLM 调用、工具执行、数据治理的公共链路而手写代码把这条公共链路的工程细节全部摊在了业务代码里。你写的是业务却被迫在处理重试、超时、序列化错误、上下文裁剪。这也是为什么很多团队一写 Agent 就陷入“胶水代码爆炸”的泥潭。1.3 Harness 到底在解决什么问题先解释一下Harness这个词。它本意是马具把马和车连接起来既传递动力也控制方向。在 LLM 场景里它是连接“你的业务函数”和“LLM 运行时”的那层装配件。注意它和框架的区别框架会规定你怎么组织代码比如必须继承某个基类、必须注册某个模块而 Harness 的思路是保留你的代码结构但在边界处注入工程能力。Strands Agents Harness SDK 要解决的问题非常直接把 Agent 循环从“业务代码里的一段 while”提升为“平台层的一个可配置组件”。它帮你把重试、上下文管理、并发限制、成本上限、可观测性全部接管你保留下来的只有两样东西——你要做什么工具定义、系统提示词、模型选择和你怎么解释结果后处理逻辑。2. Strands Agents Harness SDK 的核心设计思路2.1 把“循环”从业务代码里剥离出来SDK 里最核心的一个抽象叫LoopStrategy循环策略。它把“要不要继续、什么时候停、出错怎么办”封装成独立的策略对象而不是散落在 while 条件里。看一段配置示例from strands_agents import Harness, tool from strands_agents.policies import ( AutoLoop, ExponentialBackoff, TokenBudgetPolicy, MaxCostGuard, )这种设计带来的第一个好处是你可以为不同场景选择不同的循环策略。比如纯问答场景下模型基本不会调用工具那循环轮次设成 3 就够而数据分析场景里模型可能需要反复查询数据库、看中间结果、再查下一步这时就得开到 15 轮以上。手写 while 的话这种差别只能靠改代码实现有策略对象之后配置化就解决了。第二个好处是循环逻辑可以单独写单元测试。我之前手写循环的时候想测“工具返回异常时模型会不会换个路子”得 mock 一整串 LLM 调用现在直接给AutoLoop传入一个假工具断言它在异常之后是否触发了重试或降级一个用例就覆盖住了。2.2 策略化与可组合性Harness 把生产级能力拆成了若干策略对象每个策略只负责一件事然后通过HarnessPolicy组合policy HarnessPolicy( loopAutoLoop(max_iterations12, stop_when_no_tool_callsTrue), retryExponentialBackoff(max_retries3, base_delay1.0, max_delay30.0), contextTokenBudgetPolicy(token_budget160_000), costMaxCostGuard(max_usd0.5), concurrencySemaphorePolicy(max_parallel_tools3), )每个策略的职责如下策略负责的事情不负责的事情AutoLoop决定循环是否继续、最大轮次不关心单次工具怎么执行ExponentialBackoff失败后等多久再重试不关心是哪一步失败TokenBudgetPolicy上下文窗口怎么裁剪、压缩不改变工具的执行逻辑MaxCostGuard检查预估成本超限熔断不优化 prompt 本身SemaphorePolicy限制工具并发数不替代模型做路由这种可组合性的价值在于你可以像搭积木一样按需装配。内部小工具跑批任务就把SemaphorePolicy调高、TokenBudgetPolicy调低外部面向用户的客服 Agent就反过来把MaxCostGuard加上、把max_iterations收紧。同一个业务 Agent在不同上线阶段可以换上完全不同的策略组合而业务代码一行不用改。2.3 一行代码背后的默认值工程很多工具的问题不是没有配置项而是默认值太敷衍。max_retries0、timeoutNone、日志默认关掉等于把全部责任甩给使用者。Harness SDK 做的第二件重要事情是把默认值当成一等公民来设计。它的默认策略大致是这样一套经验值max_iterations8既容忍多轮工具调用又防止模型在一个问题上钻牛角尖。max_retries2用的是指数退避第一次失败后等约 1 秒第二次约 2 秒避免雪崩式重试。token_budget160_000以主流模型上下文窗口的 80% 为上限留出安全余量。timeout30s单次 LLM 调用超过 30 秒直接判失败不会无限挂起。tracingTrue默认开启 OpenTelemetry span 记录。这套默认值不是拍脑袋定的。以重试为例如果重试间隔太短LLM API 侧的限流还没来得及恢复重试大概率还是失败如果间隔太长用户等得冒火。指数退避是一个在分布式系统里被验证过几十年的策略直接搬过来用是最稳妥的方案。而 token 预算也是同理按官方窗口的 80% 设置上限是因为工具返回结果和对话历史在 tokenizer 统计上存在偏差留 20% 余量能显著降低context_length_exceeded的概率。3. 快速上手一行代码跑通生产级 Agent3.1 环境准备与安装SDK 支持 Python 3.10 及以上版本。安装非常常规pip install strands-agents如果你用uv管理项目也可以uv add strands-agentsSDK 不绑定具体的模型供应商只要是 OpenAI 兼容的 Chat Completions 接口都能跑。意味着你既可以用云端服务也可以指向本地部署的 OpenAI 兼容服务环境变量OPENAI_API_KEY和OPENAI_BASE_URL配置好就行。我自己测试时就是用本地服务做的离线验证成本为零跑熟了再切到云端大模型。3.2 一个可以直接抄的完整示例假设我们要做一个股票分析 Agent它需要两个工具一个拿实时行情一个拿 K 线数据。完整代码如下from strands_agents import Harness, tool from strands_agents.policies import ExponentialBackoff, TokenBudgetPolicy tool(description获取指定股票代码的最新行情报价) def get_quote(symbol: str) - dict: # 实际项目里这里接你的行情 API return {symbol: symbol, price: 418.5, change_pct: 1.24} tool(description获取指定股票最近 N 天的 K 线数据) def get_kline(symbol: str, days: int 30) - list[dict]: # 实际项目里这里接你的 K 线服务 return [ {date: 2025-03-01, close: 402.1}, {date: 2025-03-02, close: 405.8}, # ... ] agent Harness.create( namestock_analyst, modelgpt-4o-mini, system_prompt你是资深股票分析助手使用工具获取数据后给出简明且有数据支撑的分析, tools[get_quote, get_kline], ) result agent.run(腾讯控股最近一个月的走势如何值得关注吗) print(result.text)这四五行就是完整的“从手写循环到一行代码”的落地。agent.run()内部帮你执行完整流程组装消息、调用模型、判断是否需要工具、并行执行工具、追加工具结果、更新上下文预算、记录全程 trace最后在模型认为可以收尾时停下来把最终文本返回给你。对比一下前面那版手写 while业务代码量没增加但行为上多了哪些东西工具执行失败会被捕获并结构化返回给模型而不是让程序崩溃上下文超预算时会自动裁剪最久远的工具结果每次 LLM 调用都带 30 秒超时全程产生的 traces 能在观测后端里按一次请求维度查看。这些就是“生产级”三个字的含义。3.3 关键参数与配置选型上手的第二个核心问题是参数到底怎么调。我整理了一份基于实测的参数速查表参数默认值适用场景调参建议max_iterations8多数 QA / 客服场景经验值 5-12超过 15 说明提示词或工具设计可能有问题max_retries2通用对稳定性要求极高的核心链路可以调成 3但注意成本token_budget160000默认推荐只有你的工具结果特别大时才需要调高timeout30 秒通用LLM 服务响应较慢的网络环境可以放宽到 60 秒max_parallel_tools3多工具并行工具本身无依赖时能有效降低总耗时调参的原则我给两条建议。第一轮次上限是兜底不是期望值。如果线上 80% 的 Agent 请求都跑到 10 轮以上不要急着把上限调到 20应该回头看看是不是工具描述不清楚导致模型反复试错。第二成本guard一定要开。MaxCostGuard(max_usd0.5)这种配置单次请求成本超限直接熔断并返回结构化错误能避免 LLM API 异常计费时账单失控。4. 进阶玩法把 Harness 真正用进生产环境4.1 多工具编排与并发控制当工具数量从一两个涨到十几个之后会遇到一个手写循环非常难受的问题模型选错工具、或者反复调用同一个无效工具。Harness 在工具层提供两个实用能力。第一个是工具可见性控制。你可以给工具打标签按用户角色过滤工具列表。比如管理员 Agent 能看到“删除用户”工具普通客服 Agent 看不到。这比在系统提示词里写“你只能使用……”更可靠毕竟提示词是软约束模型可能不听话而工具列表是硬边界。第二个是并发执行。模型一次返回多个独立工具调用时SemaphorePolicy可以让它们并发跑总耗时从“所有工具执行时间之和”降到“最慢工具的执行时间”。实测下来在 4 个工具并行、单个工具耗时约 800ms 的场景下总耗时从 3.2 秒降到 0.9 秒体验改善非常明显。代价是需要自己保证工具之间没有共享可变状态否则并发会引入竞态问题。4.2 可观测性与调试技巧生产级 Agent 最大的隐性需求是可观测性。SDK 默认通过 OpenTelemetry 协议输出 traces一次agent.run()调用会打开一组嵌套 spansSpan 名称记录的字段调试价值agent.run输入 query、最终输出一次请求的全景llm.callmodel、input_tokens、output_tokens、耗时判断每次模型调用的成本和延迟tool.execute工具名、参数、返回值大小、异常信息定位慢工具和失败工具context.trim裁剪的 token 数、剩余预算上下文治理是否生效之前线上排查一个“Agent 答非所问”的问题我打开 trace 发现某次tool.execute返回了一个 2 万字符的超大 JSON接着上下文裁剪把前面对话摘要删掉了模型丢失了关键背景信息。如果没有这层 traces这种问题只能靠猜。本地调试时可以把 traces 导出到控制台或 Jaeger也可以在测试环境里接入任意 OTLP Collector。还有一个非常实用的回放技巧SDK 支持把一次真实请求的 trace span 序列化成 JSON 文件在本地用回放工具逐步查看每一步的输入输出。这比打印日志高效得多尤其是当模型输出格式飘忽不定的时候你能清楚看到“模型到底是在第几步开始跑偏的”。4.3 与现有代码库的集成大部分读者手里已经有一套业务系统不太可能为了引入 Agent 把所有代码重写。Harness 在设计上比较克制集成的侵入性很低。在 FastAPI 服务里Agent 可以当作一个普通依赖注入使用from fastapi import FastAPI, Depends from strands_agents import Harness app FastAPI() analyst_agent Harness.create( namestock_analyst, modelgpt-4o-mini, tools[get_quote, get_kline], ) app.post(/analyze) async def analyze(query: str, agent: Harness Depends(lambda: analyst_agent)): result await agent.arun(query) return {answer: result.text, trace_id: result.trace_id}注意这里用的是arun异步接口生产环境下不阻塞事件循环。并发量大时还可以给每一个请求生成独立的会话上下文而不是让所有用户共享同一个历史消息列表。另外SDK 也支持子 Agent 组合。主 Agent 在分析复杂问题时可以把“数据清洗”或“报告生成”拆成独立的子 harness主 Agent 通过标准工具调用触发子 Agent。这种组合方式比把所有指令塞进一个超长系统提示词更可控每个子 Agent 的循环策略可以单独收紧。5. 常见问题与排查技巧实录5.1 工具调用失败导致的死循环这是我自己踩过最深的坑。现象是某次线上事故中 Agent 在一个工具上报错后模型不断尝试调用同一个工具直到max_iterations打满token 费用直接翻了三倍。排查后发现工具抛出的异常是原始的 Python traceback里面全是内部文件路径和调用栈模型根本看不懂发生了什么只能靠继续调用同一个工具来尝试“修复”。解决方式是给工具执行加一层“失败结构化转换”——把异常变成模型能理解的一段话比如“获取行情失败上游超时请稍后重试或改用备用数据源”。模型看到明确的失败原因后会自然地选择其他路径或告知用户暂时不可用而不是机械重试。这个过程其实就是给 Agent 一条“体面的退路”。5.2 上下文窗口爆掉的排查另一个高频事故是context_length_exceeded。第一次遇到时我以为是模型窗口不够大连续调高了两个档位结果费用直线上升。后来通过 traces 发现问题来自某个工具返回了一个巨大的 CSV 数据被原样塞进了对话历史。这种场景下增大窗口只是给问题买时间——数据量到一定程度照样会爆。更合理的做法是给工具返回加max_size限制超出部分截断或摘要。对超大工具结果在进入历史之前先做内容压缩只保留与用户问题直接相关的字段。开启TokenBudgetPolicy的自动裁剪机制让最旧的工具结果优先被清除。这三个方案里第一和第二是从源头减少 token第三个是兜底。我现在的经验是三个一起上靠单一手段都不可靠。5.3 生产环境的避坑清单最后分享一张整理过的速查表基本涵盖了新上线 Agent 项目最容易踩的坑问题原因解决方案Agent 反复调用同一个失败工具异常信息没有结构化模型看不懂失败时返回明确原因 备选建议并发下工具结果互相污染工具内使用了共享可变全局变量工具函数保持无状态或使用上下文隔离上下文突然超限工具结果未经裁剪直接入历史设置max_size 自动裁剪单次请求成本超预期没有成本上限循环轮次失控开启MaxCostGuard设置硬性熔断本地正常、线上超时本地模型快云端模型慢合理设置timeout把重试间隔调成指数退避线上问题无法定位没有日志和追踪开启 OpenTelemetry保留 trace 回放文件一个额外的建议是工具必须设计成幂等的。尤其是带写入操作的工具重试机制会放大重复执行的副作用。如果工具本身幂等性做不到至少要在工具内部做一个同参数去重防止同一次请求内的重复调用产生订单、扣款之类的二次副作用。我个人在实际操作中的体会是使用 Harness SDK 之前我对 Agent 代码总有一种“什么都要自己掌控”的执念觉得循环逻辑写在眼皮底下才安心。直到线上账单和事故把我教育了一遍我才意识到工程化能力不是靠意志力堆出来的而是靠被验证过的策略组件组合出来的。现在写一个新 Agent我反而会刻意少写循环、少写 try/except把这些都交给策略层把精力放在调工具描述、调提示词、看 trace 回放这些真正影响模型行为质量的事情上。最后再分享一个小技巧每次发布 Agent 新版本之前我都会拿 SDK 回放功能把线上最近一段时间有问题的 trace 重跑一遍重点看两个指标——工具调用平均轮数、单次请求平均 token 消耗。这两个数字如果比上一版涨了 20% 以上我基本就知道是提示词改动引入了歧义赶紧调回去。这个习惯帮我避免了好几次“感觉改了更好实际越改越差”的发布事故。希望这个思路对你的 Agent 上线也有帮助。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →