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 ADK(Agent Development Kit)Python 开源库中面向调试场景的完整工具链:无头命令行工具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 的开发服务器,提供浏览器 UI(http://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}.log(Linux 上即/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} 2>&1 | 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 DEBUG | INFO | 提高日志级别;输出进入日志文件而非终端 |
--default_llm_model {model} | 无 | 为未显式设置模型的代理覆盖模型,用于验证"是不是模型的锅" |
完整列表以adk run --help为准。
JSONL 事件形状:先看真实结构再写解析器
每一行是Event.model_dump(mode='json', by_alias=True, exclude_none=True)的结果,因此键名是 camelCase(invocationId、functionCall、longRunningToolIds),并注入session_id与node_path,author排在首位。空的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(name="test", model="gemini-2.5-flash", instruction="...") runner = InMemoryRunner(agent=agent, app_name="test") async def main(): session = await runner.session_service.create_session( app_name="test", user_id="u" ) async for event in runner.run_async( user_id="u", session_id=session.id, new_message=types.Content(role="user", parts=[types.Part(text="hello")]), ): 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, verbose=True) # 加上工具调用、工具结果、代码、blobverbose是仅关键字参数。源码位于 _debug_output.py:文本部分总是显示;verbose=True时额外展示工具调用(参数截断 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的目录);默认为当前目录。相关 Flag:
| Flag | 默认值 | 说明 |
|---|---|---|
--port | 8000 | 换用第二个端口可让两个服务并存 |
--host | 127.0.0.1 | 端点未做认证,保持 loopback |
--reload_agents | 关闭 | 文件变更时重新导入代理模块;编辑代理时用这个 |
--reload | 开启 | Uvicorn 自身的源码自动重载;当中途重启造成困惑时传--no-reload |
-v/--log_level | INFO | 日志进终端而非文件 |
注意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.parts(text、functionCall、functionResponse)、output,以及actions(transferToAgent、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、config(tools、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 | 关联同一轮的 Span |
gen_ai.request.model | 实际发送的模型名 |
gen_ai.usage.input_tokens/.output_tokens | Token 计数——在责怪提示词被忽略之前先检查这里 |
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 固定为1(Agent Engine 默认关闭)或2。在2下invocationSpan 变为invoke_workflow且call_llm消失——如果上面这些 Span 名找不到,先检查它 |
GOOGLE_CLOUD_PROJECT | adk web --trace_to_cloud所必需;缺失时服务器仅记录警告并什么也不导出 |
--trace_to_cloud只导出 trace;更新的--otel_to_cloud同时覆盖 Cloud Trace 与 Cloud Logging,adk deploy agent_engine已警告--trace_to_cloud将被其取代。相关实现位于 tracing.py、context.py 与 _schema_version.py。
事件流:一次用户消息如何变成事件
调用链全景
Runner.run_async() Runner._exec_with_plugin() # 插件钩子 + 事件持久化 agent.run_async() # BaseAgent:before/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_callback | canonical_before_model_callbacks |
| 模型后 | run_after_model_callback | canonical_after_model_callbacks |
| 模型出错 | run_on_model_error_callback | canonical_on_model_error_callbacks |
| 工具前 | run_before_tool_callback | canonical_before_tool_callbacks |
| 工具后 | run_after_tool_callback | canonical_after_tool_callbacks |
| 工具出错 | run_on_tool_error_callback | canonical_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。
| 字段 | 重要性 |
|---|---|
author | user或代理名——最快看出是哪个代理在说话 |
branch | agent_1.agent_2路径;决定代理能看到哪段历史 |
nodeInfo.path | 工作流内的节点路径,如wf/A@1/B@1 |
content.parts | text、functionCall、functionResponse——没有text部分的轮次不是 bug,而是工具往返 |
output | 通用节点输出值;普通聊天事件上缺失 |
longRunningToolIds | 存在即表示运行停在一个人类在回路工具上 |
actions.transferToAgent | 代理把控制权移交给了命名代理 |
actions.escalate | 代理向父级上交,通常结束循环 |
actions.endOfAgent | 代理完成 |
actions.stateDelta/artifactDelta | 本事件写入的状态与工件变更 |
isolationScope也会出现在任务代理事件上;它是内部字段,可读作参考但不要依赖。源码见 event.py 与 event_actions.py。
源码地图:故障定位的索引
| 区域 | 文件 |
|---|---|
| Runner 与事件持久化 | runners.py |
| Flow 驱动、LLM 调用、回调 | base_llm_flow.py |
| 请求组装(模型、工具、schema) | basic.py |
| 哪些历史到达模型 | contents.py |
| 工具分发与工具错误 | functions.py |
| Agent 转移 | agent_transfer.py |
| Agent 配置与校验 | llm_agent.py |
| 调用状态与调用上限 | invocation_context.py |
| 任务代理 | task/ |
| 图编排 | workflow/ |
| 事件模型 | event.py |
| 会话服务 | sessions/ |
| 插件钩子排序 | plugin_manager.py |
| HTTP API(生产安全路由) | api_server.py |
| 仅开发路由(含 trace) | dev_server.py |
| Agent 发现 | agent_loader.py |
| 日志设置 | logs.py |
| Trace 与 Span 属性 | tracing.py |
| CLI 使用的事件打印器 | _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 exceeded
run_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没有列出代理或返回 404
curl -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_config=ToolThreadPoolConfig()) # 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_tokens,safety与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),仅供参考