ADK 代理调试实战指南:从 `adk run` 到 `adk web` 的会话、事件与 Trace 排查体系

发布时间:2026/9/13 9:25:59
ADK 代理调试实战指南:从 `adk run` 到 `adk web` 的会话、事件与 Trace 排查体系 ADK 代理调试实战指南从adk run到adk web的会话、事件与 Trace 排查体系【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python本篇指南聚焦 Google ADKAgent Development KitPython 开源库中面向调试场景的完整工具链无头命令行工具adk run与带浏览器 UI 的开发服务器adk web以及它们背后的会话Session、事件Event、日志Log与 Trace 机制。读完你将掌握如何快速复现异常代理行为、读取模型真正收到的请求与响应、定位工具调用与分支路由问题并能够基于事件流与 Span 属性在源码级定位故障根因。本文主体基于仓库中.agents/skills/adk-debug/技能文档并结合src/google/adk/下的实现源码与tests/unittests/中的测试用例展开印证。调试入口的选择两条互补的排查路径ADK 提供了两个调试入口它们的取舍非常清晰adk run单进程、无服务器、跑完即退出。其--jsonl输出是纯事件流可以直接管道给grep或python3做脚本化断言适合快速复现与自动化排查。adk web一个基于 FastAPI 的开发服务器提供浏览器 UIhttp://localhost:8000/dev-ui/与 HTTP API支持持久化会话点击查看并暴露了能够展示模型原始请求的 Trace 端点。默认优先使用adk run当需要浏览器界面、可点击浏览的持久会话或需要call_llm等 Trace 端点来查看模型真实收到的请求时再切换到adk web。从源码结构看这两个入口位于src/google/adk/cli/下api_server.py提供生产安全路由HTTP API 主体dev_server.py提供仅限开发调试的路由包括/dev/...的 trace 端点文档中提到的adk_web_server.py已标记为废弃的兼容垫片AdkWebServer现在只是DevServer的子类。调试第一步无头复现与日志定位1. 无头复现Headless Reproduction在adk run后追加查询参数即可执行单轮对话并退出不追加查询则进入交互式提示符。排查时优先使用带查询的形式——它不需要人在回路且天然与 shell 管道工具链兼容adk run --jsonl {agent_dir} {query} adk run --jsonl --in_memory {agent_dir} {query} # 不持久化会话关键点在于不加--jsonl时adk run只打印文本部分工具调用与工具错误完全不可见。这也是代理明明调用了工具终端却什么都看不到的最常见原因。2. 日志文件去向adk run与adk web完全不同我开了-v却什么都没看到——这通常不是日志没生成而是看错了地方。日志目的地按命令区分命令日志目的地adk run{tempdir}/agents_log/agent.{timestamp}.logLinux 上即/tmp/agents_log/...并提供agent.latest.log符号链接。它会清空根 logger 的 handlers因此终端上什么都不打印adk web、adk api_server、adk eval等其余命令stderr经logging.basicConfig不创建日志文件adk run -v {agent_dir} {query} tail -F /tmp/agents_log/agent.latest.log-v是--log_level DEBUG的快捷方式级别依次为DEBUG、INFO、WARNING、ERROR、CRITICAL。ADK 自身的日志记录在google_adklogger 下因此可以grep google_adk过滤或在进程内只调高该 logger 的级别。日志设置逻辑位于 logs.py。对adk web而言建议把 stderr 同时输出到文件方便自己与用户共同查看adk web -v {agents_dir} 21 | tee {readable_path}/adk_web.log3. 症状先行对照故障模式清单在阅读源码之前先对照 failure-modes.md 中的已知故障形态——绝大多数异常报告都属于少数几种已知模式先匹配症状能极大缩短定位时间详见下文高频故障模式一节。4. 文本正常但路由不对读事件流如果文本输出正常但代理行为不符合预期例如该转移的没有转移转储事件重点看author、branch、nodeInfo.path与actions字段详见下文事件流一节。5. 模型本身行为异常读call_llmSpan如果怀疑是模型自己抽风不要凭代理定义猜测直接读取call_llmSpan 中模型实际收到的请求详见下文Trace 端点一节。adk run深度使用Flag、JSONL 事件与退出码完整 Flag 一览Flag默认值使用场景--jsonl关闭每个事件以一行 JSON 输出到 stdout不加时只打印文本部分工具调用、工具错误与 actions 均不可见--in_memory关闭跳过本地会话存储避免多次运行互相污染--session_id {id}新会话查询模式下复用该会话不存在则创建——这是跨多次adk run携带状态的方式交互模式下仅用于命名--save_session写入的文件--state {json}无为只在特定状态下出错的运行预置会话状态--replay {file.json}无将保存的状态 查询列表回放到全新会话与查询参数互斥--resume {file.json}无重新打开--save_session保存的会话并继续交互仅交互模式--timeout 30s无为卡死的单轮运行设置上限避免无限等待-v/--log_level DEBUGINFO提高日志级别输出进入日志文件而非终端--default_llm_model {model}无为未显式设置模型的代理覆盖模型用于验证是不是模型的锅完整列表以adk run --help为准。JSONL 事件形状先看真实结构再写解析器每一行是Event.model_dump(modejson, by_aliasTrue, exclude_noneTrue)的结果因此键名是 camelCaseinvocationId、functionCall、longRunningToolIds并注入session_id与node_pathauthor排在首位。空的actions条目会被丢弃所以actions键缺失代表没有动作而非未知。--jsonl模式下 stdout 是纯 JSONL人类可读的会话横幅只在--jsonl关闭时打印且无论如何都走 stderr。因此下面的做法是安全的adk run --jsonl {agent_dir} {query} 2/dev/null /tmp/events.jsonl head -1 /tmp/events.jsonl | python3 -m json.tool # 查看真实结构写解析器之前必须先读一个真实事件——schema 会变化。然后基于实际看到的结构过滤例如提取所有工具调用import json for line in open(/tmp/events.jsonl): event json.loads(line) for part in (event.get(content) or {}).get(parts, []): if functionCall in part: print(event[author], part[functionCall][name], part[functionCall].get(args))退出码0完成、1出错、2暂停退出码含义0本轮完成1出错——--stateJSON 非法、同时给了查询与--replay、既无查询也无 stdin、超时或运行期间抛出异常2暂停——运行产生了带longRunningToolIds的事件即有人类在回路工具在等待答复退出码 2 时会打印 session id。恢复方式是用该--session_id重新运行并把答案作为查询传入——ADK 会自动把查询映射到挂起的adk_request_confirmation/adk_request_input函数响应上不要手工构造FunctionResponse。对于确认类工具直接传yes/no即可需要自定义载荷时传 JSON 对象。从 Python 驱动 Runner适合对事件做断言当需要对事件做程序化断言而不是肉眼查看时直接驱动 Runner。两个容易踩的坑new_message必须是types.Content不能是字符串Runner只接受关键字参数且auto_create_session默认为False所以运行前会话必须已存在。import asyncio from google.adk import Agent from google.adk.runners import InMemoryRunner from google.genai import types agent Agent(nametest, modelgemini-2.5-flash, instruction...) runner InMemoryRunner(agentagent, app_nametest) async def main(): session await runner.session_service.create_session( app_nametest, user_idu ) async for event in runner.run_async( user_idu, session_idsession.id, new_messagetypes.Content(roleuser, parts[types.Part(texthello)]), ): print(event.author, event.content) if event.actions.transfer_to_agent: print( - transfer to, event.actions.transfer_to_agent) if event.output is not None: print( - output:, event.output) asyncio.run(main())InMemorySessionService.create_session_sync仍然存在但会打印弃用警告应改用异步的create_session。Runner.run_async的实现位于 runners.py其中_exec_with_plugin约第 1383 行负责插件钩子与事件持久化InMemoryRunner定义于同文件约第 2211 行。想按 CLI 的方式打印事件而不重复实现格式化逻辑可以直接复用from google.adk.utils._debug_output import print_event print_event(event) # 仅文本部分 print_event(event, verboseTrue) # 加上工具调用、工具结果、代码、blobverbose是仅关键字参数。源码位于 _debug_output.py文本部分总是显示verboseTrue时额外展示工具调用参数截断 50 字符、工具结果截断 100 字符、代码执行与内联数据等非文本部分。adk web深度使用启动、会话 API 与测试消息启动前的端口检查与启动方式启动自己的服务前先检查是否已有实例——第二个实例会因端口 8000 被占用而启动失败而且用户可能已经运行着一个包含你要查会话的服务curl -s http://localhost:8000/health # 若返回 {status:ok} 说明已有服务在运行若无实例后台启动并在结束时关闭adk web {agents_dir} # http://127.0.0.1:8000 adk web -v --reload_agents {agents_dir}{agents_dir}可以是代理子目录的集合也可以是单个代理文件夹包含agent.py或root_agent.yaml的目录默认为当前目录。相关 FlagFlag默认值说明--port8000换用第二个端口可让两个服务并存--host127.0.0.1端点未做认证保持 loopback--reload_agents关闭文件变更时重新导入代理模块编辑代理时用这个--reload开启Uvicorn 自身的源码自动重载当中途重启造成困惑时传--no-reload-v/--log_levelINFO日志进终端而非文件注意adk api_server接受相同的 Flag但只服务生产安全路由——没有 UI也没有/dev/...调试与 trace 端点。调试场景请用adk web。通过 HTTP 检查会话curl -s http://localhost:8000/list-apps | python3 -m json.tool curl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions \ | python3 -m json.tool curl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions/{session_id} \ | python3 -m json.tool会话响应包含完整的事件列表。拉取原始 JSON 并针对你实际看到的结构写摘要器不要依赖记忆中的 schema。每个事件值得提取的字段author、branch、nodeInfo.path、content.partstext、functionCall、functionResponse、output以及actionstransferToAgent、escalate、endOfAgent。键名均为 camelCase。DELETE .../sessions/{session_id}端点存在但不要用它来清理自己——用户可能还想在 UI 中查看该会话。这一约定与主文档调试守则中离开时保留会话的原则一致。发送测试消息/run优先/run_sse仅在排查流式问题时用先创建会话再发一轮消息。/run以 JSON 形式返回整个事件列表比流式更容易断言SESSION$(curl -s -X POST http://localhost:8000/apps/{app_name}/users/test/sessions \ -H Content-Type: application/json -d {} \ | python3 -c import sys,json; print(json.load(sys.stdin)[id])) curl -s -X POST http://localhost:8000/run \ -H Content-Type: application/json \ -d {\app_name\:\{app_name}\,\user_id\:\test\,\session_id\:\$SESSION\, \new_message\:{\role\:\user\,\parts\:[{\text\:\{query}\}]}} \ | python3 -m json.tool只有当 bug 本身出在流式上——部分事件、分块顺序、或永不终止的流——才用/run_sse配合streaming:true与curl -N。/run_sse路由定义于 api_server.py约第 1894 行。两个细节对不存在的 session id 执行POST返回 404 而非自动创建所以务必先创建会话需要在多次运行间使用稳定 id 时可在创建请求体中传{session_id: ...}。上图是adk web的 dev-ui 界面左侧为代理工作流的执行路径视图如process_input、generate_headline、evaluate_headline、route_headline等节点右侧为事件流面板可逐条查看每轮对话的模型请求/响应与节点输出顶部还提供Info/State/Artifacts/Evals标签页与会话管理是点击式排查会话与事件的主战场。日志与 Trace拿到模型真实收到的请求Trace 端点仅adk web注册adk api_server运行的是生产安全的ApiServer没有/dev/...路由因此 trace 查询在那里会 404。使用adk web# 一个会话的全部 Span curl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/session/{session_id} \ | python3 -m json.tool # 针对单个事件 id 记录的 trace curl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/{event_id} \ | python3 -m json.tool会话响应是一个 Span 列表每个 Span 含name、span_id、trace_id、parent_span_id、start_time、end_time与attributes。Span 保存在运行中服务器的内存里重启即丢失。主要 Span 名称及含义Span 名覆盖范围call_llm一次模型调用包含before_model/after_model回调execute_tool (merged)由一次模型响应分发的一批工具调用generate_content {model}OTel GenAI 插桩激活时底层 GenAI SDK 的调用从call_llmSpan 读取模型实际收到的内容提取call_llmSpan 并解码gcp.vertex.agent.llm_request——它是一个 JSON字符串包含contents、configtools、response_schema、response_mime_type、system_instruction与model。这是模型为什么这么做的 ground truth拿它与你认为代理应发送的内容做对比。关键 Span 属性属性含义gcp.vertex.agent.llm_request完整请求JSON 字符串gcp.vertex.agent.llm_response完整响应JSON 字符串gcp.vertex.agent.tool_call_args/.tool_response工具参数与结果gcp.vertex.agent.event_id将 Span 与会话中的事件关联gcp.vertex.agent.invocation_id/.session_id关联同一轮的 Spangen_ai.request.model实际发送的模型名gen_ai.usage.input_tokens/.output_tokensToken 计数——在责怪提示词被忽略之前先检查这里gen_ai.response.finish_reasons小写原因列表如[max_tokens]表示回答被截断[safety]表示被过滤如果内容型属性返回{}说明内容捕获被关闭了——去检查下面的环境变量而不是怀疑代理有 bug。控制内容捕获与环境变量变量作用ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS是否把提示词与响应写入旧版gcp.vertex.agent.*Span 属性。默认true设false去除内容OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT针对 GenAI 语义约定 Span 与日志记录的 OTel 规范内容捕获ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN把遥测 schema 固定为1Agent Engine 默认关闭或2。在2下invocationSpan 变为invoke_workflow且call_llm消失——如果上面这些 Span 名找不到先检查它GOOGLE_CLOUD_PROJECTadk web --trace_to_cloud所必需缺失时服务器仅记录警告并什么也不导出--trace_to_cloud只导出 trace更新的--otel_to_cloud同时覆盖 Cloud Trace 与 Cloud Loggingadk deploy agent_engine已警告--trace_to_cloud将被其取代。相关实现位于 tracing.py、context.py 与 _schema_version.py。事件流一次用户消息如何变成事件调用链全景Runner.run_async() Runner._exec_with_plugin() # 插件钩子 事件持久化 agent.run_async() # BaseAgentbefore/after agent 回调 LlmAgent._run_async_impl() # 产出事件 BaseLlmFlow.run_async() SingleFlow | AutoFlow # AutoFlow 额外支持 agent 转移 call_llm # 请求构建 模型调用 handle_function_calls_async() # 工具分发LlmAgent._llm_flow仅在disallow_transfer_to_parent与disallow_transfer_to_peers都设置且代理没有子代理时才选择SingleFlow否则是AutoFlow。如果代理拒绝转移先检查这两个字段再怀疑提示词。工作流图执行走另一条路径LlmAgent._run_impl通过 workflow/ 将代理作为节点运行。回调顺序插件管理器先于代理插件管理器与代理各有一次机会插件在前时机插件管理器代理模型前run_before_model_callbackcanonical_before_model_callbacks模型后run_after_model_callbackcanonical_after_model_callbacks模型出错run_on_model_error_callbackcanonical_on_model_error_callbacks工具前run_before_tool_callbackcanonical_before_tool_callbacks工具后run_after_tool_callbackcanonical_after_tool_callbacks工具出错run_on_tool_error_callbackcanonical_on_tool_error_callbacks管理器还暴露了代理没有对应的运行级钩子run_on_user_message_callback、run_before_run_callback、run_after_run_callback、run_on_event_callback以及 agent/run 错误钩子。源码见 plugin_manager.py。返回值的插件回调会短路该步骤——所以代理忽略了自己的回调时往往是一个插件已经抢先回答。值得读的事件字段Event通过 camelCase 别名生成器序列化因此 HTTP API 或adk run --jsonl输出的 JSON 使用invocationId、functionCall、nodeInfo、longRunningToolIds而 Python 属性访问仍是 snake_case。字段重要性authoruser或代理名——最快看出是哪个代理在说话branchagent_1.agent_2路径决定代理能看到哪段历史nodeInfo.path工作流内的节点路径如wf/A1/B1content.partstext、functionCall、functionResponse——没有text部分的轮次不是 bug而是工具往返output通用节点输出值普通聊天事件上缺失longRunningToolIds存在即表示运行停在一个人类在回路工具上actions.transferToAgent代理把控制权移交给了命名代理actions.escalate代理向父级上交通常结束循环actions.endOfAgent代理完成actions.stateDelta/artifactDelta本事件写入的状态与工件变更isolationScope也会出现在任务代理事件上它是内部字段可读作参考但不要依赖。源码见 event.py 与 event_actions.py。源码地图故障定位的索引区域文件Runner 与事件持久化runners.pyFlow 驱动、LLM 调用、回调base_llm_flow.py请求组装模型、工具、schemabasic.py哪些历史到达模型contents.py工具分发与工具错误functions.pyAgent 转移agent_transfer.pyAgent 配置与校验llm_agent.py调用状态与调用上限invocation_context.py任务代理task/图编排workflow/事件模型event.py会话服务sessions/插件钩子排序plugin_manager.pyHTTP API生产安全路由api_server.py仅开发路由含 tracedev_server.pyAgent 发现agent_loader.py日志设置logs.pyTrace 与 Span 属性tracing.pyCLI 使用的事件打印器_debug_output.py高频故障模式先匹配症状再读源码以下是 ADK 特有的故障形态每条都给出机制与可执行的检查方法。代理输出原始 JSON 而不是调用工具output_schema将模型置入受控生成它会在请求上设置config.response_schema与config.response_mime_type application/json而 JSON 模式下的模型返回的是 JSON 而不是工具调用。检查call_llmSpan 的gcp.vertex.agent.llm_request中是否有response_mime_type。ADK 只在代理没有工具、或模型能同时处理 schema 与工具时应用 schema——所以反向症状schema 似乎被忽略说明你落在了另一个分支上。源码见 basic.py 与 output_schema_utils.py。构造 Agent 时的ValueError以下三条消息来自同一个校验器含义都是你把这些设在了generate_content_config上而不是 agent 上All tools must be set via LlmAgent.tools.System instruction must be set via LlmAgent.instruction.Response schema must be set via LlmAgent.output_schema.源码见 llm_agent.py 中的LlmAgent.validate_generate_content_config。LlmCallsLimitExceededError: Max number of llm calls limit of N exceededrun_config.max_llm_calls被触达。在调高上限之前先把它当作循环检测器——转储事件看同一个工具是否一轮接一轮地用相同参数被调用。源码见 invocation_context.py。工具失败了但代理继续运行工具失败会被转换成携带错误的函数响应模型看到的是结果于是继续。查找 payload 带error键的functionResponse。FunctionTool对两种非异常情况也产生同样形状缺少必填参数以及需要确认但未被确认或被拒绝的确认类工具。要干预可在插件上注册on_tool_error_callback或在 agent 上注册on_tool_error_callbacks。源码见 functions.py 与 function_tool.py。adk web没有列出代理或返回 404curl -s http://localhost:8000/list-apps | python3 -m json.tool加载器在{agents_dir}下接受四种布局且先检查顶层app再检查root_agent{name}/agent.py # 定义 root_agent或 app {name}.py # 定义 root_agent或 app {name}/__init__.py # 在包中定义 root_agent或 app {name}/root_agent.yaml # 配置定义的代理__init__.py不需要from . import agent——加载器会自行导入agent子模块。把adk web指向一个自身就包含agent.py或root_agent.yaml的目录会运行该单个代理而不是把目录当集合处理。这一判定逻辑对应 agent_loader.py 中的is_single_agent_directory检查目录下是否存在agent.py或root_agent.yaml四种布局分别对应_load_from_module_or_package、_load_from_submodule与_load_from_yaml_config加载顺序为模块/包 →{name}.agent子模块 →root_agent.yaml配置。子代理看不到父对话事件携带branch如agent_1.agent_2.agent_3内容构建器会丢弃不属于当前代理 branch 的事件——这种隔离是刻意的兄弟代理互不读取对方的历史。委派的任务代理还通过isolation_scope进一步隔离。没有开关可以关掉它。把子代理需要的一切放进委派输入子代理的description才是驱动父代理决定是否包含它的关键。源码见 contents.py 中的_is_event_belongs_to_branch。一个工具运行时整个代理卡住同步工具函数在事件循环上被内联等待所以函数内部任何阻塞操作——requests调用、time.sleep、大文件读取——会冻结整个运行而不仅是该工具。把工具改成async即可。仅在 live 模式下还可以把工具交给线程池from google.adk.agents.run_config import RunConfig, ToolThreadPoolConfig run_config RunConfig(tool_thread_pool_configToolThreadPoolConfig()) # 4 个 worker源码见 function_tool.py 的FunctionTool._invoke_callable与 functions.py 的_call_tool_in_thread_pool。运行提前结束但看起来一切正常adk run退出码为 2 时说明有事件携带longRunningToolIds一个人工在回路工具正在等待答复。恢复方式见上文退出码一节。回答被截断、为空或被屏蔽读call_llmSpan 上的gen_ai.response.finish_reasons而不是从文本反推——max_tokens意味着应调高max_output_tokenssafety与recitation意味着模型拒绝了请求。调试守则收尾时的三条纪律离开时保留会话。用户可能仍要在 web UI 中打开它们而adk web没有撤销删除功能。删除为复现而创建的临时代理除非用户要求保留。按 bug 类型选择验证手段当问题在一个组件内部时优先用 tests/unittests/ 下的单元测试复现当问题只有把 runner、agent、workflow 组装在一起才出现时参考 contributing/samples/ 下的示例配合adk-sample-creator技能。总结整套调试体系可以概括为一句话方法论先用adk run --jsonl无头复现并确认症状形态再用日志文件补足细节需要交互式点击或查看模型原始请求时切到adk web通过会话 HTTP API、/run端点与call_llmTrace 属性拿到 ground truth最后对照故障模式清单与源码地图定位根因。事件流字段author/branch/actions/longRunningToolIds与 Span 属性gcp.vertex.agent.llm_request、gen_ai.response.finish_reasons是贯穿两条入口的统一语言掌握它们即可在症状 → 机制 → 源码之间快速收敛而无需在代理定义里盲目猜测。【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询