ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent 部署不稳定?从 RunConfig 开始掌控运行时配置

AI Agent 部署不稳定?从 RunConfig 开始掌控运行时配置 同一套AI Agent代码你在本地跑得好好的部署上去后却表现判若两人——回答变慢、上下文串场、输出时粗时细。你反复检查Prompt和工具函数都没发现问题其实很可能是“运行配置”这一层出了状况。在Google的ADKAgent Development Kit里这一层被集中抽象成了Runtime Config也就是RunConfig。它用一组配置项回答了“这个Agent在被调用时究竟应该怎么跑”用哪个模型、生成参数是多少、会话状态怎么存、要不要缓存、超时和指标怎么处理。把这些配置清楚之后你的Agent才谈得上稳定、可控、可复现。这篇文章适合三类读者刚开始用ADK搭Agent、想让Agent从“能跑”变成“好跑”的开发者遇到Agent行为不稳定但查不出逻辑问题的实践者以及准备把Agent部署到线上、需要控制成本与延迟的工程师。我会尽量把RunConfig的每一块面板都讲明白并附上可以直接抄的配置示例。1. 先理解一件事RunConfig到底在配置“什么”1.1 一次真实的现象同样的Agent两种表现先讲一个我在实际调试中遇到的例子。我做了一个用于售后答疑的Agent核心逻辑很简单根据用户描述找到问题分类然后调用知识库工具给出解决方案。Prompt写得挺标准工具函数的输入输出也调通了。本地跑一遍测试用例全过。然后部署到测试环境跑了不到半天就暴露出一堆问题有两个用户问相似的问题第二个用户得到的答案里居然混进了第一个用户的历史信息同一个问题连续问两遍第二次Agent“换了个说法”最长的一次回答直接超时。第一反应是模型不稳定或者是知识库接口波动。后来把日志翻出来才明白跟这些都没有关系真正的原因是运行时这一层太“裸”了——会话状态没有正确隔离、生成参数没设、工具结果缓存策略缺失结果Agent在一个不受控的“野”状态下运行。这其实特别典型。很多人第一次用ADK写Agent把注意力全放在Agent的名字、模型和指令上忽略了RunConfig。代码能跑但“怎么跑”完全不受控。1.2 RunConfig是驾驶规则不是发动机我习惯用一个类比来理解RunConfig和Agent逻辑的关系。Agent本身可以看作一辆车。Prompt是发动机工具函数是轮子这些都是“硬件”层面的东西决定了这辆车能跑多快、能走什么路。而RunConfig是驾驶规则——用几挡起步、什么时候换挡、每个路口怎么转向、要不要开巡航、油表什么时候报警。同一辆车让不同驾驶规则去开表现完全不同。放在ADK的语境下Prompt负责“Agent是什么、懂什么”工具负责“Agent能做什么”RunConfig负责“Agent被调用时每一步具体怎么跑”1.3 RunConfig挂在哪里和Agent、Runner的关系在ADK里RunConfig不是孤立的一块它分布在不同的配置入口Agent初始化时model、instruction、generation_config、tools、run_config等Runner初始化时session_service、artifact_service等Session创建时状态存储方式RunConfig和Runner的分工大致是Agent层的配置决定了单个Agent内部的生成行为Runner层的配置决定了Agent和外部世界的交互方式怎么接收消息、怎么保持会话、怎么返回事件流。如果落到代码上from google.adk.agents import Agent from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService agent Agent( namesupport_agent, modelgemini-2.0-flash, instruction你是一名售后客服始终基于知识库内容回答不做猜测。, ) session_service InMemorySessionService() runner Runner( agentagent, app_namesupport_app, session_servicesession_service, )上面这段代码里Agent的名字、模型、指令是Agent的核心配置而Runner里的session_service就是运行时配置的一部分。RunConfig要做的就是把这些“怎么跑”的参数统一规划好而不是想到哪个设哪个。2. 逐个旋钮拆解RunConfig核心配置项详解2.1 模型选择model与endpoint第一个绕不开的配置就是模型。ADK把模型选择做成了Agent的一个标准参数agent Agent( namemy_agent, modelgemini-2.0-flash, )这里model指定了Agent推理时用的模型。我提一个容易忽略的点ADK的Kotlin版本目前内置模型只支持Gemini系列这也是我在项目里看到的一个明确限制而Python版本可以通过endpoint等配置连接不同的模型服务灵活性更高。如果你在Kotlin工程里想换非Gemini模型大概率是要自己扩展适配层了。model的参数选择要结合任务复杂度简单的分类、抽取、格式化任务用响应快的轻量模型复杂的推理、规划、多步工具调用用能力更强的模型endpoint参数决定了模型服务的访问地址。默认情况下SDK会用内置的默认endpoint。只有在走自定义API网关、私有化部署或者做本地Model Runner测试的时候才需要显式设置。这里我多说一句千万别在生产环境随便改endpoint除非你确实有一个稳定的自定义网关在背后。对了很多人遇到“agent execution terminated due to error.”这类错误其实很多就跟endpoint配置指向不可服务的地址有关。先看endpoint再看认证最后才怀疑Prompt。2.2 生成质量temperature、top_p、max_output_tokens生成参数是调节“Agent输出风格”最直接的一批旋钮。在ADK中GenerationConfig或generation_config就是干这个的agent Agent( namecreative_writer, modelgemini-2.0-flash, generation_config{ temperature: 0.7, top_p: 0.95, max_output_tokens: 2048, }, )temperature控制随机性。数值越接近0输出越确定、越稳定数值越高越有发散和创造力。对客服、工单分类这类任务建议0到0.3之间对创意文案、头脑风暴0.7到0.9能明显看出效果差异。top_p核采样模型只在累计概率达到top_p的候选token里选。通常和temperature配合使用基础配置里设0.9-0.95就够了。max_output_tokens单次输出的最大token数。如果不设模型会按默认上限生成。做对接时一定要检查这个值否则长答案会被截断轻则内容不完整重则JSON解析失败。Kotlin版本里对应的是GenerationConfig类参数名是驼峰形式比如maxOutputTokens语义完全一样。不同任务场景的推荐配置我列个参考表任务类型temperaturetop_pmax_output_tokens配置要点客服/售后0.0-0.30.9256-512稳定优先避免编造内容创作0.7-0.90.952048以上创意优先允许发散数据抽取/格式化0.0-0.20.91024-2048精确优先输出可控这里有个实操建议把“任务类型”和“生成参数”绑定做成一份场景化的配置模板。客服Agent一定用低temperature加较短的max_output_tokens确保回复稳定且不啰嗦创意Agent则反过来。不要让所有Agent共用一套默认参数否则一定会出现“该稳定的不稳定、该发散的太干瘪”的尴尬情况。2.3 记忆机制会话服务与状态持久化Agent单次生成模型只能“看到”当前上下文但Agent产品是多轮对话的所以会话状态必须由运行时来管。ADK里这块的叫法是SessionService。from google.adk.sessions import InMemorySessionService session_service InMemorySessionService()InMemorySessionService把会话直接放在内存里优点是快、零配置缺点是进程一停所有会话跟着消失。所以它的定位是本地调试和Demo不是生产。生产环境要接入持久化的SessionService实现把session存到数据库或分布式存储里。这样Agent重启、扩容之后用户的历史上下文还能继续使用。然后就是RunConfig里的session配置块主要定义了会话的存储位置、会话ID的生成规则、上下文清理策略。常见做法是每次用户请求都带上同一个session_idAgent自然就能读到历史。我遇到过一个很隐蔽的失忆问题底层模型是支持长上下文的但会话服务配置了过短的历史截断导致多轮之后Agent“忘了”前面的关键信息。排查时不会直接报错只表现为回答质量突然下降。所以会话配置里上下文保留策略一定要和模型的上下文窗口做匹配。2.4 成本与速度缓存策略RunConfig里的caching配置很多人一开始完全忽略。因为Agent逻辑写出来之后本地反复调试每次都要真实调用模型既慢又费token。ADK支持对请求和工具结果做缓存。典型收益场景工具函数的返回结果基本稳定比如天气查询、价格查询同一个用户短时间内反复触发同一个工具相同上下文的重试请求配置了缓存之后相同的输入直接命中缓存不再重复调用模型或工具。这个对生产环境的成本和延迟优化非常明显。需要注意缓存失效问题。缓存不是万能的如果工具结果本身就是变化的比如实时汇率还去加缓存用户会拿到过时的数据。我的经验是只对“结果确定性高、时效要求低”的工具开缓存对实时性敏感的工具要么不缓存要么设置很短的过期时间。2.5 其他值得注意的参数RunConfig还会涉及几个不那么显眼、但可能决定上线成败的参数认证配置authAgent在调用受限资源时的身份凭证。多Agent协作时每个子Agent可能有不同的权限这时候授权配置要安排明白。指标收集metrics把每一次调用耗时、token消耗、错误类型打点输出。这个在排查问题的时候价值极高。实时事件live events决定Agent执行过程中的中间事件要不要实时推给客户端。做流式输出和进度展示时会用到。这些参数日常调试可能用不上但一到上线评估阶段就是必选项。我建议从第一天就把metrics打开不要等到线上出问题再去补。3. 实战从最小可用到多Agent协作的配置演进3.1 第一步最小可用配置先用最短的代码把Agent跑起来。from google.adk.agents import Agent from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService agent Agent( namebasic_agent, modelgemini-2.0-flash, instruction你是一个友好的助手。, ) session_service InMemorySessionService() runner Runner( agentagent, app_namebasic_app, session_servicesession_service, ) session session_service.create_session( app_namebasic_app, user_idu001, session_ids001, ) events runner.run( user_idu001, session_ids001, message你好介绍一下你自己。, ) for event in events: if event.content: print(event.content)这段代码没有任何显式的RunConfig跑起来直接、干净。它的作用是验证链路通不通虽然在生产上是不可用的但在学习阶段这是最正确的第一步。3.2 第二步针对真实场景调整生成参数假设现在要做一个售后客服Agent。需求很明确回答必须基于知识库不能自由发挥回复要简洁多轮对话要记得用户之前的技术问题。那配置就会变成from google.adk.agents import Agent agent Agent( namesupport_agent, modelgemini-2.0-flash, instruction( 你是售后客服助手。请严格根据知识库文档回答用户问题。 不确定的内容就说明不确定不要编造。回复控制在200字以内。 ), generation_config{ temperature: 0.2, max_output_tokens: 512, }, )我把temperature从默认值降到了0.2输出上限设到512。这个组合特别适合客服场景稳定优先、简洁优先。实测下来答案一致性能提升非常明显用户角度感受就是“这个客服不飘了”。3.3 第三步把会话从内存搬到持久化存储接下来是接线到生产的第一步——会话持久化。把InMemorySessionService换成持久化实现。# 把 InMemorySessionService 替换为你项目里接入的持久化实现 session_service PersistentSessionService(database_urlsqlite:///sessions.db) runner Runner( agentagent, app_namesupport_app, session_servicesession_service, )这一步做完Agent重启、横向扩容之后用户历史仍然保留。用户id加session id的设计也保证了不同用户之间的会话完全隔离。3.4 第四步多Agent协作与配置隔离很多场景不是一个Agent能覆盖的。我做过一个旅游咨询Agent主Agent负责对话入口子Agent分别覆盖酒店查询、机票查询、行程规划。每个子Agent挂不同的工具配置按各自任务做了差异化hotel_agent Agent( namehotel_agent, modelgemini-2.0-flash, instruction你是酒店查询助手只处理酒店相关请求调用酒店搜索工具。, tools[hotel_search_tool], generation_config{temperature: 0.2}, ) flight_agent Agent( nameflight_agent, modelgemini-2.0-flash, instruction你是机票查询助手只处理航班相关请求。, tools[flight_search_tool], generation_config{temperature: 0.2}, ) main_agent Agent( nametravel_assistant, modelgemini-2.0-flash, instruction你是旅游助手负责理解用户意图并分发给对应的子Agent。, sub_agents[hotel_agent, flight_agent], )子Agent的配置是独立的这就是多Agent协作里很重要的“配置隔离”思想。根Agent不需要也不应该知道酒店搜索的细节它的职责是路由和汇总。反过来子Agent不需要也不应该继承根Agent的Prompt。这种隔离让每个Agent边界清晰出现问题也容易定位。3.5 完整可运行示例最后给一个相对完整的示例把上面的点都串在一起from google.adk.agents import Agent from google.adk.runners import Runner from google.adk.artifacts import InMemoryArtifactService # 子Agent酒店查询 hotel_agent Agent( namehotel_agent, modelgemini-2.0-flash, instruction你是酒店查询助手只处理酒店相关请求调用酒店搜索工具。, tools[hotel_search_tool], generation_config{temperature: 0.2, max_output_tokens: 512}, ) # 根Agent旅游助手 main_agent Agent( nametravel_assistant, modelgemini-2.0-flash, instruction你是旅游助手负责理解用户意图并分发给对应的子Agent。, sub_agents[hotel_agent], generation_config{temperature: 0.3, max_output_tokens: 1024}, ) session_service PersistentSessionService(database_urlsqlite:///sessions.db) runner Runner( agentmain_agent, app_nametravel_app, session_servicesession_service, ) session session_service.create_session( app_nametravel_app, user_idu001, session_ids001, ) events runner.run( user_idu001, session_ids001, message帮我找一下南京夫子庙附近评分4.5以上的酒店, ) for event in events: if event.content: print(event.content)这个示例可以直接抄来改。到这一步你手里的Agent已经具备了稳定的生成参数、持久化的会话、清晰的Agent边界。从最小可用到能上线RunConfig相关的核心配置基本覆盖完了。接下来如果还想继续优化通常就该往性能、成本和可观测性方面走了。4. 常见配置异常的完整排查链路这一章写几个我实际踩过的坑每个都按“现象-排查-根因-修复”的顺序来。4.1 现象一模型配置没生效Agent用了奇怪的默认行为有次我建了一个Agentmodel明明设成了gemini-2.0-flash但实际输出风格明显不是这个模型该有的表现。第一反应是SDK bug。排查链路确认Agent初始化代码里model确实传了——检查了传了。查看环境变量——发现系统里存在一个旧的GOOGLE_GENAI_MODEL环境变量SDK的加载优先级导致环境变量覆盖了代码里的配置。清掉环境变量重启问题消失。这个坑的教训是配置优先级要理清。代码参数、RunConfig、环境变量这三者有明确的覆盖关系别想当然认为代码里的值一定生效。排查的时候先看有没有“更高优先级”的配置源存在。调试时可以打印Agent初始化后的实际配置对象很多时候配置对象里已经反映了加载完成的最终值确认覆盖关系很快。4.2 现象二temperature设了0输出还是不稳定另一个让我困惑的问题是客服Agent明明把temperature设成了0但同一问题问两次答案还是有细微差异。排查链路先怀疑模型API是不是忽略了temperature——从日志看请求参数里temperature确实传了0。再怀疑是不是有多个generation_config在互相覆盖——检查Agent初始化发现子Agent自己又设了一个temperature0.7的配置而调用链路上实际走的是子Agent。把子Agent配置改一致重新测试输出稳定了。核心教训在多Agent场景里实际生效的是你调用链路末端那个Agent的配置。根Agent的配置不会自动传给子Agent必须各自显式设置。4.3 现象三Agent“失忆”多轮对话上下文丢失用户说第三句话时Agent完全忘了前面两句话的内容。这是那种不报错但体验极差的问题。我的排查链路确认每次run都带了同一个session_id——检查代码确实带了。再看session_service的实现——发现用的是InMemorySessionService服务进程有一次重启所有内存会话都没了。切换到持久化session service并确认会话恢复逻辑正确问题解决。另外补充一个隐藏坑即使session没丢如果上下文整理策略配置太激进比如每轮只保留最后一条消息Agent照样会“失忆”。要把历史保留策略和模型上下文窗口对齐。4.4 现象四运行时卡顿与超时最后一个是性能和超时问题。Agent逻辑不复杂但线上经常出现单次响应超过20秒的情况。排查链路先看metrics——发现模型调用本身耗时不高大头花在了等待工具返回上。再看工具调用——发现一个外部HTTP接口每次调用需要5秒而Agent为了确认结果会连续调用三次。优化方法分两层一是给工具调用加缓存相同参数直接复用上次结果二是在运行时配置里限制Agent的最大执行轮次减少无意义的重复调用。调优之后平均响应时间从20多秒降到了6秒左右。这个案例说明性能问题不一定是模型慢有时是运行时调度和工具调用策略不合理。RunConfig里的轮次限制、超时策略都是调节杠杆。我平时会把“耗时指标”看作配置调优的导航仪哪个环节数值异常就去对应那一层找问题。5. 最后聊几点我的个人体会写了这么多最后分享几个我实际工作中的习惯不保证全对但至少帮我少踩了很多坑。第一把配置模板化。我在项目里会维护一个config目录按场景拆文件support.conf、creative.conf、multi_agent.conf。每个场景都固化好model、temperature、max_output_tokens、session_service等关键值。新Agent直接套模板不裸写参数。第二默认打开metrics。无论做demo还是正式项目我都会把调用耗时、token消耗、错误类型的打点打开。出了问题先看数据不要猜。第三版本化配置。RunConfig这种“怎么跑”的配置和代码一样应该进入版本管理。常常有这种情况线上表现突然变化一查是某次部署把配置改了。配置入库才好追溯。第四小步验证。给一个Agent改配置时不要一次改七八个参数。一次改一个跑一轮测试看效果再改下一个。否则出了变化你根本不知道是哪个参数引起的。第五预生产环境必须有一份和线上完全一致的RunConfig。很多问题都是因为预生产和线上的配置漂移。我的做法是部署时用配置渲染模板从同一个配置文件生成预生产和线上两份配置人工review差异。这个习惯可以明显减少“测试环境好好的上线就出问题”的诡异事件。RunConfig看起来不复杂但它决定了Agent在真实环境里的稳定性、成本和响应速度。把“怎么跑”配置清楚你的Agent才能真正从Demo变成产品。
返回列表