<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golang on elonnzhang</title><link>https://jiabin.dev/categories/golang/</link><description>Recent content in Golang on elonnzhang</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Wed, 23 Sep 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://jiabin.dev/categories/golang/index.xml" rel="self" type="application/rss+xml"/><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) · 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) · 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><item><title>Agent Development Kit (ADK) · 07 - Flow</title><link>https://jiabin.dev/blog/adk-flow/</link><pubDate>Sun, 20 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk-flow/</guid><description>&lt;h1 id="adk-go-源码阅读笔记--flow一次-llm-agent-回合是怎样跑完的"&gt;ADK-Go 源码阅读笔记 · Flow：一次 LLM 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-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;code&gt;get_weather(city=&amp;quot;Hangzhou&amp;quot;)&lt;/code&gt;。到这里，天气查询还没有执行，用户也还没有得到答案。程序还必须找到工具、执行代码、把结果交回模型，并判断是否还要继续调用模型。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Flow 就是把这些阶段接起来的内部控制层。&lt;/strong&gt; 它让一次模型调用成为可以反复“请求 → 执行 → 反馈”的 Agent 运行过程。理解它，关键是同时追踪控制流和数据：下一步由谁触发，上一轮结果又通过什么对象进入下一步。&lt;/p&gt;&#10;&lt;p&gt;本文回答三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;谁创建 Flow，它依赖哪些接口和数据？&lt;/li&gt;&#10;&lt;li&gt;一条模型 FunctionCall 如何变成工具结果，再进入下一次模型请求？&lt;/li&gt;&#10;&lt;li&gt;Flow 与 Runner、Model、Tool 之间的职责边界在哪里？&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;主线先看普通 &lt;code&gt;Run&lt;/code&gt; 的一次工具调用闭环；SSE、确认、transfer 和 Live 放到第四节展开。本文不是 Workflow scheduler 或 Session 数据库实现的完整说明。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;阅读准备。&lt;/strong&gt; 只需要了解 Go 的接口、指针、map，以及大语言模型的 Function Calling：模型输出函数名和参数，实际函数由应用执行。如果不熟悉 &lt;code&gt;iter.Seq2&lt;/code&gt;，第三节会结合代码解释；无需先掌握 ADK 的全部模块。&lt;/p&gt;&#10;&lt;p&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;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;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;Invocation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次执行上下文所关联的运行；恢复路径可能沿用原 invocation ID&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;Flow step&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次 &lt;code&gt;runOneStep&lt;/code&gt;：准备请求、消费模型响应、处理工具及后续动作&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;通常 2 步&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;code&gt;GenerateContent&lt;/code&gt;；BeforeModel 可使它被跳过&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本例 2 次&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;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;主线为用户输入、调用、结果、答案 4 条；外层还可能有控制事件&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 / final&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Partial 表示响应尚未聚合完成；&lt;code&gt;IsFinalResponse()&lt;/code&gt; 是事件的结束判定方法&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;p&gt;第一次阅读可先读第一至第三节，再看第五至第七节；第四节用于沿具体问题深入。文中标为“源码摘录”的代码对应固定 tag；省略外围代码会明确说明。“伪代码”“示意数据”和教学示例不冒充源码原文。&lt;/p&gt;</description></item><item><title>ADK-Go 源码阅读系列</title><link>https://jiabin.dev/blog/adk/</link><pubDate>Sat, 19 Sep 2026 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/adk/</guid><description>&lt;h1 id="adk-go-源码阅读合集"&gt;ADK-Go 源码阅读合集&lt;/h1&gt;&#10;&lt;p&gt;用户发来一句“杭州天气怎么样”，从接收输入到给出答案，中间会经历模型请求、工具执行、事件保存和下一轮推理。多 Agent、人工确认、流式输出和长会话则会改变这条路径。&lt;/p&gt;&#10;&lt;p&gt;这个系列沿着一次请求追踪 ADK-Go 的实现：&lt;strong&gt;谁创建对象，谁调用接口，数据在哪一步变成事件，结果又如何成为下一次输入。&lt;/strong&gt; 各篇聚焦一个模块，并用相邻模块的实际调用方说明它的边界。&lt;/p&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、Live、不同模型适配器与存储后端的行为分别说明；一种运行方式的结论不自动适用于其他方式。&lt;/p&gt;&#10;&lt;/blockquote&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;Overview / Quickstart&#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 / Model / 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;Runner → Flow → Tool&#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;Context → Session / Event&#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 / Plugin / Workflow&#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;Artifact → Server → Registry&#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 / Telemetry / Platform&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果还不了解 ADK，先读 Overview 和 Quickstart；如果已写过简单 Agent，可从 Flow 的天气示例进入，再沿 &lt;code&gt;Runner → Tool → Context → Session&lt;/code&gt; 追踪它上下游的实现。&lt;/p&gt;</description></item><item><title>Go 代码规范</title><link>https://jiabin.dev/blog/go-stander/</link><pubDate>Wed, 31 Dec 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/go-stander/</guid><description>&lt;h1 id="golang-代码规范---tencent-golang-代码规范"&gt;Golang 代码规范 - Tencent golang 代码规范&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;内容从网上搜索得到&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="1-前言"&gt;1. 前言&lt;/h2&gt;&#10;&lt;p&gt;为形成公司统一的 Go 编码风格，以保障公司项目代码的易维护性和编码安全性，特制定本规范。&lt;/p&gt;&#10;&lt;p&gt;本规范在 &lt;a href="https://github.com/golang/go/wiki/CodeReviewComments"&gt;Google Golang 代码规范&lt;/a&gt; 的基础上，根据腾讯实际情况进行了调整和补充。&lt;/p&gt;&#10;&lt;p&gt;每项规范内容，给出了要求等级，其定义为：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;必须（Mandatory）&lt;/strong&gt;：用户必须采用；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;推荐（Preferable）&lt;/strong&gt;：用户理应采用，但如有特殊情况，可以不采用；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;可选（Optional）&lt;/strong&gt;：用户可参考，自行决定是否采用；&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;目前本规范以 Gometalinter 工具落地，点击查看&lt;a href=""&gt;工具源码&lt;/a&gt;。&lt;/p&gt;&#10;&lt;h2 id="2-代码风格"&gt;2. 代码风格&lt;/h2&gt;&#10;&lt;h3 id="21-必须格式化"&gt;2.1 【必须】格式化&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;代码都必须用 &lt;code&gt;gofmt&lt;/code&gt; 格式化。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="22-推荐换行"&gt;2.2 【推荐】换行&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;建议一行代码不要超过&lt;code&gt;120列&lt;/code&gt;，超过的情况，使用合理的换行方法换行。&lt;/li&gt;&#10;&lt;li&gt;例外场景：&#10;&lt;ul&gt;&#10;&lt;li&gt;import 模块语句&lt;/li&gt;&#10;&lt;li&gt;工具生成代码&lt;/li&gt;&#10;&lt;li&gt;struct tag&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="23-必须括号和空格"&gt;2.3 【必须】括号和空格&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;遵循 &lt;code&gt;gofmt&lt;/code&gt; 的逻辑。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;运算符和操作数之间要留空格。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;作为输入参数或者数组下标时，运算符和运算数之间不需要空格，紧凑展示。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="24-必须import-规范"&gt;2.4 【必须】import 规范&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;使用 &lt;code&gt;goimports&lt;/code&gt; 自动格式化引入的包名，import 规范原则上以 &lt;code&gt;goimports&lt;/code&gt; 规则为准。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;goimports&lt;/code&gt; 会自动把依赖包按首字母排序，并对包进行分组管理，通过空行隔开，默认分为本地包（标准库、内部包）、第三方包。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;标准包永远位于最上面的第一组。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;内部包是指不能被外部 import 的包，如 GoPath 模式下的包名或者非域名开头的当前项目的 GoModules 包名。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;带域名的包名都属于第三方包，如 company.code.oa.com/xxx/xxx，github.com/xxx/xxx，不用区分是否是当前项目内部的包。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;goimports&lt;/code&gt; 默认最少分成本地包和第三方包两大类，这两类包必须分开不能放在一起。本地包或者第三方包内部可以继续按实际情况细分不同子类。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;不要使用相对路径引入包：&lt;/p&gt;</description></item><item><title>Go GMP 调度模型</title><link>https://jiabin.dev/blog/golang-gmp/</link><pubDate>Thu, 14 Aug 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/golang-gmp/</guid><description>&lt;h3 id="g-m-p-模型"&gt;G M P 模型&lt;/h3&gt;&#10;&lt;p&gt;一句话：调度器调度任务队列、P在M上运行去执行G&lt;/p&gt;&#10;&lt;p&gt;两个任务队列&#10;- 全局任务队列&#10;- P本地队列&#10;调度器&lt;/p&gt;&#10;&lt;h3 id="总览"&gt;总览&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://jiabin.dev/assets/img/0fac1127-3a5c-4baa-b83a-f4381e28644d.png" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;​ 1、我们通过 go func () 来创建一个 goroutine；&lt;/p&gt;&#10;&lt;p&gt;​ 2、有两个存储 G 的队列，一个是局部调度器 P 的本地队列、一个是全局 G 队列。新创建的 G 会先保存在 P 的本地队列中，如果 P 的本地队列已经满了就会保存在全局的队列中；&lt;/p&gt;&#10;&lt;p&gt;​ 3、G 只能运行在 M 中，一个 M 必须持有一个 P，M 与 P 是 1：1 的关系。M 会从 P 的本地队列弹出一个可执行状态的 G 来执行，如果 P 的本地队列为空，就会向其他的 MP 组合偷取一个可执行的 G 来执行；&lt;/p&gt;&#10;&lt;p&gt;​ 4、一个 M 调度 G 执行的过程是一个循环机制；&lt;/p&gt;&#10;&lt;p&gt;​ 5、当 M 执行某一个 G 时候如果发生了 syscall 或则其余阻塞操作，M 会阻塞，如果当前有一些 G 在执行，runtime 会把这个线程 M 从 P 中摘除 (detach)，然后再创建一个新的操作系统的线程 (如果有空闲的线程可用就复用空闲线程) 来服务于这个 P；&lt;/p&gt;</description></item><item><title>Go heap 包笔记</title><link>https://jiabin.dev/blog/golang-heap/</link><pubDate>Thu, 14 Aug 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/golang-heap/</guid><description>&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;// The Interface type describes the requirements&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="c1"&gt;// for a type using the routines in this package.&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="c1"&gt;// Any type that implements it may be used as a&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="c1"&gt;// min-heap with the following invariants (established after&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="c1"&gt;// [Init] has been called or if the data is empty or sorted):&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="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="c1"&gt;//&#9;!h.Less(j, i) for 0 &amp;lt;= i &amp;lt; h.Len() and 2*i+1 &amp;lt;= j &amp;lt;= 2*i+2 and j &amp;lt; h.Len()&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="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="c1"&gt;// Note that [Push] and [Pop] in this interface are for package heap&amp;#39;s&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="c1"&gt;// implementation to call. To add and remove things from the heap,&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="c1"&gt;// use [heap.Push] and [heap.Pop].&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;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &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;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Interface&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;&#9;&lt;/span&gt;&lt;span class="nf"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&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; &lt;/span&gt;&lt;span class="c1"&gt;// add x as element Len()&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;&#9;&lt;/span&gt;&lt;span class="nf"&gt;Pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// remove and return element Len() - 1.&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;p&gt;初始化一个堆&lt;/p&gt;</description></item><item><title>Go GC 原理与观测</title><link>https://jiabin.dev/blog/gogc/</link><pubDate>Sat, 02 Aug 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/gogc/</guid><description>&lt;h2 id="1-什么是gc"&gt;1. 什么是GC&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;GC&lt;/code&gt;，全称 &lt;code&gt;Garbage Collection&lt;/code&gt;，即垃圾回收，是一种自动内存管理的机制。&lt;/p&gt;&#10;&lt;h2 id="2-为什么gc"&gt;2. 为什么GC&lt;/h2&gt;&#10;&lt;p&gt;程序向操作系统申请的内存不再需要时&lt;/p&gt;&#10;&lt;h2 id="3-怎么gc"&gt;3. 怎么GC&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;赋值器（&lt;em&gt;Mutator&lt;/em&gt;）&lt;/strong&gt;：用户代码，负责修改对象引用关系。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;回收器（&lt;em&gt;Collector&lt;/em&gt;）&lt;/strong&gt;：负责标记和回收无用内存。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="4-gc-可观测"&gt;4. GC 可观测&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;GODEBUG=gctrace=1&lt;/code&gt;&#10;&lt;code&gt;debug.ReadGCStats&lt;/code&gt;&#10;&lt;code&gt;runtime.ReadMemStats&lt;/code&gt;&lt;/p&gt;&#10;&lt;h2 id="5-为什么会出现内存泄漏"&gt;5. 为什么会出现内存泄漏&lt;/h2&gt;&#10;&lt;pre&gt;&lt;code&gt;- 预期能被快速释放的内存因被根对象引用而没有得到迅速释放&#10;&#9;&#9; - 指针&#10;&#9;&#9; - 全局变量&#10; - Goroutine 泄漏&#10; Goroutine 作为一种逻辑上理解的轻量级线程，需要维护执行用户代码的上下文信息。在运行过程中也需要消耗一定的内存来保存这类信息，而这些内存在目前版本的 Go 中是不会被释放的。因此，如果一个程序持续不断地产生新的 goroutine、且不结束已经创建的 goroutine 并复用这部分内存，就会造成内存泄漏的现象.&#10; eg: 曾经项目中遇到的，mock了一个链接，忘记关闭&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// FillArgsInSQL 使用 mock db 将参数填回 SQL&#10;func FillArgsInSQL(sql string, args ...any) string {&#10;&#9;db, _, _ := sqlmock.New()&#10;&#9;defer db.Close() // &amp;lt;—— 如果没关闭&#10;&#9;gormDB, _ := gorm.Open(&#10;&#9;&#9;mysql.New(mysql.Config{&#10;&#9;&#9;&#9;Conn: db, // Existing mock database connection&#10;&#9;&#9;&#9;SkipInitializeWithVersion: true, // 跳过 SELECT VERSION() 检查&#10;&#9;&#9;}),&#10;&#9;&#9;&amp;amp;gorm.Config{DryRun: true})&#10;&#9;sqlStmt := gormDB.ToSQL(func(tx *gorm.DB) *gorm.DB {&#10;&#9;&#9;return tx.Raw(sql, JsonToBasicType(args)...)&#10;&#9;})&#10;&#9;return sqlStmt&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="6-常见的垃圾回收方法"&gt;6. 常见的垃圾回收方法&lt;/h2&gt;&#10;&lt;p&gt;所有的 GC 算法其存在形式可以归结为追踪（Tracing）和引用计数（Reference Counting）这两种形式的混合运用。&#10;目前比较常见的 GC 实现方式包括：&lt;/p&gt;</description></item><item><title>Go sort 包笔记</title><link>https://jiabin.dev/blog/golang-package-sort/</link><pubDate>Tue, 29 Jul 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/golang-package-sort/</guid><description>&lt;p&gt;&lt;a href="https://www.notion.so/sort-23f21f3fbf218042a172fcee79ac3d8b?source=copy_link"&gt;https://www.notion.so/sort-23f21f3fbf218042a172fcee79ac3d8b?source=copy_link&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Go 中 exec.Command 与 os.StartProcess 的区别</title><link>https://jiabin.dev/blog/go-exec/</link><pubDate>Sat, 26 Jul 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/go-exec/</guid><description>&lt;p&gt;exec.Command 与 os.StartProcess 有什么不一样&lt;/p&gt;&#10;&lt;p&gt;这是一个非常经典的问题：&lt;strong&gt;exec.Command 和 os.StartProcess 都可以启动新进程，但功能和易用性完全不同&lt;/strong&gt;。我给你详细拆解一下：&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="1"&gt;&lt;strong&gt;1.&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;h2 id="osstartprocess"&gt;&lt;strong&gt;os.StartProcess&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;h3 id="特点"&gt;&lt;strong&gt;特点&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Go 最底层的 &lt;strong&gt;进程创建 API&lt;/strong&gt;，是对系统调用 fork + exec（类 Unix）或 CreateProcess（Windows）的直接封装。&lt;/li&gt;&#10;&lt;li&gt;调用麻烦，需要手动准备命令参数、环境变量、标准输入输出、文件描述符等。&lt;/li&gt;&#10;&lt;li&gt;返回 *os.Process，只能用它做简单的进程管理（等待、杀死）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="用法示例"&gt;&lt;strong&gt;用法示例&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;package main&#10;&#10;import (&#10;&#9;&amp;#34;os&amp;#34;&#10;&#9;&amp;#34;syscall&amp;#34;&#10;)&#10;&#10;func main() {&#10;&#9;// argv 必须是完整路径 + 参数数组&#10;&#9;procAttr := &amp;amp;os.ProcAttr{&#10;&#9;&#9;Dir: &amp;#34;&amp;#34;,&#10;&#9;&#9;Env: os.Environ(),&#10;&#9;&#9;Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, // 指定标准IO&#10;&#9;&#9;Sys: &amp;amp;syscall.SysProcAttr{},&#10;&#9;}&#10;&#10;&#9;process, err := os.StartProcess(&amp;#34;/bin/ls&amp;#34;, []string{&amp;#34;ls&amp;#34;, &amp;#34;-l&amp;#34;}, procAttr)&#10;&#9;if err != nil {&#10;&#9;&#9;panic(err)&#10;&#9;}&#10;&#9;process.Wait()&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;👉 缺点：调用复杂，几乎不做封装。&lt;/p&gt;</description></item><item><title>Go 1.23 迭代器原理</title><link>https://jiabin.dev/blog/go-iter/</link><pubDate>Mon, 14 Jul 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/go-iter/</guid><description>&lt;p&gt;(&lt;a href="https://mp.weixin.qq.com/s?biz=MzA4ODg0NDkzOA==&amp;amp;mid=2247510020&amp;amp;idx=1&amp;amp;sn=c4daaaf3e895bf17a76e2481a74bf243&amp;amp;chksm=91a791e540e5ee433099f24654eaf06126750dd2392d660bf9144cbf33582e4e0022a6f060f9#rd"&gt;https://mp.weixin.qq.com/s?biz=MzA4ODg0NDkzOA==&amp;mid=2247510020&amp;idx=1&amp;sn=c4daaaf3e895bf17a76e2481a74bf243&amp;chksm=91a791e540e5ee433099f24654eaf06126750dd2392d660bf9144cbf33582e4e0022a6f060f9#rd&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://jiabin.dev/assets/img/iter1.png" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://jiabin.dev/assets/img/iter2.png" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;也就是说只有符合上述函数签名的函数类型才是可以被for range支持的iterator。即所谓自定义iterator，本质上就是一个&lt;strong&gt;接受一个函数类型参数的函数(如上图中红框①)&lt;/strong&gt;，按惯例，这个函数类型的参数&lt;strong&gt;被命名为yield(见红框②)&lt;/strong&gt;。从Backward函数的返回值(一个iterator)的实现来看，&lt;em&gt;&lt;strong&gt;当yield函数返回false时，迭代结束；否则迭代继续进行，直到集合类型(如slice)中所有元素都被遍历完&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;&#10;&lt;p&gt;其实，for range+自定义iterator可以看成是Go提供的又一个“语法糖”，它是&lt;strong&gt;通过Go编译器在编译阶段的代码转换&lt;/strong&gt;来实现的。下面我们还基于Backward那个例子来看看这个转换过程：&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://jiabin.dev/assets/img/iter3.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>Go pprof 性能分析</title><link>https://jiabin.dev/blog/go-pprof/</link><pubDate>Mon, 14 Jul 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/go-pprof/</guid><description>&lt;p&gt;&lt;a href="https://www.liwenzhou.com/posts/Go/pprof/"&gt;https://www.liwenzhou.com/posts/Go/pprof/&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;四个方面&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;CPU profile：报告程序的 CPU 使用情况，按照一定频率去采集应用程序在 CPU 和寄存器上面的数据&lt;/li&gt;&#10;&lt;li&gt;Memory profile（Heap Profile：内存使用情况&lt;/li&gt;&#10;&lt;li&gt;Block profiling：goroutine 不在运行的状态，可以用来分析和查找死锁等性能瓶颈&lt;/li&gt;&#10;&lt;li&gt;Goroutine profiling：goroutines的使用情况，以及调用关系&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Go语言内置了获取程序的运行数据的工具，包括以下两个标准库：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;runtime/pprof&lt;/code&gt;：采集工具型应用运行数据进行分析&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;net/http/pprof&lt;/code&gt;：采集服务型应用运行时数据进行分析&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;CPU的采集&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pprof.StartCPUProfile(w io.Writer)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pprof.StopCPUProfile()&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;得到采样数据之后，使用&lt;code&gt;go tool pprof&lt;/code&gt;工具进行内存性能分析。&lt;/p&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-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pprof.WriteHeapProfile(w io.Writer)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;go tool pprof&lt;/code&gt;默认是使用&lt;code&gt;-inuse_space&lt;/code&gt;进行统计，还可以使用&lt;code&gt;-inuse-objects&lt;/code&gt;查看分配对象的数量。&lt;/p&gt;&#10;&lt;p&gt;import _ &amp;ldquo;net/http/pprof”&lt;/p&gt;&#10;&lt;p&gt;你的 HTTP 服务都会多出&lt;code&gt;/debug/pprof&lt;/code&gt; endpoint&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://jiabin.dev/assets/img/c95a629c-e7a3-48eb-ae44-4d1c5be92e72.png" alt=""&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(pprof) top3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Showing nodes accounting for 100.37s, 87.68% of 114.47s total&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Dropped 17 nodes (cum &amp;lt;= 0.57s)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Showing top 3 nodes out of 4&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; flat flat% sum% cum cum%&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 42.52s 37.15% 37.15% 91.73s 80.13% runtime.selectnbrecv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 35.21s 30.76% 67.90% 39.49s 34.50% runtime.chanrecv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 22.64s 19.78% 87.68% 114.37s 99.91% main.logicCode&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中：&lt;/p&gt;</description></item><item><title>Go database/sql 连接池源码阅读</title><link>https://jiabin.dev/blog/go-databases-sql/</link><pubDate>Sat, 11 Jan 2025 00:00:00 +0800</pubDate><guid>https://jiabin.dev/blog/go-databases-sql/</guid><description>&lt;h1 id="golang-databasesql-数据库连接源码阅读"&gt;Golang database/sql 数据库连接源码阅读&lt;/h1&gt;&#10;&lt;p&gt;代码仓库 database/sql&lt;/p&gt;&#10;&lt;h3 id="打开数据库连接方法"&gt;打开数据库连接方法&lt;/h3&gt;&#10;&lt;p&gt;打开数据库连接 sql.Open() 返回 *sql.DB&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// Open 打开一个由数据库驱动名称和驱动特定的数据源名称指定的数据库，&#10;// 通常至少包括一个数据库名称和连接信息。&#10;//&#10;// 大多数用户会通过一个特定于驱动的连接助手函数来打开数据库，该函数返回一个 [*DB]。&#10;// Go 标准库中不包含数据库驱动。请参见 &amp;lt;https://golang.org/s/sqldrivers&amp;gt; 获取第三方驱动列表。&#10;//&#10;// Open 可能仅验证其参数而不创建数据库连接。要验证数据源名称是否有效，请调用 [DB.Ping]。&#10;//&#10;// 返回的 [DB] 对象是线程安全的，可以被多个 goroutine 并发使用，并维护自己的空闲连接池。&#10;// 因此，Open 函数通常只需调用一次。通常不需要关闭 [DB]。&#10;func Open(driverName, dataSourceName string) (*DB, error) {&#10;&#9;driversMu.RLock()&#10;&#9;driveri, ok := drivers[driverName]&#10;&#9;driversMu.RUnlock()&#10;&#9;if !ok {&#10;&#9;&#9;return nil, fmt.Errorf(&amp;#34;sql: unknown driver %q (forgotten import?)&amp;#34;, driverName)&#10;&#9;}&#10;&#10;&#9;if driverCtx, ok := driveri.(driver.DriverContext); ok {&#10;&#9;&#9;connector, err := driverCtx.OpenConnector(dataSourceName)&#10;&#9;&#9;if err != nil {&#10;&#9;&#9;&#9;return nil, err&#10;&#9;&#9;}&#10;&#9;&#9;return OpenDB(connector), nil&#10;&#9;}&#10;&#10;&#9;return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;先看一下DB 定义&lt;/p&gt;</description></item></channel></rss>