<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>elonnzhang</title><link>https://jiabin.dev/</link><description>Recent content on elonnzhang</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Sat, 10 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://jiabin.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>VSCode Remote SSH 问题排查</title><link>https://jiabin.dev/blog/fix-vscode-ssh/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/fix-vscode-ssh/</guid><description>&lt;p&gt;一台开发机可以正常 SSH 登录，VS Code 的 Remote-SSH 却始终无法建立开发会话。问题不一定在网络或账号：SSH 登录成功之后，VS Code 还要在远端安装并启动 &lt;strong&gt;VS Code Server&lt;/strong&gt;。这一步有自己的系统依赖。&lt;/p&gt;&#10;&lt;p&gt;本文记录一次实际排查：宿主机运行 CentOS 7，内核为 &lt;strong&gt;4.14&lt;/strong&gt;，glibc 为 &lt;strong&gt;2.21&lt;/strong&gt;。我们最终让本机 VS Code 通过 SSH 连接远端 Docker daemon，再直接进入开发容器。文末也说明为什么给宿主机补新版 glibc sysroot 只能算临时方案，以及容器对旧内核的限制。&lt;/p&gt;&#10;&lt;h2 id="ssh-连通为什么-remote-ssh-仍然失败"&gt;SSH 连通，为什么 Remote-SSH 仍然失败？&lt;/h2&gt;&#10;&lt;p&gt;普通 SSH 只需在远端启动 shell。Remote-SSH 的完整路径则是：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;本机 VS Code → SSH 登录宿主机 → 安装或启动 VS Code Server → 打开远端工作区&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;VS Code 当前的 Linux 远程开发文档列出的通用要求包括：&lt;strong&gt;内核 ≥ 4.18、glibc ≥ 2.28、libstdc++ ≥ 3.4.25&lt;/strong&gt;。这台机器的内核与 glibc 均低于门槛。所以 &lt;code&gt;ssh&lt;/code&gt; 能打开 shell，并不能证明 VS Code Server 能在宿主机运行。遇到类似问题，先在宿主机检查 &lt;code&gt;uname -r&lt;/code&gt; 和 &lt;code&gt;getconf GNU_LIBC_VERSION&lt;/code&gt;，再查看 VS Code 的 &lt;strong&gt;Remote - SSH&lt;/strong&gt; 输出，确认失败发生在登录、下载、安装还是启动阶段。仅凭版本号可以判断存在兼容风险，但不要把所有连接报错都归因于 glibc。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 08 - Tool</title><link>https://jiabin.dev/blog/adk-tool/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-tool/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--tool从声明到执行再回到事件"&gt;ADK-Go 源码阅读笔记 · Tool：从声明到执行，再回到事件&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。下文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-plugin/"&gt;Plugin&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-workflow/"&gt;Workflow&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户问：“东京现在天气怎样？”模型返回 &lt;code&gt;get_weather(city=&amp;quot;Tokyo&amp;quot;)&lt;/code&gt;。Tool 的工作不是把这串 JSON 直接交给某个函数，而是把&lt;strong&gt;模型可见的声明&lt;/strong&gt;、&lt;strong&gt;本地可执行的实现&lt;/strong&gt;、&lt;strong&gt;工具上下文里的动作&lt;/strong&gt;和&lt;strong&gt;要写回 Session 的事件&lt;/strong&gt;接成一条协议。本文用这一次调用贯穿正常、失败、流式、长任务与确认恢复路径。&lt;/p&gt;&#10;&lt;p&gt;&lt;span class="ann ann-n ann-amber" data-note="声明不等于执行"&gt;FunctionDeclaration 只是模型可见的能力描述&lt;/span&gt;；本地执行还要在当前请求的 &lt;code&gt;req.Tools&lt;/code&gt; 中找到实现。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;词&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;在本文中的准确含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不要混淆&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tool&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Name&lt;/code&gt;、&lt;code&gt;Description&lt;/code&gt;、&lt;code&gt;IsLongRunning&lt;/code&gt; 三项公共元数据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不是必然带 &lt;code&gt;Run&lt;/code&gt; 的公开接口&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FunctionCall ID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型这一次调用的 &lt;code&gt;FunctionCall.ID&lt;/code&gt;，如 &lt;code&gt;call-weather-1&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;工具确认事件自己的 ID 可以不同&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Toolset&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;根据上下文返回工具集合，本条 Flow 会缓存收集结果&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不是静态全局注册表&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FunctionResponse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tool 结果在 &lt;code&gt;session.Event&lt;/code&gt; 中的模型协议表示&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不等于已对用户显示的文本&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阅读顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;源码&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;需要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/tool/tool.go#L37-L57"&gt;&lt;code&gt;tool/tool.go#L37-L57&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;公共 Tool/Toolset 契约是什么？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/toolinternal/tool.go#L28-L62"&gt;&lt;code&gt;internal/toolinternal/tool.go#L28-L62&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;哪些能力只在 ADK 内部识别？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/tool/toolutils/toolutils.go#L29-L73"&gt;&lt;code&gt;tool/toolutils/toolutils.go#L29-L73&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;声明与本地实现如何写入请求？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/tool/functiontool/function.go#L77-L181"&gt;&lt;code&gt;tool/functiontool/function.go#L77-L181&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go 泛型函数怎样获得 schema 和声明？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/base_flow.go#L1160-L1360"&gt;&lt;code&gt;internal/llminternal/base_flow.go#L1160-L1360&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FunctionCall 怎样并发执行并生成事件？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/request_confirmation_processor.go#L37-L229"&gt;&lt;code&gt;internal/llminternal/request_confirmation_processor.go#L37-L229&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;确认回复怎样从历史恢复原调用？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tool / Toolset&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Tools(ReadonlyContext)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Flow.f.Tools&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ProcessRequest&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├───────────────┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;req.Tools req.Config.Tools&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;本地执行表 模型声明&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────┬────────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; FunctionCall&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ID + Name + Args&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ToolContext&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ Run / RunStream&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ Actions / StateDelta&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ FunctionResponse Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="二接口与数据公开能力内部能力和对象生命周期"&gt;二、接口与数据：公开能力、内部能力和对象生命周期&lt;/h2&gt;&#10;&lt;h3 id="1-公共接口故意很小"&gt;1. 公共接口故意很小&lt;/h3&gt;&#10;&lt;p&gt;源码中的 &lt;code&gt;Tool&lt;/code&gt; 只有三个方法；&lt;code&gt;Toolset.Tools&lt;/code&gt; 接收只读上下文并返回当前工具列表（&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/tool/tool.go#L37-L57"&gt;&lt;code&gt;tool/tool.go#L37-L57&lt;/code&gt;&lt;/a&gt;）。它没有 &lt;code&gt;Run&lt;/code&gt;、&lt;code&gt;Declaration&lt;/code&gt; 或 &lt;code&gt;ProcessRequest&lt;/code&gt;。原因是 Tool 也可以代表 Gemini 原生能力或仅修改请求的工具，并不一定有本地 handler。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 09 - Context</title><link>https://jiabin.dev/blog/adk-context/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-context/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--context上下文能力状态变化与恢复边界"&gt;ADK-Go 源码阅读笔记 · Context：上下文能力、状态变化与恢复边界&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。本文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;范围：公开 Context 接口、commonContext、callback/tool wrapper、状态与 artifact 追踪、派生和恢复信息；workflow 调度算法见配套文章。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-agent/"&gt;Agent&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-workflow/"&gt;Workflow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session Event&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;工具函数收到一个 &lt;code&gt;agent.Context&lt;/code&gt;，里面既能读用户输入，也能写状态、搜索 Memory、请求用户确认。它究竟是一份数据、一组服务，还是一个可以随时结束 Agent 的控制器？&lt;/p&gt;&#10;&lt;p&gt;答案需要分两层：&lt;strong&gt;Context 接口列出运行能力，具体构造器和 wrapper 决定当前调用点真正可用的能力。&lt;/strong&gt; 同样写着 &lt;code&gt;agent.Context&lt;/code&gt;，Agent 回调、工具、workflow 节点收到的对象并不完全相同。&lt;/p&gt;&#10;&lt;p&gt;本文用“杭州天气怎么样”贯穿：&lt;code&gt;weather_agent&lt;/code&gt; 调用 &lt;code&gt;get_weather&lt;/code&gt;，工具写入 &lt;code&gt;last_city=Hangzhou&lt;/code&gt;，然后返回天气。第四节再把工具改成需要确认的“购买天气报告”，观察正常读写与暂停恢复的区别。&lt;/p&gt;&#10;&lt;p&gt;本文回答三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Go context、InvocationContext、ReadonlyContext、Context 各自提供什么？&lt;/li&gt;&#10;&lt;li&gt;工具写入的状态怎样进入 Event，何时才涉及持久化？&lt;/li&gt;&#10;&lt;li&gt;Context 派生、取消、分支和确认能保证什么，又不能保证什么？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="阅读前先分清这些词"&gt;阅读前先分清这些词&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不应混成&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go Context&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deadline、Done、Err、Value 的请求链&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session 状态容器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;InvocationContext&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前 Agent、Session、服务和 invocation 环境&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;单次模型请求&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;EventActions.StateDelta&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本事件声明的状态变化&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Context 派生用的 Delta&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FunctionCallID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型要求的一次函数调用 ID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Event.ID 或 InterruptID&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;InterruptID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;workflow 等待外部响应的中断键&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前节点 RunID&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Branch / IsolationScope&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;历史选择条件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;用户权限或数据库隔离&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阅读顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件 / 符号&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;agent/context.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;三个公开接口之间到底怎样组合？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;internal/context/invocation_context.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Runner 创建的基础环境在哪里？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;agent/common_context.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;callbacks、tools、nodes 怎样共享实现？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;callback_context_wrapper.go&lt;/code&gt;、&lt;code&gt;tool_context_wrapper.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同一接口在不同调用点有哪些护栏？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;agent/common_context_delta.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;浅拷贝派生保留和替换哪些字段？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;internal/llminternal/request_confirmation_processor.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;确认对象怎样在下一次输入后重建？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;runner/run_node.go&lt;/code&gt;、&lt;code&gt;runner/runner.go&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;谁把 Context 里的 actions 接到 Event 和存储？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用的 context.Context&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Runner 注入父子树、Plugin、运行配置等 Value&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;internal/context.InvocationContext&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ Session、Agent、InvocationID、UserContent&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ Artifacts、Memory、RunConfig&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ Branch、IsolationScope、Ended&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent.commonContext&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ Go Context 链 + invocation 环境&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ actions / artifacts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ functionCallID / confirmation&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ resumeInputs / Path / RunID / SubScheduler&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 按场景构造&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────┼─────────────────┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;普通节点 callback wrapper tool wrapper&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;public API 位于 &lt;code&gt;agent&lt;/code&gt;；&lt;code&gt;internal/context&lt;/code&gt; 是 Runner 等框架代码使用的适配层，应用不能从模块外直接导入 internal。业务扩展通常由框架把 Context 传进 callback、工具或节点函数。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 13 - Session Vertex AI</title><link>https://jiabin.dev/blog/adk-session-vertexai/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-session-vertexai/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--vertex-ai-session远端后端怎样保持-session-契约"&gt;ADK-Go 源码阅读笔记 · Vertex AI Session：远端后端怎样保持 Session 契约&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。以下“当前实现”均指此版本。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session、Event 与 State&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-inmemory/"&gt;InMemory&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-database/"&gt;Database&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-server/"&gt;Server&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;&lt;code&gt;session.Service&lt;/code&gt; 是 Runner 依赖的抽象，但不同后端并不提供相同的性能和事务语义。InMemory 把对象放在进程内，Database 把对象编码进本地数据库；&lt;code&gt;session/vertexai&lt;/code&gt; 则把 Session、Event 和 State 映射到 Vertex AI Agent Engine 的 RPC。&lt;/p&gt;&#10;&lt;p&gt;这篇文章只追踪这个适配层，不把 Agent Engine 当成另一个 Runner。Runner 仍然负责 invocation 和事件消费，Vertex AI service 负责远端 Session 的创建、读取、列举、删除及追加。&lt;/p&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;入口&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;源码&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;构造&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/vertexai.go#L34-L56"&gt;&lt;code&gt;session/vertexai/vertexai.go#L34-L56&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Project、Location、ReasoningEngine 怎样变成 Service？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/vertexai.go#L58-L155"&gt;&lt;code&gt;vertexai.go#L58-L155&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;五个公开操作如何委托远端 client？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本地句柄&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/session.go#L29-L91"&gt;&lt;code&gt;session.go#L29-L91&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;远端 Session 返回后，Runner 持有的对象保存什么？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;State&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/session.go#L116-L154"&gt;&lt;code&gt;session.go#L116-L154&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;State 的读写是否需要再次 RPC？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RPC 映射&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/vertexai_client.go#L69-L110"&gt;&lt;code&gt;vertexai_client.go#L69-L110&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Create 如何映射 Agent Engine resource？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Get 并发&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/vertexai.go#L69-L114"&gt;&lt;code&gt;vertexai.go#L69-L114&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session 元数据和 Events 如何同时读取？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;runner.Run&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ session.Service&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vertexAiService&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ vertexAiClient.Get/Create/List/Delete&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ Vertex AI Agent Engine RPC&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ localSession（本地句柄与快照）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ State() → 本地 map + mutex&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ Events() → 本地切片快照&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="二接口与数据远端资源加本地句柄"&gt;二、接口与数据：远端资源加本地句柄&lt;/h2&gt;&#10;&lt;h3 id="1-配置中的-appname-不是-reasoning-engine-id"&gt;1. 配置中的 AppName 不是 Reasoning Engine ID&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;VertexAIServiceConfig&lt;/code&gt; 要求 ProjectID、Location 和 ReasoningEngine。创建 Session 时，client 会根据 &lt;code&gt;req.AppName&lt;/code&gt; 解析对应的 Agent Engine resource，再把 &lt;code&gt;req.UserID&lt;/code&gt;、初始 State 和可选 SessionID 转成 &lt;code&gt;CreateSessionRequest&lt;/code&gt;（&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/vertexai/vertexai_client.go#L69-L105"&gt;&lt;code&gt;vertexai_client.go#L69-L105&lt;/code&gt;&lt;/a&gt;）。因此一个 service 可以服务多个 app，但每个 app 必须能解析到自己的 reasoning engine。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 15 - Plugin</title><link>https://jiabin.dev/blog/adk-plugin/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-plugin/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--plugin理解每一次拦截会跳过什么"&gt;ADK-Go 源码阅读笔记 · Plugin：理解每一次拦截会跳过什么&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。以下行为均限定于此版本。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-workflow/"&gt;Workflow&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户问“东京现在天气怎样”，Agent 调 &lt;code&gt;get_weather(city=&amp;quot;Tokyo&amp;quot;)&lt;/code&gt;，模型再生成回答。应用希望同时记录审计日志、复用缓存并限制高风险能力。Plugin 把这些横切逻辑接到 Runner、Agent、Model、Tool 和 Event 的真实调用点上。&lt;/p&gt;&#10;&lt;p&gt;&lt;span class="ann ann-n ann-amber" data-note="先看条件，再看名字"&gt;回调返回值决定哪些后续步骤会执行&lt;/span&gt;。&lt;code&gt;AfterModel&lt;/code&gt; 不保证在 &lt;code&gt;BeforeModel&lt;/code&gt; 后出现；&lt;code&gt;OnEvent&lt;/code&gt; 不保证收到每个曾被写入 Session 的事件；&lt;code&gt;AfterRun&lt;/code&gt; 也有尚未注册 defer 就返回的分支。&lt;/p&gt;&#10;&lt;p&gt;本文让 &lt;code&gt;audit&lt;/code&gt;、&lt;code&gt;cache&lt;/code&gt;、&lt;code&gt;policy&lt;/code&gt; 三个插件按此顺序注册。正常路径先假设所有回调返回 &lt;code&gt;nil,nil&lt;/code&gt;，然后逐一改变缓存命中、工具错误和事件拒绝条件。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;准确含义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Plugin 实例&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Runner 共享的一组 callback 和可选 CloseFunc，可同时服务多个 invocation&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PluginManager&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;内部按注册顺序调度回调的对象&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;返回 nil&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;保留当前对象并继续同阶段；共享指针仍可能已被原地修改&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;返回非 nil&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;结束当前阶段的回调链，具体跳过哪些后续步骤由调用方决定&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Invocation / FunctionCall / Event ID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;分别标识逻辑运行、一次工具请求、一个历史事件，不能相互替代&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;层&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;入口&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阅读问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;配置&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/plugin/plugin.go#L28-L80"&gt;&lt;code&gt;plugin/plugin.go#L28-L80&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册的函数保存在哪里？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;调度&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/plugininternal/plugin_manager.go#L44-L133"&gt;&lt;code&gt;plugin_manager.go#L44-L133&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;顺序、去重、nil 与 error 怎样处理？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Run / Event&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/runner/runner.go#L703-L775"&gt;&lt;code&gt;runner.go#L703-L775&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;插件先于持久化还是后于持久化？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/agent/agent.go#L163-L211"&gt;&lt;code&gt;agent.go#L163-L211&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;BeforeAgent 返回内容后还会运行 AfterAgent 吗？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Model / Tool&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/base_flow.go#L815-L999"&gt;&lt;code&gt;base_flow.go#L815-L999&lt;/code&gt;&lt;/a&gt;、&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/base_flow.go#L1374-L1465"&gt;&lt;code&gt;#L1374-L1465&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;两种 before 返回替代结果后分别跳过什么？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;runner.PluginConfig.Plugins = [audit, cache, policy]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PluginManager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ context value（共享实例）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌──────────┼────────────┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Runner Agent Flow&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Run/Event Agent hooks Model/Tool hooks&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────────┴────────────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 每一个调用点自己决定后续控制流&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="二核心接口与数据结构"&gt;二、核心接口与数据结构&lt;/h2&gt;&#10;&lt;h3 id="plugin-与-agent-callback-的边界"&gt;Plugin 与 Agent Callback 的边界&lt;/h3&gt;&#10;&lt;p&gt;源码：&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/runner/runner.go#L113-L161"&gt;&lt;code&gt;runner/runner.go#L113-L161&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 16 - Workflow</title><link>https://jiabin.dev/blog/adk-workflow/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-workflow/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--workflow图怎样执行暂停后怎样继续"&gt;ADK-Go 源码阅读笔记 · Workflow：图怎样执行，暂停后怎样继续&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。下文的“当前实现”均指此版本。&lt;/p&gt;&#10;&lt;p&gt;本文完整比较三组相关代码：&lt;code&gt;workflow&lt;/code&gt; 定义 Node、Edge 与图调度，&lt;code&gt;agent/workflowagent&lt;/code&gt; 将图接入 Agent/Runner，&lt;code&gt;agent/workflowagents&lt;/code&gt; 下的 Sequential、Parallel、Loop 直接组合已有 Agent。先解释图的构建与执行，再比较两种组合方式的数据传递、并发、停止和恢复边界。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-agent/"&gt;Agent&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-plugin/"&gt;Plugin&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户要求“为东京生成天气简报”。最小实现可以是三个确定性节点：规范化城市名、查询天气、格式化简报。以后还可以同时查询出行提示，根据天气路由到不同模板，或者在发送简报前等待用户确认。&lt;/p&gt;&#10;&lt;figure class="mermaid-figure"&gt;&#10; &lt;button class="mermaid-fullscreen" type="button" aria-label="全屏查看图表 / view fullscreen" title="全屏查看"&gt;&#10; &lt;svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;&lt;path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3"/&gt;&lt;/svg&gt;&#10; &lt;/button&gt;&#10; &lt;pre class="mermaid" data-mermaid role="img" aria-label="Mermaid diagram"&gt;flowchart TD&#10; city[&amp;#34;city：规范化城市名&amp;lt;br/&amp;gt;东京 → Tokyo&amp;#34;] --&amp;gt;|&amp;#34;城市名 Tokyo&amp;#34;| weather[&amp;#34;weather：查询天气&amp;lt;br/&amp;gt;晴，22°C&amp;#34;]&#10; weather --&amp;gt;|&amp;#34;天气数据 Weather&amp;#34;| format[&amp;#34;format：生成简报&amp;lt;br/&amp;gt;Tokyo：晴，22°C&amp;#34;]&lt;/pre&gt;&#10;&lt;/figure&gt;&#10;&lt;p&gt;图中的天气是示例程序返回的固定数据。&lt;code&gt;city&lt;/code&gt; 输出城市名，&lt;code&gt;weather&lt;/code&gt; 输出天气结构，&lt;code&gt;format&lt;/code&gt; 把结构化数据变成简报文本；节点之间的箭头对应后文的 &lt;code&gt;Edge&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 17 - Artifact</title><link>https://jiabin.dev/blog/adk-artifact/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-artifact/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--artifact文件版本作用域与事件摘要"&gt;ADK-Go 源码阅读笔记 · Artifact：文件版本、作用域与事件摘要&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit 4e57df40cbd35f055ba654b03ffb0e509fdd754a。本文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;本文范围：公开 artifact.Service 契约、请求校验、InMemoryService、GCS 实现，以及 Runner 如何把它们接到 agent.Context。Session/Event 中的 ArtifactDelta 只作为“版本摘要”解释，不展开整个 Session Service 的实现。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session &amp;amp; Event&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户上传一张天气截图，工具把它保存成 report.txt，随后又生成一版带结论的文件。应用需要回答几个看似简单的问题：第二次保存是不是覆盖第一次，下一次会话能不能看到同一个文件，Event 里的 ArtifactDelta 是否就是文件内容，以及 GCS 返回的 URI 能不能直接拿来下载？&lt;/p&gt;&#10;&lt;p&gt;先给结论：&lt;strong&gt;Artifact Service 保存的是带版本号的 genai.Part，版本键由 app/user/session/filename 组成；agent.Artifacts 只是把当前 Session 的身份填进请求；ArtifactDelta 只记录“某文件成功保存到了哪个版本”，并不代替保存，也不包含 payload。&lt;/strong&gt; InMemory 和 GCS 遵守同一个六方法接口，但版本竞争、返回的 Part 形态和元数据能力不同。&lt;/p&gt;&#10;&lt;p&gt;本文用同一组身份贯穿：AppName=weather、UserID=u-1、SessionID=s-1。先保存两次 report.txt，再读最新版本、指定版本和元数据；第四节把文件改名为 user:profile.json，并把后端换成 GCS，观察作用域和并发分支。&lt;/p&gt;&#10;&lt;h3 id="阅读前先分清这些词"&gt;阅读前先分清这些词&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;本文中的准确含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不要混成什么&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Artifact&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一个 genai.Part 指针及其文件名、版本和存储身份&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session Event 本身；Event 只可携带版本摘要&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;artifact.Service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;接受 AppName/UserID/SessionID/FileName 请求的六操作存储契约&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;agent.Artifacts 这个给 Context 使用的薄适配器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;版本 0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;在 Load/GetArtifactVersion 中表示“取最新”，在 Delete 中表示“删全部”&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一个实际存储版本；实际版本从 1 开始&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;user: 文件名&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;触发跨 Session 的用户作用域&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;文件系统路径；路径分隔符仍会被拒绝&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ArtifactDelta&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;filename -&amp;gt; version 的事件动作字段&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;保存操作、payload 或事务日志&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CanonicalURI&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;后端原生方案的对象身份，例如 gs://&amp;hellip;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;已认证的 HTTP 下载地址&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;p&gt;Artifact 的入口不在 Runner，而在顶层 artifact 包。Runner 只负责把当前 Session 的身份装配进内部适配器；Context 和工具看到的是 agent.Artifacts，不是 artifact.Service 的请求结构。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 18 - Server</title><link>https://jiabin.dev/blog/adk-server/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-server/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--server同一个-agent-怎样被暴露成四种协议"&gt;ADK-Go 源码阅读笔记 · Server：同一个 Agent 怎样被暴露成四种协议&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。下文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-artifact/"&gt;Artifact&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-launcher/"&gt;Launcher&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-infrastructure/"&gt;基础设施&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户向服务发送“东京现在天气怎样？”。在 ADK-Go 中，这句话可以从 REST &lt;code&gt;POST /run&lt;/code&gt; 进入，也可以从 &lt;code&gt;/run_sse&lt;/code&gt; 按事件流返回；同一个 Agent 还可以由 A2A JSON-RPC 或 Agent Engine 的执行方法调用。对应的执行入口最终都把输入交给 Runner，但它们对事件的包装、结束状态、会话 ID 和错误传递方式并不相同；Agent Engine 另外提供的会话 CRUD 方法则不会进入 Runner。&lt;/p&gt;&#10;&lt;p&gt;本文把 &lt;code&gt;server&lt;/code&gt; 看成&lt;strong&gt;协议边界&lt;/strong&gt;，而不是另一个 Agent 执行引擎：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server/adkrest&lt;/code&gt; 建立 REST 路由、认证 middleware、授权检查，并为每次请求创建 Runner；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;server/adka2a/v2&lt;/code&gt; 实现 A2A &lt;code&gt;AgentExecutor&lt;/code&gt;，把 ADK &lt;code&gt;session.Event&lt;/code&gt; 转成 Task、artifact 和 status event；根路径的 &lt;code&gt;server/adka2a&lt;/code&gt; 是兼容旧版 a2a-go 的 façade，已委托给 v2，不是另一套执行引擎；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;server/agentengine&lt;/code&gt; 把 Agent Engine 的 &lt;code&gt;class_method&lt;/code&gt; 分派到会话方法或 JSONL 流式方法；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cmd/launcher/web&lt;/code&gt; 只负责把这些 handler 挂到总 router、加前缀、CORS 和 HTTP server 生命周期。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;span class="ann ann-n ann-amber" data-note="协议适配不等于再次执行 Agent"&gt;REST、A2A 和 Agent Engine 都把实际推理交给 Runner&lt;/span&gt;；Server 负责身份、传输、编码和状态协议，不复制 Flow 的模型/工具循环。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 19 - Agent Registry</title><link>https://jiabin.dev/blog/adk-agentregistry/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-agentregistry/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--agent-registry从目录记录到-remoteagent-与-mcptoolset"&gt;ADK-Go 源码阅读笔记 · Agent Registry：从目录记录到 RemoteAgent 与 MCPToolset&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。下文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;本文范围是根模块 &lt;code&gt;agentregistry&lt;/code&gt;：客户端创建、Agent/MCP server/模型 endpoint 的只读发现、分页迭代器，以及两个工厂 &lt;code&gt;RemoteAgent&lt;/code&gt; 和 &lt;code&gt;MCPToolset&lt;/code&gt;。注册资源的写入 API、远端 Agent 的业务推理和 MCP server 的工具实现不在这个包里。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-agent/"&gt;Agent&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-server/"&gt;Server&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-infrastructure/"&gt;基础设施&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户要“生成东京天气简报”。应用不把某个 MCP endpoint 写死，而是在 &lt;code&gt;projects/demo/locations/global&lt;/code&gt; 中寻找声明了 &lt;code&gt;get_weather&lt;/code&gt; 的 MCP server；找到后用 &lt;code&gt;MCPToolset&lt;/code&gt; 接入一个本地 &lt;code&gt;llmagent&lt;/code&gt;。另一条路径把已注册的天气专家解析成 &lt;code&gt;RemoteAgent&lt;/code&gt;，让本地 Agent 通过 A2A 委托整段任务。&lt;/p&gt;&#10;&lt;p&gt;这两个路径共享同一个目录访问层，却在各自读完记录后分开出站：注册表请求可以用 ADC，MCP 的 Google API endpoint 可以复用这份客户端，A2A 远端则&lt;strong&gt;不会&lt;/strong&gt;自动拿到注册表凭据。&lt;span class="ann ann-n ann-amber" data-note="目录不是代理"&gt;Agent Registry 返回元数据和 endpoint；后续 A2A/MCP 流量直接发往远端服务&lt;/span&gt;。&lt;/p&gt;&#10;&lt;p&gt;本文回答三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;New&lt;/code&gt; 如何选择 ADC、mTLS endpoint、配额项目和自定义 HTTP client？&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;List/Get/All&lt;/code&gt; 如何拼资源名、保留过滤器并按需分页？&lt;/li&gt;&#10;&lt;li&gt;一条 Agent 记录怎样变成 &lt;code&gt;agent.Agent&lt;/code&gt;，一条 MCP 记录怎样变成 &lt;code&gt;tool.Toolset&lt;/code&gt;，两者的出站认证边界在哪里？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="阅读前先分清这些词"&gt;阅读前先分清这些词&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;本文中的准确含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不要混淆&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Registry &lt;code&gt;Agent&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册表返回的目录记录，包含协议、技能和可选嵌入卡片&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;可运行的 &lt;code&gt;agent.Agent&lt;/code&gt;；它只是元数据&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent Card&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A2A 客户端协商所需的 &lt;code&gt;a2a.AgentCard&lt;/code&gt;，可从 &lt;code&gt;Card.Content&lt;/code&gt; 解码或由协议字段合成&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册表记录本身&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;List*&lt;/code&gt; / &lt;code&gt;All*&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;List*&lt;/code&gt; 返回一页；&lt;code&gt;All*&lt;/code&gt; 返回 Go &lt;code&gt;iter.Seq2&lt;/code&gt;，消费时才请求下一页&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次请求就把所有资源读入内存&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Registry HTTP client&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;调用 &lt;code&gt;agentregistry.googleapis.com&lt;/code&gt; 的 REST client，可能带 ADC&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;发往 A2A/MCP endpoint 的出站 client&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MCP &lt;code&gt;Tool&lt;/code&gt; 元数据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MCPServer.Tools&lt;/code&gt; 中目录声明的工具名称和提示&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MCP 会话连上后由 server 实际返回的 live tool&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;agent.Loader&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本地名字到已构造 &lt;code&gt;agent.Agent&lt;/code&gt; 的装载器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册表的远程目录查询&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;贯穿示例使用三个资源名：&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 20 - Auth、Telemetry 与 Platform</title><link>https://jiabin.dev/blog/adk-infrastructure/</link><pubDate>Wed, 23 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-infrastructure/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--authtelemetry-与-platform横切能力怎样进入一次运行"&gt;ADK-Go 源码阅读笔记 · Auth、Telemetry 与 Platform：横切能力怎样进入一次运行&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。本文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-plugin/"&gt;Plugin&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-server/"&gt;Server&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;ADK 的主链文章通常从 Runner 开始：取得 Session，运行 Agent，保存 Event。但真实应用还要解决三类横切问题：工具访问外部服务时用谁的凭据，运行过程怎样被观测，以及事件里的时间戳和 ID 怎样在测试中变得可控。&lt;/p&gt;&#10;&lt;p&gt;这三件事分别落在 &lt;code&gt;auth&lt;/code&gt;、&lt;code&gt;telemetry&lt;/code&gt; 和 &lt;code&gt;platform&lt;/code&gt; 包。它们都不负责调用模型，也不拥有 Session；它们通过接口和 &lt;code&gt;context.Context&lt;/code&gt; 进入调用树，把能力交给真正的请求、事件或服务。&lt;/p&gt;&#10;&lt;p&gt;&lt;span class="ann ann-n ann-amber" data-note="横切能力不是隐式全局状态"&gt;凭据和测试 provider 都沿着 context 或对象边界传递&lt;/span&gt;。因此“配置了 ADC”不等于每个 HTTP 请求都会自动带 token，“初始化了 Telemetry”也不等于已经注册成全局 provider。&lt;/p&gt;&#10;&lt;h2 id="阅读前先分清三个层次"&gt;阅读前先分清三个层次&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;层次&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;负责什么&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不负责什么&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;auth.CredentialProvider&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;在一次请求上下文中解析可用凭据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不发送 HTTP 请求&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;auth.Credential&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把已解析凭据写入 HTTP headers&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不决定何时刷新或重试&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;auth.Transport&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每次 &lt;code&gt;RoundTrip&lt;/code&gt; 解析并应用凭据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不替调用方管理用户授权&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;telemetry.Providers&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;持有 OTel tracer/logger provider，注册和关闭&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不自动创建业务 span&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;platform&lt;/code&gt; provider&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;为当前 context 提供时间、UUID 和任务调度策略&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不改变包级默认行为&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阅读顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;源码&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/auth/credential.go#L25-L120"&gt;&lt;code&gt;auth/credential.go#L25-L120&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一个凭据怎样写到请求头？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/auth/providers.go#L29-L178"&gt;&lt;code&gt;auth/providers.go#L29-L178&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Static token、ADC、Service Account 的解析边界是什么？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/auth/transport.go#L22-L75"&gt;&lt;code&gt;auth/transport.go#L22-L75&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;provider 何时被调用，Request 是否会被原地修改？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/telemetry/telemetry.go#L28-L119"&gt;&lt;code&gt;telemetry/telemetry.go#L28-L119&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OTel provider 怎样创建、注册和关闭？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/telemetry/config.go#L55-L136"&gt;&lt;code&gt;telemetry/config.go#L55-L136&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cloud 导出和自定义 processor 怎样组合？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/platform/time.go#L22-L50"&gt;&lt;code&gt;platform/time.go#L22-L50&lt;/code&gt;&lt;/a&gt;、&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/platform/uuid.go#L23-L52"&gt;&lt;code&gt;uuid.go#L23-L52&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;时间和 ID 的 provider 如何只影响当前调用树？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/platform/exec.go#L22-L94"&gt;&lt;code&gt;platform/exec.go#L22-L94&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型工具并发怎样替换为宿主自己的 TaskRunner？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/auth/gcp/client.go#L203-L370"&gt;&lt;code&gt;auth/gcp/client.go#L203-L370&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent Identity / IAM Connector 的 transport client 如何轮询和映射凭据？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent.Context / request.Context&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── auth.Transport.RoundTrip&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ├─ CredentialProvider.Credential&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ├─ Credential.Apply&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └─ Base.RoundTrip&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── session.NewEvent / other ADK code&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ├─ platform.Now(ctx)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └─ platform.NewUUID(ctx)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── telemetry.New → Providers.SetGlobalOtelProviders&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ Providers.Shutdown&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="二核心接口与数据"&gt;二、核心接口与数据&lt;/h2&gt;&#10;&lt;h3 id="1-providercredentialtransport-是三次分工"&gt;1. Provider、Credential、Transport 是三次分工&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;CredentialProvider&lt;/code&gt; 只返回一个实现 &lt;code&gt;Apply(http.Header)&lt;/code&gt; 的 &lt;code&gt;Credential&lt;/code&gt;。例如 &lt;code&gt;BearerCredential&lt;/code&gt; 设置 &lt;code&gt;Authorization: Bearer ...&lt;/code&gt;，&lt;code&gt;APIKeyCredential&lt;/code&gt; 设置调用方指定的 header；&lt;code&gt;OAuth2Credential&lt;/code&gt; 则在每次 &lt;code&gt;Apply&lt;/code&gt; 时从 &lt;code&gt;TokenSource&lt;/code&gt; 取得 token（&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/auth/credential.go#L47-L95"&gt;&lt;code&gt;credential.go#L47-L95&lt;/code&gt;&lt;/a&gt;）。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 10 - Session&Event</title><link>https://jiabin.dev/blog/adk-session-event/</link><pubDate>Tue, 22 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-session-event/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--sessionevent-与-state一轮执行怎样留下历史和状态"&gt;ADK-Go 源码阅读笔记 · Session、Event 与 State：一轮执行怎样留下历史和状态&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。以下“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-inmemory/"&gt;InMemory Session&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-database/"&gt;Database Session&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-memory/"&gt;Memory&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户先说“以后天气都用摄氏度”，再问“杭州天气怎么样”。应用需要保留三类信息：用户说过什么、这一轮执行发生过什么、下次应该读取哪个偏好。ADK 分别用 &lt;code&gt;Event&lt;/code&gt;、&lt;code&gt;Session.Events()&lt;/code&gt; 和带作用域的 &lt;code&gt;State&lt;/code&gt; 表达这些信息。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Session 是一段会话的历史与状态入口；Event 承载一次可记录的交互及其动作；StateDelta 则把本次状态修改送进 Session 服务。&lt;/strong&gt; 理解这套模型，重点是区分 &lt;span class="ann ann-n ann-amber" data-note="三个不同阶段"&gt;本地可读、事件携带、存储成功&lt;/span&gt;。工具能读到自己刚写的值，并不意味着数据库已经提交。&lt;/p&gt;&#10;&lt;p&gt;本文回答三个问题：一段会话怎样标识多轮执行？一次 &lt;code&gt;State().Set&lt;/code&gt; 如何变成下轮可见的状态？哪些事件会保存、哪些字段只是给其他运行时组件消费？&lt;/p&gt;&#10;&lt;h3 id="贯穿示例与术语"&gt;贯穿示例与术语&lt;/h3&gt;&#10;&lt;p&gt;应用名为 &lt;code&gt;weather&lt;/code&gt;，用户为 &lt;code&gt;u1&lt;/code&gt;，有两段会话 &lt;code&gt;s1&lt;/code&gt;、&lt;code&gt;s2&lt;/code&gt;。&lt;code&gt;s1&lt;/code&gt; 的 invocation &lt;code&gt;i1&lt;/code&gt; 接收“杭州天气怎么样，以后用摄氏度”，工具产生以下增量：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 对象快照示意：同一个事件可以同时修改不同作用域。&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;app:weather_provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;provider-a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user:temperature_unit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;last_city&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hangzhou&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;temp:lookup_attempt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;概念&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;例子&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;生命周期和用途&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session 身份&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;(weather, u1, s1)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一段跨多轮的对话；不能仅凭 &lt;code&gt;s1&lt;/code&gt; 区分所有用户&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Invocation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;i1&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次执行关联的一组事件；恢复路径可能延续已有身份&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Event&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;e-tool&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一条工具结果、模型响应、用户消息或框架记录&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;State&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;user:temperature_unit=C&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前可读的键值视图&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;StateDelta&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;{&amp;quot;user:temperature_unit&amp;quot;:&amp;quot;C&amp;quot;}&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;这条事件携带的变更，不是完整状态&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件 / 符号&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;需要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/service.go#L43-L115"&gt;&lt;code&gt;session/service.go: Service&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建、读取和追加分别通过什么接口？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/session.go#L36-L149"&gt;&lt;code&gt;session/session.go: Session / Event&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;谁保存身份、历史和状态？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/agent/common_context.go#L415-L462"&gt;&lt;code&gt;agent/common_context.go: callbackContextState&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Context 的写入为什么会记录 delta？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/runner/runner.go#L725-L775"&gt;&lt;code&gt;runner/runner.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent 事件何时追加，何时交给调用方？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/sessionutils/utils.go#L28-L74"&gt;&lt;code&gt;internal/sessionutils/utils.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;混合前缀怎样拆成独立状态？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/sessiontestsuite/service_suite.go#L82"&gt;&lt;code&gt;session/sessiontestsuite/service_suite.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;服务共享的行为检查有哪些？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用与工具 运行协调层 存储层&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ctx.State().Set(k, v) ──┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─&amp;gt; Event.Actions.StateDelta ──&amp;gt; AppendEvent&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;模型 / 工具返回 Content ┘ ↑ │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Runner 消费 Agent ├─ 追加历史&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 更新状态&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ 更新时间&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;session&lt;/code&gt; 是公开包；&lt;code&gt;internal/sessionutils&lt;/code&gt; 是框架内部的拆合实现，应用不应导入它。应用通常选择一种 &lt;code&gt;session.Service&lt;/code&gt; 注入 Runner，而不是直接构造未导出的内存 Session 或数据库 Session。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 11 - Session InMemory</title><link>https://jiabin.dev/blog/adk-session-inmemory/</link><pubDate>Tue, 22 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-session-inmemory/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--inmemory-session一把锁下的两份-session"&gt;ADK-Go 源码阅读笔记 · InMemory Session：一把锁下的两份 Session&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回源码阅读合集&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session、Event 与 State&lt;/a&gt; · 对照阅读：&lt;a href="https://jiabin.dev/blog/adk-session-database/"&gt;Database Session&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-memory/"&gt;Memory&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;&lt;code&gt;session.InMemoryService()&lt;/code&gt; 适合单进程、短生命周期的运行。它没有数据库连接和 JSON 往返，而是用一个有序 map 保存 Session，再用 App/User 两级 map 保存共享状态。真正容易误解的地方是：一次 &lt;code&gt;AppendEvent&lt;/code&gt; 同时改调用者持有的 Session 和服务 map 中保存的 Session；&lt;span class="ann ann-n ann-amber" data-note="理解双写"&gt;两边不是同一条 Event 切片&lt;/span&gt;。&lt;/p&gt;&#10;&lt;p&gt;贯穿例子仍是 &lt;code&gt;weather/u1&lt;/code&gt;：&lt;code&gt;s1&lt;/code&gt; 把 &lt;code&gt;user:temperature_unit=C&lt;/code&gt; 和 &lt;code&gt;last_city=Hangzhou&lt;/code&gt; 写入；&lt;code&gt;s2&lt;/code&gt; 读取到用户偏好，却不会自动看到 &lt;code&gt;s1&lt;/code&gt; 的 Session 状态。随后用 &lt;code&gt;temp:attempt&lt;/code&gt; 展示当前实现的临时键例外。&lt;/p&gt;&#10;&lt;h3 id="术语先分开"&gt;术语先分开&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;词&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;在 InMemory 中的含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不能推断什么&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;服务保存的 Session&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;omap&lt;/code&gt; 中服务持有的记录&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不是调用方拿到的同一个指针&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;调用方持有的 Session&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Create&lt;/code&gt; / &lt;code&gt;Get&lt;/code&gt; 返回给调用方的 &lt;code&gt;*session&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不是数据库事务代理&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;State map&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;读视图中的扁平 key map&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不代表物理上只有一块 map&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;保存后的 Event&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;追加到某个 Session 的事件对象&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不保证与传入 Event 深拷贝一致&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;位置&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/service.go#L79-L85"&gt;&lt;code&gt;session/service.go:79-85&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;InMemoryService()&lt;/code&gt; 装配 App/User map&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L37-L44"&gt;&lt;code&gt;session/inmemory.go:37-44&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;inMemoryService&lt;/code&gt; 的锁、Session 表、共享状态&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L46-L93"&gt;&lt;code&gt;session/inmemory.go:46-93&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Create 与初始 State 分发&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L95-L176"&gt;&lt;code&gt;session/inmemory.go:95-176&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Get/List 的过滤、合并和复制&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L197-L303"&gt;&lt;code&gt;session/inmemory.go:197-303&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AppendEvent、共享状态与 服务记录&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L319-L481"&gt;&lt;code&gt;session/inmemory.go:319-481&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本地 Session、State、临时键过滤&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;InMemoryService()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── mu: sync.RWMutex&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── sessions: ordered map[encoded(app,user,session)] *session&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── appState: map[app]stateMap&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── userState: map[app][user]stateMap&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Create/Get ──复制──&amp;gt; 调用方 调用方 *session&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AppendEvent ───────&amp;gt; 调用方 *session + 服务记录 *session&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有序 map 的价值不只是“稳定遍历”：&lt;code&gt;List&lt;/code&gt; 通过 &lt;code&gt;ordered.Encode&lt;/code&gt; 后的范围扫描，按 App、User、Session 的编码前缀选出范围。&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L305-L317"&gt;编码&lt;/a&gt;与&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/inmemory.go#L150-L157"&gt;扫描边界&lt;/a&gt;决定了 &lt;code&gt;userID==&amp;quot;&amp;quot;&lt;/code&gt; 时列出某 App 下所有用户。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 12 - Session Database</title><link>https://jiabin.dev/blog/adk-session-database/</link><pubDate>Tue, 22 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-session-database/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--database-session一次-appendevent-如何跨四张表提交"&gt;ADK-Go 源码阅读笔记 · Database Session：一次 AppendEvent 如何跨四张表提交&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回源码阅读合集&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session、Event 与 State&lt;/a&gt; · 对照阅读：&lt;a href="https://jiabin.dev/blog/adk-session-inmemory/"&gt;InMemory Session&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-memory/"&gt;Memory&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;&lt;code&gt;session/database&lt;/code&gt; 并不是把完整 Session JSON 塞进一列。它把 Session 元数据和 Session state 放在 &lt;code&gt;sessions&lt;/code&gt;，事件放在 &lt;code&gt;events&lt;/code&gt;，App/User 共享状态各自放在独立表；一次 &lt;code&gt;AppendEvent&lt;/code&gt; 在一个 GORM 事务内更新状态、插入事件和推进 &lt;code&gt;update_time&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;例子中，&lt;code&gt;weather/u1/s1&lt;/code&gt; 的 &lt;code&gt;e1&lt;/code&gt; 把 &lt;code&gt;user:temperature_unit=C&lt;/code&gt;、&lt;code&gt;last_city=Hangzhou&lt;/code&gt; 写入；另一个请求持有旧快照并尝试追加 &lt;code&gt;e2&lt;/code&gt;。数据库实现会先把 &lt;code&gt;e2&lt;/code&gt; 应用到本地 &lt;code&gt;localSession&lt;/code&gt;，再在事务里用更新时间拒绝 stale 快照。&lt;span class="ann ann-n ann-red" data-note="事务失败的本地代价"&gt;拒绝不等于调用方手中的对象自动回滚&lt;/span&gt;。&lt;/p&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件 / 符号&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/service.go#L46-L82"&gt;&lt;code&gt;database/service.go:46-82&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;构造 Service 与 AutoMigrate&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/storage_session.go#L30-L108"&gt;&lt;code&gt;database/storage_session.go:30-108&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;四个 GORM 模型中的 Session/Event&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/service.go#L85-L158"&gt;&lt;code&gt;database/service.go:85-158&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Create 事务&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/service.go#L160-L253"&gt;&lt;code&gt;database/service.go:160-253&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Get 的 SQL 过滤和 JSON 解码&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/service.go#L354-L482"&gt;&lt;code&gt;database/service.go:354-482&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AppendEvent、stale 检查与事务&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/gorm_datatypes.go#L29-L168"&gt;&lt;code&gt;database/gorm_datatypes.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;stateMap&lt;/code&gt; / &lt;code&gt;dynamicJSON&lt;/code&gt; 的数据库适配&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Create / Get / AppendEvent&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; databaseService (*gorm.DB)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────┴──────────┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ GORM 操作 │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ app_states │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ user_states │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ sessions │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ events │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └───────┬──────────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL / MySQL / SQLite / Spanner Dialector&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;NewSessionService&lt;/code&gt; 自己调用 &lt;code&gt;gorm.Open&lt;/code&gt;；&lt;code&gt;NewSessionServiceFromDB&lt;/code&gt; 接收既有连接；&lt;code&gt;AutoMigrate&lt;/code&gt; 通过具体类型断言拿到 &lt;code&gt;*gorm.DB&lt;/code&gt;，把其他 Session Service 实现传进去会返回 &lt;code&gt;invalid session service type&lt;/code&gt;。&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/database/service.go#L38-L82"&gt;构造代码&lt;/a&gt;说明它不是所有 Session Service 都共享的迁移入口。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 14 - Memory</title><link>https://jiabin.dev/blog/adk-memory/</link><pubDate>Tue, 22 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-memory/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--memory过去的会话怎样进入下一次模型请求"&gt;ADK-Go 源码阅读笔记 · Memory：过去的会话怎样进入下一次模型请求&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session、Event 与 State&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户在 &lt;code&gt;s1&lt;/code&gt; 说“以后请用摄氏度报告天气”，第二天新建 &lt;code&gt;s2&lt;/code&gt;，问“还记得我偏好的温度单位吗”。Session 服务知道每段会话的历史，却不会自动把 &lt;code&gt;s1&lt;/code&gt; 的内容塞进 &lt;code&gt;s2&lt;/code&gt;。Memory 提供一条独立路径：应用显式摄取旧 Session，再在新请求中搜索相关 Entry，把结果放进工具响应或模型 instruction。&lt;/p&gt;&#10;&lt;p&gt;最先要分清的是 &lt;span class="ann ann-n ann-amber" data-note="三步缺一不可"&gt;保存 Session、摄取 Memory、消费检索结果&lt;/span&gt;。Runner 会在普通事件路径追加 Session 事件，但仅仅配置 MemoryService 不会让每轮结束时自动摄取，也不会自动修改 Prompt。&lt;/p&gt;&#10;&lt;p&gt;本文用 &lt;code&gt;weather/u1/s1&lt;/code&gt; 的温度偏好和 &lt;code&gt;weather/u1/s2&lt;/code&gt; 的新问题，追踪谁写、什么时候写、如何搜、结果又通过哪条路径到达模型。&lt;/p&gt;&#10;&lt;h3 id="阅读前先分清这些词"&gt;阅读前先分清这些词&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;本文中的含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;与相近概念的区别&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一段特定对话的历史与状态&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不等于跨会话搜索索引&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Memory Service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;摄取 Session、执行检索的接口&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不强制向量数据库或自动摘要&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Entry&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;后端返回的一条记忆内容&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;InMemory 是文本事件，Vertex 可以是服务端提炼的 fact&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;load_memory&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型显式调用的函数工具&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;搜索 query 由模型给出&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;preload_memory&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型请求处理器形式的工具&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每次请求自动检索，且不向模型声明函数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;顺序&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;位置&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;需要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/memory/service.go#L27-L66"&gt;&lt;code&gt;memory/service.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;公共写入和检索契约是什么？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/runner/runner.go#L676-L694"&gt;&lt;code&gt;runner/runner.go:676-694&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;配置的服务怎样进入 InvocationContext？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/memory/memory.go#L24-L41"&gt;&lt;code&gt;internal/memory/memory.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;搜索作用域由谁填充？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/memory/inmemory.go#L32-L213"&gt;&lt;code&gt;memory/inmemory.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;摄取、覆盖、词索引、排序与限制&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/tool/loadmemorytool/tool.go#L64-L118"&gt;&lt;code&gt;loadmemorytool/tool.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;显式工具如何把 Entry 交回模型？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/tool/preloadmemorytool/tool.go#L71-L134"&gt;&lt;code&gt;preloadmemorytool/tool.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;哪个 query、哪次请求、注入什么 instruction？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/memory/vertexai/vertexai_client.go#L63-L138"&gt;&lt;code&gt;memory/vertexai/vertexai_client.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;云端写入究竟上传内容还是引用 Session？&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;上一段会话 s1 新一段会话 s2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Session.Events 用户新问题&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用调用 AddSessionToMemory ├─ preload: 请求前搜索&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └─ load: 模型调用工具&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Memory 后端 &amp;lt;──────────── SearchMemory(query) ─────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ Entry[] ──&amp;gt; instruction 或 FunctionResponse ──&amp;gt; 模型&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;internal/memory.Memory&lt;/code&gt; 是 Context 适配器，不是另一个记忆库。它填充 AppName/UserID，再调用配置的公开 &lt;code&gt;memory.Service&lt;/code&gt;。服务是进程级或应用装配阶段的依赖；适配器则随 invocation 绑定当前用户和应用身份。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 05 - Agent</title><link>https://jiabin.dev/blog/adk-agent/</link><pubDate>Mon, 21 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-agent/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--agent统一事件流与不同执行策略"&gt;ADK-Go 源码阅读笔记 · Agent：统一事件流与不同执行策略&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。本文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;范围：base Agent 的普通 Run、LlmAgent 的 Flow 创建过程、顺序 / 循环 / 并行 Agent，以及 Remote 和节点入口的边界。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-workflow/"&gt;Workflow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-plugin/"&gt;Plugin&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户问“杭州天气怎么样”，模型要求调用 &lt;code&gt;get_weather(city=&amp;quot;Hangzhou&amp;quot;)&lt;/code&gt;，工具返回气温，模型再生成答案。这几次操作属于同一个 Agent call，却包含两次模型调用和多条事件。&lt;/p&gt;&#10;&lt;p&gt;Agent 模块解决的是：&lt;strong&gt;把不同执行策略统一成“接收上下文、逐条产生事件”的对象，让 Runner 和父 Agent 可以用相同方式驱动它们。&lt;/strong&gt; 模型回合由 Flow 实现，顺序与并行由组合 Agent 实现，共同的回调和事件骨架则放在 base Agent。&lt;/p&gt;&#10;&lt;p&gt;本文回答三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Agent 的接口为何只有几个方法，却不能由应用直接从零实现？&lt;/li&gt;&#10;&lt;li&gt;before → 主体 → after 的真实顺序是什么，错误和内容如何改变后续步骤？&lt;/li&gt;&#10;&lt;li&gt;LlmAgent、Sequential、Loop、Parallel、Remote 怎样接入这套协议，又有哪些不同边界？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="阅读前先分清这些词"&gt;阅读前先分清这些词&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;准确含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;天气例子&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent 对象&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;保存名称、配置、子 Agent 和执行函数的对象&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;weather_agent&lt;/code&gt; 可以被多次运行&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent call&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次 &lt;code&gt;Agent.Run&lt;/code&gt; 的实际迭代过程&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;读取天气并回答&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Invocation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Runner 组织的一次执行；恢复可沿用 ID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;可包含多个 Agent call&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Flow step&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;准备请求、一次模型调用及其工具处理&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;查询 step + 总结 step&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Event&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ADK 运行记录，承载 Content、Actions、身份等&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;调用、工具结果、答案&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ended&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前上下文关联的提前停止标志&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不是 Go 的 Done 通道&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;主线假设回调都返回 nil、没有 transfer、非流式输出，工具成功返回 &lt;code&gt;{&amp;quot;temperature&amp;quot;:26}&lt;/code&gt;。理解正常路径后，再改变这些条件。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 06 - Runner</title><link>https://jiabin.dev/blog/adk-runner/</link><pubDate>Mon, 21 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-runner/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--runner一次输入的执行持久化与恢复"&gt;ADK-Go 源码阅读笔记 · Runner：一次输入的执行、持久化与恢复&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码基准：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。本文的“当前实现”均指这个版本。&lt;/p&gt;&#10;&lt;p&gt;范围：普通 Run 的两条执行路径、消息与 Plugin、HITL、Live 和 Context Compaction。模型与工具内部循环见 Flow，数据库内部存储见 Session 配套文章。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;返回 ADK 源码阅读合集&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-agent/"&gt;Agent&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-context/"&gt;Context&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-plugin/"&gt;Plugin&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-workflow/"&gt;Workflow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-session-event/"&gt;Session Event&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户问“杭州天气怎么样”，Agent 可以调用工具并产出答案，但应用还需要决定：这条输入属于哪个会话、由哪个 Agent 接着回答、工具结果何时保存、用户确认回来后从哪里继续，以及前端断开后还执行哪些收尾。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Runner 把输入、运行环境、事件持久化和恢复入口接成一条调用链。&lt;/strong&gt; 它不直接实现模型推理，却决定 Agent 产生的记录怎样进入 Session、怎样经过 Plugin、怎样交到调用方。&lt;/p&gt;&#10;&lt;p&gt;本文回答三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Runner 为一次输入创建哪些对象，它们被谁读取和修改？&lt;/li&gt;&#10;&lt;li&gt;用户消息、模型 FunctionCall、工具结果、最终答案如何按序保存并流出？&lt;/li&gt;&#10;&lt;li&gt;错误、提前停止、长任务恢复、Live 和压缩分别改变哪一段协议？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="阅读前先分清这些词"&gt;阅读前先分清这些词&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;术语&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;本文中的含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;易混淆之处&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Runner&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;长期持有 root Agent、服务、Plugin 和配置的运行器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不是每次模型调用都新建&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;App + User + SessionID 关联的历史与状态&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一个 Session 可以有多次 invocation&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Invocation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次执行关联的逻辑 ID&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;暂停和恢复可能共用 ID&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;普通 Run&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;返回一个迭代函数，range 时才执行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;非 LLM root 与 LLM root 内部路径不同&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Node runtime&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LLM root 的合成 workflow / 节点运行路径&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不代表所有 root 都进入它&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Partial&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;流式过程中尚未形成完整记录的响应&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;final 判断和 partial 标志不是同一件事&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AppendEvent&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;服务收到一条待记录事件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不等于跨工具、artifact、数据库的原子事务&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="贯穿示例"&gt;贯穿示例&lt;/h3&gt;&#10;&lt;p&gt;主线使用 &lt;code&gt;AppName=weather_app&lt;/code&gt;、&lt;code&gt;userID=user-1&lt;/code&gt;、&lt;code&gt;sessionID=session-1&lt;/code&gt;，root 为 chat 模式 &lt;code&gt;weather_agent&lt;/code&gt;。输入是“杭州天气怎么样”，模型先请求 &lt;code&gt;get_weather(city=&amp;quot;Hangzhou&amp;quot;)&lt;/code&gt;，工具返回 &lt;code&gt;temperature=26&lt;/code&gt;，第二次模型调用生成中文回答。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 01 - Overview</title><link>https://jiabin.dev/blog/adk-overview/</link><pubDate>Sun, 20 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-overview/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--overview从一次请求看懂整个框架"&gt;ADK-Go 源码阅读笔记 · Overview：从一次请求看懂整个框架&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码版本：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;系列目录&lt;/a&gt; · 下一篇：&lt;a href="https://jiabin.dev/blog/adk-quickstart/"&gt;Quickstart&lt;/a&gt; · 执行主线：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;用户问“杭州现在多少度”，模型输出一个 &lt;code&gt;get_weather&lt;/code&gt; 调用，工具返回 &lt;code&gt;26°C&lt;/code&gt;，模型再组织成自然语言回答。这里至少发生了两次模型请求、一次工具执行和多次事件追加。&lt;/p&gt;&#10;&lt;p&gt;ADK-Go 把这些工作分给不同组件：Agent 描述行为，Runner 驱动执行，Flow 连接模型和工具，服务保存历史与状态。&lt;span class="ann ann-n ann-amber" data-note="先记住分工"&gt;一次模型响应只是 Agent 运行过程中的一步&lt;/span&gt;。&lt;/p&gt;&#10;&lt;p&gt;这篇总览围绕三个问题展开：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;从程序入口到模型、工具和数据库，中间有哪些边界？&lt;/li&gt;&#10;&lt;li&gt;一次输入怎样变成事件，工具结果又怎样进入下一次模型请求？&lt;/li&gt;&#10;&lt;li&gt;接口抽象提供了什么，又有哪些必须结合具体实现理解的限制？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;p&gt;ADK-Go 是用 Go 代码构建 Agent 应用的框架。应用通过构造函数组合 Agent、Model、Tool、服务和运行入口，不需要先把整个应用写成一份声明式工作流。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阅读层次&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件 / 入口&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;解决的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;应用示例&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/examples/quickstart/main.go"&gt;&lt;code&gt;examples/quickstart/main.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;最小应用怎样装配&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;启动入口&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/launcher.go"&gt;&lt;code&gt;cmd/launcher/launcher.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;console / web 如何共用应用配置&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Agent 契约&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/agent/agent.go#L35-L76"&gt;&lt;code&gt;agent/agent.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;什么对象可以被执行&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;请求驱动&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/runner/runner.go#L536-L610"&gt;&lt;code&gt;runner/runner.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次输入如何进入运行时&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型工具循环&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/base_flow.go#L612-L745"&gt;&lt;code&gt;internal/llminternal/base_flow.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模型提出调用后由谁执行&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;会话记录&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/session/session.go"&gt;&lt;code&gt;session/session.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;历史、状态和事件如何表达&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;图调度&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/workflow/scheduler.go"&gt;&lt;code&gt;workflow/scheduler.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;节点、事件和恢复如何协调&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;先把模块放在同一张结构图上：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Application&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-- Launcher / HTTP handler / 自定义入口&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | Runner &amp;lt;------ SessionService&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | &amp;lt;--------- ArtifactService / MemoryService&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | InvocationContext：绑定本次执行所需的对象&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | Agent / node runtime&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | +-- LlmAgent --&amp;gt; Flow --&amp;gt; Model&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | +&amp;lt;- Tool &amp;lt;-+ FunctionCall&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | +-- 自定义 Agent / Workflow / 远程 Agent&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-- Plugin：在已接入的生命周期边界观察或改变行为&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Model 适配器与 Tool 的业务实现可以替换，运行层仍围绕请求、响应和事件协作。但是模型共用接口并不意味着所有 provider 支持相同工具、多模态或流式能力。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 02 - 初见 ADK</title><link>https://jiabin.dev/blog/adk-quickstart/</link><pubDate>Sun, 20 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-quickstart/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--quickstart一个-main-函数怎样启动-agent-应用"&gt;ADK-Go 源码阅读笔记 · Quickstart：一个 main 函数怎样启动 Agent 应用&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码版本：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;系列目录&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-overview/"&gt;Overview&lt;/a&gt; · 后续阅读：&lt;a href="https://jiabin.dev/blog/adk-launcher/"&gt;Launcher&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-model/"&gt;Model&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-agent/"&gt;Agent&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;一个能回答天气问题的程序，为什么既要创建 Model，又要创建 Agent，还要经过 Loader 和 Launcher？因为这几个对象回答的是不同问题：调用哪个模型、如何组织行为、如何找到应用入口、用户通过什么界面交互。&lt;/p&gt;&#10;&lt;p&gt;官方 Quickstart 把这条装配链放在一个 &lt;code&gt;main&lt;/code&gt; 中。先看它的结构，再跟踪用户输入到达以后发生的事情，就能把配置代码与运行代码分开。&lt;/p&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件 / 符号&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;在这个例子中的职责&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/examples/quickstart/main.go#L18-L65"&gt;examples/quickstart/main.go&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建模型、Agent、Loader 与 Launcher&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/model/gemini/gemini.go#L49-L80"&gt;gemini.NewModel&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建 Gemini SDK client 和模型适配器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/agent/llmagent/llmagent.go#L39-L137"&gt;llmagent.New&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把配置装配为 LlmAgent&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/agent/loader.go#L50-L76"&gt;agent.NewSingleLoader&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;用一个 Agent 提供加载入口&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/full/full.go#L30-L33"&gt;full.NewLauncher&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;提供 console 与 web 两类启动方式&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/console/console.go#L71-L121"&gt;console.Run&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建 Session 和 Runner，进入输入循环&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;构造阶段&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; genai.ClientConfig&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; gemini.NewModel -&amp;gt; llmagent.New -&amp;gt; NewSingleLoader&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; launcher.Config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;运行阶段 v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; os.Args[1:] ----------------&amp;gt; Launcher.Execute&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; console 或 web 入口&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Runner / Agent&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;span class="ann ann-n ann-amber" data-note="装配不等于执行"&gt;创建了 Agent 对象，还不代表模型已经生成过一次回答&lt;/span&gt;。模型请求通常要等到入口收到输入并消费运行事件时才发生。&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 03 - Launcher</title><link>https://jiabin.dev/blog/adk-launcher/</link><pubDate>Sun, 20 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-launcher/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--launcher从命令行到-runner"&gt;ADK-Go 源码阅读笔记 · Launcher：从命令行到 Runner&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码版本：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;系列目录&lt;/a&gt; · 前置阅读：&lt;a href="https://jiabin.dev/blog/adk-quickstart/"&gt;Quickstart&lt;/a&gt; · 后续阅读：&lt;a href="https://jiabin.dev/blog/adk-runner/"&gt;Runner&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Launcher 是 ADK 应用的进程入口层。它处理命令行参数、校验进程级配置、选择运行方式，并把同一份 AgentLoader、SessionService、Plugin 和鉴权配置交给 console 或 web 子入口。&lt;/p&gt;&#10;&lt;p&gt;它不负责模型推理，也不负责工具执行。&lt;span class="ann ann-n ann-amber" data-note="Launcher 负责进来"&gt;Launcher 选择怎么进入应用，Runner 才驱动一次 Agent 运行&lt;/span&gt;。&lt;/p&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;层次&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件 / 符号&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;顶层契约&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/launcher.go#L34-L116"&gt;&lt;code&gt;cmd/launcher/launcher.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Launcher、SubLauncher、Config&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;选择器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/universal/universal.go#L34-L101"&gt;&lt;code&gt;cmd/launcher/universal/universal.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;按第一个关键词选择入口&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;预设组合&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/full/full.go#L30-L33"&gt;&lt;code&gt;cmd/launcher/full/full.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;console + 全部 web 子入口&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;生产组合&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/prod/prod.go#L22-L31"&gt;&lt;code&gt;cmd/launcher/prod/prod.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;REST API + A2A&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;终端运行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/console/console.go#L71-L121"&gt;&lt;code&gt;cmd/launcher/console/console.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Session、Runner、stdin 循环&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;HTTP 运行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/web/web.go#L174-L274"&gt;&lt;code&gt;cmd/launcher/web/web.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;router、sublauncher、HTTP server&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;REST 挂载&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/cmd/launcher/web/api/api.go#L250-L315"&gt;&lt;code&gt;cmd/launcher/web/api/api.go&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建 &lt;code&gt;adkrest.Server&lt;/code&gt; 并挂到 path prefix&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;整体关系可以先压缩成一张图：&lt;/p&gt;</description></item><item><title>Agent Development Kit (ADK) · 04 - Model</title><link>https://jiabin.dev/blog/adk-model/</link><pubDate>Sun, 20 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-model/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--model统一接口怎样连接不同模型服务"&gt;ADK-Go 源码阅读笔记 · Model：统一接口怎样连接不同模型服务&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;源码版本：&lt;a href="https://github.com/google/adk-go/tree/v2.4.0"&gt;ADK-Go v2.4.0&lt;/a&gt;&lt;/strong&gt;，commit &lt;code&gt;4e57df40cbd35f055ba654b03ffb0e509fdd754a&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jiabin.dev/blog/adk/"&gt;系列目录&lt;/a&gt; · 配套阅读：&lt;a href="https://jiabin.dev/blog/adk-quickstart/"&gt;Quickstart&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-flow/"&gt;Flow&lt;/a&gt;、&lt;a href="https://jiabin.dev/blog/adk-tool/"&gt;Tool&lt;/a&gt;。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Flow 已经准备好用户消息、历史、指令和工具声明，下一步要把请求交给模型服务。Gemini 和 OpenAI 的请求字段、角色、函数调用与流式事件并不相同；Model 层把这些差异放在适配器中处理，向上提供统一的 &lt;code&gt;LLMRequest → LLMResponse&lt;/code&gt; 接口。&lt;/p&gt;&#10;&lt;p&gt;&lt;span class="ann ann-n ann-amber" data-note="统一入口，不是功能同一"&gt;实现了同一个 LLM 接口，不代表所有模型支持相同的配置和工具&lt;/span&gt;。这篇文章围绕一次天气函数调用，说明统一数据怎样跨过 provider 边界，以及信息在转换中如何被保留或拒绝。&lt;/p&gt;&#10;&lt;h2 id="一源码入口与阅读地图"&gt;一、源码入口与阅读地图&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;model/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ llm.go LLM / LLMRequest / LLMResponse&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ registry.go 模型名正则 -&amp;gt; Factory&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ gemini/gemini.go GenAI SDK 适配器&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ openaimodel/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ openai.go Responses API 调用与流式收尾&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ request.go 请求、消息、配置转换&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ tools.go 函数声明与 tool choice&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ response.go 返回内容、usage、结束原因&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └─ stream.go Responses 事件 -&amp;gt; GenAI 中间结构&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─ apigee/apigee.go 配置代理后委托 Gemini adapter&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;入口 / 文件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阅读时要回答的问题&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/model/llm.go#L25-L68"&gt;model/llm.go&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;上层到底传什么、收到什么&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/base_flow.go#L815-L899"&gt;Flow.callLLM&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;哪些 callback 返回后不再调用模型&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/model/gemini/gemini.go#L87-L159"&gt;gemini.GenerateContent&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;普通调用和流式调用如何选择&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/model/openaimodel/request.go#L36-L86"&gt;buildOpenAIParams&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;通用请求如何转换成 Responses 参数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/model/openaimodel/stream.go"&gt;streamTranslator&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;provider 的 delta 事件怎样转成通用 part&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/internal/llminternal/stream_aggregator.go"&gt;streamingResponseAggregator&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;增量和完整响应怎样共存&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/google/adk-go/blob/v2.4.0/model/registry.go"&gt;model.Register / NewLLM&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;字符串名称怎样解析成实例&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="二核心接口与数据结构"&gt;二、核心接口与数据结构&lt;/h2&gt;&#10;&lt;h3 id="1-两个方法的公共契约"&gt;1. 两个方法的公共契约&lt;/h3&gt;&#10;&lt;p&gt;下面是接口与请求结构的源码摘录：&lt;/p&gt;</description></item></channel></rss>