jingyuan-opc-agents-platform(Go 语言),每一篇都从项目里实际长出来的代码出发,再下钻到 ADK 框架源码,讲清楚"怎么用"与"为什么这样设计"。第 1 篇先解决从零到一的问题:装好环境、读懂配置、让一个名叫 articleAgent 的"爆款文案工坊"在终端和浏览器里跑起来。通过本篇的学习,读者可以完整复现这个最小可运行工程,并对 ADK 的功能地图形成整体印象。
本篇主要内容:
◆ ADK 的功能特性与架构全景
◆ 为什么值得认真学:与 LangGraph 的对比
◆ 项目背景:一人公司内容运营,为什么需要专用 Agent
◆ Hello World 实战:依赖安装、配置机制、代码精讲、控制台与 Web 运行
本篇亮点:
◆ 逐行精讲了 article-agent/agent.go 的全部 52 行代码,每个关键点都对照 ADK v2.6.0 源码给出出处
◆ 详细分析了 OpenAI 协议模型接入层的参数映射(TopP → top_p、ThinkingLevel → reasoning_effort)
◆ 给出了"裸 web 命令启动失败"等三个资料里查不到的实战坑位
版本约定:除特别指出,本手册的 Go 版 ADK 即 google.golang.org/adk/v2(v2.6.0),Go 工具链为 1.26+,操作系统以 macOS / Linux 为准。
1.1 ADK 是什么:一套自包含的 Agent 开发全家桶
在动手写代码之前,我们先用一节的篇幅把 ADK 的功能地图铺开,后面每一步实操都能在这张地图上找到落点。
1.1.1 出身与定位
ADK 在 2025 年 4 月 9 日的 Google Cloud NEXT 大会上发布,Apache-2.0 协议开源。官方发布博客的定位是:"一个来自 Google 的开源框架,旨在简化 agent 与多 agent 系统的全栈端到端开发"(full stack end-to-end development)。更值得注意的是这句——"ADK 是驱动 Google 自家产品(如 Agentspace 和 Google Customer Engagement Suite)中 agent 的同一套框架"。也就是说,它不是实验室里为论文而生的原型,而是从 Google 内部大规模 agent 实践中提炼出来、又为外部开发者重新起笔的一套框架。
提示 ADK 与 A2A 协议(Agent2Agent,agent 之间互操作的标准协议)在 2025 年 4 月同期发布。A2A 于 2025 年 6 月捐赠给 Linux Foundation,2026 年 8 月转入其下的 Agentic AI Foundation(MCP 协议也是该基金会创始项目)。ADK 对 MCP 的支持则是发布第一天就有的。这两个"开放标准"是理解 ADK 生态位的背景板:Google 想做的是互联的 agent 生态,而不是一座孤岛。
1.1.2 核心理念:工作流与 Agent 树的统一编排
ADK 最有辨识度的架构决策,是把确定性工作流(deterministic workflow)和自主 agent(autonomous agent)统一在同一棵编排树里,而不是像多数框架那样二选一。
□ Agent 树:任何一个 agent 都可以挂 SubAgents,形成父子层级。LLM agent 之间可以按语义自动流转(transfer),父节点也可以是纯粹的结构节点。
□ Workflow agents:SequentialAgent(顺序)、ParallelAgent(并行)、LoopAgent(循环)三种确定性编排原语,它们自己不调用模型,只负责组织子 agent 的执行次序。
□ 图编排(ADK 2.0 新增):workflow 包提供显式的节点—边(Node/Edge)图编排,支持 agent 节点、函数节点、分支、汇合,兼顾确定性与灵活性。
为什么这个统一如此重要?
因为真实业务几乎总是两类逻辑的混合体。
以我们要做的内容平台为例:选题→写作→审稿是一条确定性流水线(顺序 + 不合格打回重写的循环),而每个环节内部又是自主的 LLM agent(它自己决定怎么写)。
在 ADK 里,这两种逻辑用同一套 agent 抽象表达;在"编排库 + agent 库"拼接的方案里,你得维护两套心智模型。
1.1.3 Go 版功能全景
ADK 家族目前官方支持五种语言(Python、Java、Go、TypeScript/JavaScript、Kotlin),Go 版仓库在 github.com/google/adk-go,模块路径 google.golang.org/adk/v2。图 1-1 是以 Go v2.6.0 源码为准的功能分层图。
launcher 启动器层(命令行入口,cmd/launcher)
├─ console ─ 终端 REPL 交互(无参数时的默认模式)
└─ web ─ HTTP 服务器(默认 127.0.0.1:8080)
├─ webui ADK Web UI 调试界面(/ui/)
├─ api ADK REST API(/api)
├─ a2a A2A 协议端点(/a2a/v1/invoke)
└─ pubsub / eventarc GCP 云事件触发端点
runner 运行时 + plugin 生命周期钩子(观察/拦截 run、model、tool 各阶段)
├─ agent 编排层
│ ├─ llmagent LLM 智能体(Instruction / 工具 / 回调 / 子 agent)
│ ├─ workflowagents 确定性工作流:Sequential / Parallel / Loop
│ ├─ workflow 图编排(节点 + 边,ADK 2.0 新增)
│ └─ tool 工具体系:functiontool / mcptoolset / geminitool / agenttool
└─ model 模型接入层
├─ gemini Gemini 原生接入
└─ openaimodel OpenAI 协议接入(官方 openai-go SDK),对接任意兼容端点
状态服务层
├─ session 会话与状态(InMemory / database-GORM / vertexai 三种实现)
├─ memory 跨会话长期记忆
└─ artifact 版本化文件存取
telemetry 可观测层(OpenTelemetry 全链路追踪,支持 OTLP 导出)
提示 五个语言版本的成熟度是有差异的:Python 版最完整(评测 evaluation 体系是内置能力),Go 版 2025 年 11 月才官宣、2026 年 6 月底 v2.0 GA,评测等能力仍在跟进中。选 Go 版意味着接受"更年轻",换来的是单二进制部署、无运行时依赖和编译期类型检查——对我们这个要长期演进的内容平台来说,这笔账是划算的。
1.2 为什么值得认真学
框架选型是双向奔赴,先说 ADK 值得投入的理由,再说它与最强劲的对手 LangGraph 的差异。
其一,Google 出品且更新活跃。 表 1-1 是截至 2026 年 10 月 27 日的官方仓库数据:主仓 adk-python 超过 2.2 万 star,发布节奏稳定在每一到两周一个版本;五条语言线在 18 个月里全部走完了"官宣→GA"的历程。对一个开源框架来说,这种投入强度本身就是可持续性的信号。
表 1-1 ADK 五语言版本现状(数据截至 2026-10-27,来自 GitHub API)
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
其二,站在巨人肩膀上,又没有历史包袱。 它把 Google 内部验证过的大规模 agent 工程经验(Agentspace、Customer Engagement Suite 同源)带了进来,但代码是 2025 年全新起笔的——没有十年前为"链式调用"设计的 API 遗产,架构干净得可以直接当教科书读。这一点笔者在读它的 launcher 源码时感受尤深:接口小、职责正交、注释诚实(连"这个 context 参数没用,只是为了签名对齐"都写在注释里)。
其三,理念优异:统一编排 + 全生命周期自包含。 与 LangGraph 的对比,社区流传一句总结得很准的话——"LangGraph accelerates experimentation; ADK industrializes it"(LangGraph 加速实验,ADK 把实验工业化)。展开说:
□ 定位不同:LangGraph 是底层编排库,强项是显式图状态机与细粒度控制,但调试要配 LangSmith、部署要配 LangGraph Platform,工具链是拼装出来的;
ADK 是应用开发框架,Dev UI 调试界面、事件追踪、会话服务、部署路径(Cloud Run / Agent Engine)开箱即用。
□ 编排表达力已趋同:ADK 2.0 引入 graph workflows 后,显式图编排这块 LangGraph 的传统强项也被补齐了,而"agent 树 + 工作流统一抽象"是 ADK 独有的整洁。
□ 不是零和关系:ADK 官方把 LangChain/LangGraph 列为互操作伙伴(可以作为 ADK 的组件嵌入),格局上是开放生态。
我的判断:说 ADK 完全不输 LangGraph,在工程完整性上成立,在社区生态的厚度上暂时还不成立——但方向和速度都对。对一个 2026 年才入场的新项目来说,选一个上升期的框架,比押一个已经定型的框架更有想象空间。
轻松一刻 "Hello, World!" 这个传统始于 1978 年 Kernighan 和 Ritchie 的《The C Programming Language》——在此之前,1972 年 Kernighan 写 B 语言教程时用的还是 "hello, world"(小写 w)。半个世纪里,每一代技术的入门仪式都是向机器打一声招呼、等它原样回应。到了 agent 时代,入门仪式悄悄变了味:你打一声招呼,它不光回应,还要思考一下再回应,并且记得你上一句说了什么——本篇稍后控制台里那段"思考过程"的输出,就是新时代的 hello, world。
1.3 我们要用它做什么:一人公司的内容运营产线
交代一下贯穿本系列的项目背景。笔者在经营一家一人公司(OPC,One-Person Company),自媒体内容是核心经营动作之一,这个平台就是用 ADK 把内容生产工程化的载体。
第一个 agent 是 articleAgent——一个"爆款文案工坊",负责标题、文案、内容生成。它是本篇的主角,也是未来整棵 agent 树的第一片叶子:
内容运营平台(未来的根编排,SequentialAgent 顺序流水线)
├── 选题 agent(LlmAgent:洞察热点,产出选题卡)
├── 写作 agent(LlmAgent:articleAgent,按风格产出初稿)★ 本篇主角
└── 审稿 agent(LlmAgent:按清单逐项检查,不合格打回)
└── 打回 ──→ 回到写作 agent(LoopAgent 的循环语义)
1.3.1 通用 Agent 这么强了,为什么还要自己开发?
这是被问得最多的问题,值得认真回答。现在 Claude Code、ZCode 这类通用编码 agent 已经非常强,而且它们本身就能写文章,为什么还要用框架自己开发专用 agent?因为通用 agent 的设计目标是最大化灵活性,而高频内容生产需要的是最大化工序的稳定性。具体来说,痛点集中在三组:
第一组:控制权缺失。 通用 agent 不开放模型生成参数——top_p、温度这些恰恰是内容风格的物理旋钮:爆款文案要的就是高温多样,审稿要的就是低温严格。系统提示词同样不可定制,而系统提示词是内容平台最核心的资产——账号的语气、人设、禁忌词全在里面。这两样在 ADK 里各是一行配置的事(1.4.4 节代码清单 1-5 的④⑤两处),在通用 agent 里却无处安放。
第二组:效果不可优化。 内容是拿来投放市场的,写得好不好要用数据说话,这就要求每次改动都可归因、可对照。基于 skill(技能文件)组织的业务流程,同一个任务两次执行可能走出不同的路径,提示词改一个字的效果也无法和上周的版本对比——不可控的东西就不可优化。多 agent 协作更是如此:通用 agent 自己临时组的"团队"每次都不一样,编排水涨船落,质量全凭运气。专用平台则把流程固化在代码里(编排结构是编译期确定的),把风格固化在配置里(1.4.2 节),每一版提示词、每一个参数都对应一次可复盘的运行。
第三组:成本不可承受。 通用 agent 的 token 账单,大头不在任务本身,而在每笔任务都要背的固定开销:庞大的系统提示词、成套的工具与技能定义、agent 循环自身的管理上下文——哪怕只是起一个文章标题,这些"基础设施"也要先原样支付一遍。探索性任务付这笔钱物有所值,可内容生产是日常高频动作,固定开销乘以频次,账单就难看了。
专用 agent 反其道而行,可以做到极简:一段几句话的系统提示词、零工具冗余、单次精简请求,本篇的 articleAgent 一轮交互的有效负载只有通用 agent 的零头;哪个环节用哪个模型、什么参数,全部自己说了算。
通用 agent 依然是探索性任务的正确工具。但"稳定的产线"它给不了,这活儿该交给 ADK 这样的框架。定位分工,各得其所。
1.4 Hello World 实战:让 articleAgent 跑起来
理论铺垫到此,下面开工。本节的目标:从空目录开始,把 articleAgent agent 在终端和浏览器里跑起来,并且让它记住你说过的话(会话持久化)。
1.4.1 环境准备与依赖安装
环境假设:Go 1.26+(笔者本地 1.27.3),项目模块名 opc-platform。先看工程全貌:
jingyuan-opc-agents-platform/
├── article-agent/
│ └── agent.go # Agent 本体:组模型、组 agent、启动
├── config/
│ └── config.go # 配置装载:config.toml → 结构体
├── runner/
│ └── session.go # 会话服务:SQLite 持久化
├── data/
│ └── adk.db # 会话库(首次运行自动生成)
├── config.toml # 配置文件(含密钥,勿入库)
└── go.mod
初始化模块并安装直接依赖,如代码清单 1-1 所示。
代码清单 1-1 终端命令:初始化模块并安装依赖
go mod init opc-platform
go get google.golang.org/adk/v2@v2.6.0 # ADK 框架本体
go get google.golang.org/genai@v1.76.0 # 生成参数协议类型(Temperature/TopP/Thinking…)
go get github.com/spf13/viper@v1.21.0 # 配置读取
go get github.com/glebarez/sqlite@v1.11.0 # 纯 Go 的 SQLite GORM 驱动
go get gorm.io/gorm@v1.31.2 # ORM(ADK database 会话服务的底座)
go get github.com/openai/openai-go/v3 # openai协议模型调用支持
这几样各司其职:adk/v2 是框架;genai 提供跨模型统一的生成参数协议(下文会看到它和 Gemini 生态共享一套类型);viper 负责配置装载与热更新;glebarez/sqlite + gorm 负责把会话落到本地文件。
提示
glebarez/sqlite是纯 Go 实现的 SQLite 驱动,不需要 CGO——这对交叉编译非常友好。常见的mattn/go-sqlite3功能更全但要 CGO,在容器里构建会多一层麻烦。另外,ADK 的 database 会话服务接受任意 GORM dialector,runner/session.go里换成gorm.io/driver/postgres就能平移到生产数据库,代码一行不用改(只换sqlite.Open(...)那一个参数)。
1.4.2 配置机制:config.toml 与 viper
我们希望"改提示词不重新编译",所以模型端点、系统提示词这些经常要调的东西全部外置到 config.toml,如代码清单 1-2 所示。
代码清单 1-2 config.toml:运行配置(密钥已脱敏)
[llm]
openApiKey='sk-xxx'
openApiBase='http://localhost:8000/v1'
model='Qwen3.8-27B'
[bigModel]
openApiKey='************.****'
openApiBase='https://open.bigmodel.cn/api/paas/v4'
model='glm-4.7-flash'
[runnerConfig]
sessionDbUrl="/Users/yourname/jingyuan-opc-agents-platform/data/adk.db"
[articleAgent]
systemPrompt="你是自媒体爆款文案写作达人,唯一的目标是获取自媒体平台用户的注意力,并且吸引用户点击、阅读、点赞、转发、收藏。始终用中文极简回复"
四个配置段两两成对:[llm] 是主力的本地 vLLM 端点(Qwen,OpenAI 兼容协议),[bigModel] 是云端智谱 GLM 端点——双端点为后续"不同环节路由到不同模型"留了口子(写作用本地大模型、分类打标用便宜的 flash 档)。[runnerConfig] 指定会话库路径,[articleAgent] 就是 articleAgent 的系统提示词,1.3 节说的"账号人设资产"落在这里。表 1-2 汇总了各配置项含义。
表 1-2 config.toml 配置项说明
|
|
|
|
|---|---|---|
llm.openApiKey
openApiBase
|
|
|
llm.model |
|
|
bigModel.* |
|
|
runnerConfig.sessionDbUrl |
|
|
articleAgent.systemPrompt |
|
|
读取这份配置的代码在 config/config.go,如代码清单 1-3 所示。
代码清单 1-3 config/config.go:viper 装载配置并注册热更新
package config
import (
"fmt"
"log"
"sync"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
// 读取配置文件config.toml
type RunnerConfig struct {
SessionDbUrl string
}
type Config struct {
RunnerConfig RunnerConfig `json:"runnerConfig"`
LLM `json:"llm"`
BigModel `json:"bigmodel"`
ArticleAgent `json:"articleAgent"`
}
type LLM struct {
OpenApiKey string
OpenApiBase string
Model string
}
type BigModel struct {
OpenApiKey string
OpenApiBase string
Model string
}
type ArticleAgent struct {
SystemPrompt string
}
func loadConfig() Config {
// 把配置文件读取到结构体上
var c Config
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath("$HOME/My-Agent-Space/jingyuan-opc-agents-platform")
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Fatal error config file: %s \n", err)
}
viper.Unmarshal(&config) //将最新配置文件重新绑定到全局config上
})
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Fatal error config file: %s \n", err)
}
viper.Unmarshal(&c) //将配置文件绑定到config上
viper.WatchConfig()
return c
}
func GetConfig() Config {
load := sync.OnceFunc(func() {
config = loadConfig()
})
load()
return config
}
var config Config
结构体定义与 TOML 的节一一对应,viper.Unmarshal 会把 [llm] 节的内容绑定到嵌入的 LLM 结构上。四个标注点:
用 SetConfigName + AddConfigPath 告诉 viper 去哪里找 config.toml;Unmarshal 完成配置到结构体的绑定;用 Go 1.21 的 sync.OnceFunc 把 GetConfig 做成懒加载单例——无论被多少个 goroutine 调用,磁盘只读一次。
实战经验 把系统提示词放进
config.toml而不是写死在代码里,是内容平台的第一条工程纪律。提示词是这个平台迭代最频繁的"生产资料"——今天试一个新人设、明天加一组禁忌词,如果每次都要重新编译部署,A/B 对比的节奏就被编译周期拖垮了。配置化之后,改提示词 = 改一行 TOML + 重启(未来可以做成免重启热加载),git 里还天然留下每一版提示词的演化史。
1.4.3 会话持久化:runner/session.go
ADK 的 launcher.Config 里若不传 SessionService,会话默认存在内存里——进程一退,对话记忆清零。内容运营的 agent 需要跨进程记住上下文,所以我们用 SQLite 把会话落盘,如代码清单 1-4 所示。
代码清单 1-4 runner/session.go:基于 GORM 的 SQLite 会话服务
package runner
import (
"log"
"os"
"time"
"opc-platform/config"
"github.com/glebarez/sqlite"
"google.golang.org/adk/v2/session"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"google.golang.org/adk/v2/session/database"
)
func GetSessionService() session.Service {
sessionService, err := newSessionService(config.GetConfig().RunnerConfig.SessionDbUrl)
if err != nil {
log.Fatal(err)
}
// 创建表并添加较新 ADK 版本所需的列。
// 每次应用程序启动时都要运行它。
if err := database.AutoMigrate(sessionService); err != nil {
log.Fatal(err)
}
return sessionService
}
// newSessionService 基于给定的 SQLite 文件创建 ADK 数据库会话服务。
// 任何 GORM dialector 都可用,例如 PostgreSQL 可使用 gorm.io/driver/postgres。
func newSessionService(dbUrl string) (session.Service, error) {
// GORM 默认 logger 会把 record not found 当错误打印(ADK 在
// app_states/user_states 无记录时按正常路径初始化新对象,见
// fetchStorageAppState/fetchStorageUserState),这里显式忽略该错误;
// 慢查询告警与真实 DB 错误仍然保留。
gormLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 1000 * time.Millisecond,
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: true,
Colorful: true,
},
)
return database.NewSessionService(sqlite.Open(dbUrl), &gorm.Config{Logger: gormLogger})
}
这个函数短小,但两处都值得停一下。database.NewSessionService 收的就是一个标准 GORM dialector,所以"换数据库"这件事的全部工作量就是换掉 sqlite.Open(...) 这一个实参——ADK 对存储的态度是把选择权留给业务方,自己只依赖 session.Service 接口(Create/Get/List/Delete/AppendEvent 五个方法)。AutoMigrate 负责建表和补列,源码注释明确说它可重复执行、只增不删——所以惯例是每次启动都调用,框架升级加了字段也能自动补齐,不需要手工管理迁移脚本。
运行之后,data/adk.db 里会出现四张表:sessions(会话元信息)、events(事件流,每轮对话的完整记录)、app_states 与 user_states(应用级/用户级状态),1.4.5 节会实际查验。
1.4.4 Agent 本体:agent.go 逐行精讲
主角登场。article-agent/agent.go 全文 52 行,是本篇的 Hello World,也是以后每个更复杂 agent 的骨架,如代码清单 1-5 所示。
代码清单 1-5 article-agent/agent.go:articleAgent 的完整定义与启动
package main
import (
"context"
"log"
"opc-platform/config"
"opc-platform/runner"
"os"
"google.golang.org/adk/v2/agent"
"google.golang.org/adk/v2/agent/llmagent"
"google.golang.org/adk/v2/cmd/launcher"
"google.golang.org/adk/v2/cmd/launcher/full"
"google.golang.org/adk/v2/model/openaimodel"
"google.golang.org/genai"
)
func main() {
//加载统一配置
conf := config.GetConfig()
ctx := context.Background()
// 实例化openai 协议的模型
model, err := openaimodel.NewModel(context.Background(), conf.LLM.Model,
&openaimodel.ClientConfig{
//从配置中设置 api key和 api baseurl
APIKey: conf.LLM.OpenApiKey,
BaseURL: conf.LLM.OpenApiBase,
//注意,默认是走openai的 response api协议,这里设置成 chat completions api
API: openaimodel.APIChatCompletions,
},
)
if err != nil {
log.Fatal(err)
}
var topP float32 = 0.95
//创建 agent
articleAgent, err := llmagent.New(llmagent.Config{
Name: "articleAgent",
Model: model,
GenerateContentConfig: &genai.GenerateContentConfig{TopP: &topP, ThinkingConfig: &genai.ThinkingConfig{ThinkingLevel: genai.ThinkingLevelLow}},
Description: "负责爆款内容、文案、标题生成",
Instruction: conf.ArticleAgent.SystemPrompt,
})
if err != nil {
log.Fatalf("创建智能体失败: %v", err)
}
//agent 运行配置:会话怎么存,记忆怎么存等等
config := &launcher.Config{
//SessionService不传的话默认是在内存中记录会话,这里我们使用 SQLite 存储会话状态
SessionService: runner.GetSessionService(),
AgentLoader: agent.NewSingleLoader(articleAgent),
}
//agent 运行工具,支持直接通过 cli、web、api 调用 我们前面的agent
l := full.NewLauncher()
if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
log.Fatalf("运行失败: %v\n\n%s", err, l.CommandLineSyntax())
}
}
1.4.5 运行起来:控制台与 Web 两种模式
代码就绪,ADK 给了两种开箱即用的运行形态。进入到 article-agent目录:
cd article-agent
1.控制台模式(console)
最简单的启动方式——不带任何参数,默认进 console:
go run agent.go
2.Web 模式(web + 子服务)
Web 模式把 agent 暴露成 HTTP 服务,自带调试 UI。启动命令:
go run agent.go web -port 8098 webui api
实战经验 这是本篇最大的坑:裸写
web不带子服务关键词,启动会直接失败——no active sublaunchers found - please specify them in the command line. Possible values: [webui a2a pubsub eventarc api]。这与 Python 版adk web一条命令全包的行为不同,Go 版要求显式声明要起哪些子服务。日常开发记住webui api这对组合即可。
启动成功后控制台输出:
2026/10/27 16:23:22 No artifact service configured. Using an in-memory one, so whatever it holds is lost when the process exits.
2026/10/27 16:23:22 No memory service configured. Using an in-memory one, so whatever it holds is lost when the process exits.
2026/10/27 16:23:22 Web servers starts on http://localhost:8098
2026/10/27 16:23:22 webui: you can access API using http://localhost:8098/ui/
2026/10/27 16:23:22 api: you can access API using http://localhost:8098/api
2026/10/27 16:23:22 api: for instance: http://localhost:8098/api/list-apps
2026/10/27 16:23:22 api: debug excluded: for the web UI's Traces and agent structure panels, use -include_debug_api
开头两行是 1.4.4 节⑥处说过的"降级提醒":未配置的 artifact/memory 服务退化为内存实现。浏览器打开 http://localhost:8098/ui/ 就是 ADK Web UI——可以选 agent、开会话、看事件流的调试界面(想让 Traces 和 agent 结构图面板亮起来,启动时加 -include_debug_api)。
REST API 同步可用。注册的 agent 列表:
$ curl -s http://127.0.0.1:8098/api/list-apps
["articleAgent"]
返回的正是代码清单 1-5 里③处的 Name——单 agent 装载器把它注册成了应用名。再查会话列表,之前 console 模式跑出来的会话赫然在列:
$ curl -s http://127.0.0.1:8098/api/apps/console_app/users/console_user/sessions
[{"id":"10f1dec4-963e-4348-bcf4-9e0591b6f62d","appName":"console_app","userId":"console_user",
"lastUpdateTime":1793089324,"events":[],"state":{}}, …]
3.会话落库验证
最后验一下 1.4.3 节的承诺。Web 模式是独立进程,它却能列出 console 进程写入的会话——说明会话确实落在 SQLite 里跨进程共享了。直接查库:
$ sqlite3 data/adk.db .tables
app_states events sessions user_states
四张表与 1.4.3 节所述一致。至此,Hello World 全链路闭环:配置装载 → OpenAI 协议接入本地模型 → agent 定义 → 会话持久化 → 双模式运行。
提示 web 模式默认只绑定
127.0.0.1(loopback),不暴露到网络——这是刻意的安全默认值。要在局域网访问需显式-host 0.0.0.0,但请注意这些端点默认无认证,公网部署前必须配好launcher.Config里的Authenticator/Authorizer与-allow_origins。另外config.toml含真实密钥,务必加进.gitignore再做 git 提交。
1.5 小结
本篇介绍了 ADK 的出身与功能全景——一套把开发、调试、部署、观测装进同一个工具箱、以"agent 树与工作流统一编排"为核心抽象的框架;同时介绍了它值得认真学的理由:Google 出品、更新活跃、架构干净、五语言并进,与 LangGraph 相比胜在工程完整性与自包含。我们还交代了贯穿系列的项目背景:一人公司的内容运营平台,以及为什么高频内容生产需要专用 agent 而非通用 agent——控制权、可优化性、成本三组理由。
最后以 articleAgent 为例,完整走通了 Hello World:六项依赖、viper 配置机制、SQLite 会话持久化、53 行 agent.go 的逐行精讲,以及 console 与 web 两种运行模式的实测。
第一个 agent 已经会写文案、会记事了,但它还是"光杆司令":没有工具(不能查资料、不能读文件)、没有同伴(孤 agent 谈不上编排)、没有记忆(换个会话就不认识你)。
后续我将持续完善,待你抽丝剥茧,敬请期待。

