Agno 会话存储接入 Google Cloud Storage:基于 GcsJsonDb 的 JSON Blob 持久化完整实战指南
导读
本指南聚焦于 agno(Agno Agent 框架)中的一个具体问题:如何把 Agent 的会话(session)与运行(run)数据持久化到 Google Cloud Storage(GCS)。Agno 官方在 cookbook/06_storage/gcs 目录下提供了完整的接入示例,其核心是把 GCS 当做一个"JSON 文件仓库"——每个逻辑表对应桶里的一个 JSON Blob,通过 GcsJsonDb 完成读写。读完本文你将掌握:GcsJsonDb 的完整配置与全部构造参数含义、gcloud 认证与 IAM 权限配置、如何用 UUID 生成隔离桶名并跨 Agent 延续同一会话,以及在无 GCS 账号时如何用 Docker 启动 fake-gcs-server 做本地联调。
一、背景:存储抽象与 GcsJsonDb 的定位
Agno 的 Agent 在运行时会产生会话(session)、运行(run)、用户记忆(user memory)等状态数据。默认情况下这些数据仅存在于内存中,进程退出即丢失。为了让 Agent "记得上次聊到哪里",Agno 在 Agent 上暴露了 db 参数,接受一个实现统一接口的后端对象。
在 cookbook/06_storage 目录下,Agno 为同一套接口提供了多种实现:SQLite、Postgres、MySQL、MongoDB、Redis、DynamoDB,也包括纯 JSON 文件形态的 in_memory / json_db 后端。GCS 方案则是把这些 JSON 文件进一步搬到了云上:不再使用本地文件系统,而是把每一个逻辑表存储为 GCS 桶里的一个 JSON Blob(对象)。这样会话数据天然具备云端冗余、跨机器共享、无本地磁盘依赖的特性。
从源码实现看,GcsJsonDb 继承自 libs/agno/agno/db/base.py 中定义的 BaseDb,类定义于 libs/agno/agno/db/gcs_json/gcs_json_db.py#L44-L104。因此它对上层 Agent/Team/Workflow 暴露的能力与其他后端完全一致:会话的 upsert/查询/删除、run 记录管理、用户记忆(memory)的增删查、指标统计、知识库内容、trace/span 等。
关键概念:GcsJsonDb 与 SQL 后端的关键差异在于"没有表结构",因此其
table_exists()恒返回True(见 gcs_json_db.py)。它是用"一个 JSON 文件 + 全量读写"来模拟表语义的。
二、环境准备与依赖安装
官方 README 给出的最小安装命令:
uv pip install google-cloud-storage
如果使用官方示例脚本(涉及 Web 搜索工具与云端搜索),还需要一并安装:
uv pip install google-auth google-cloud-storage openai ddgs
其中:
google-cloud-storage:GcsJsonDb 底层依赖的官方 GCS 客户端。源码在 gcs_json_db.py#L38-L41 中通过try: from google.cloud import storage导入,一旦缺失会抛出明确提示google-cloud-storage not installed. Please install it with pip install google-cloud-storage;google-auth:用于在示例脚本中调用google.auth.default()读取当前环境默认凭据;openai:Agent 默认调用 OpenAI 模型所需的 SDK;ddgs:DuckDuckGo 搜索工具链的依赖。
三、最小接入配置
把 GCS 作为 Agent 会话后端的代码非常简短。官方 README 给出的最小配置如下:
from agno.agent import Agent
from agno.db.gcs_json import GcsJsonDb
db = GcsJsonDb(
bucket_name="your-bucket-name",
)
agent = Agent(
db=db,
add_history_to_context=True,
)
这段代码中:
GcsJsonDb(bucket_name=...)仅强制要求桶名,其余参数都有默认值(详见下文第四节);Agent(db=db)把会话与运行数据落盘到该桶;add_history_to_context=True是关键:让 Agent 在生成回复前把历史消息放回上下文,从而在多轮对话中"记得"前文。
从这里也可以看到整个 cookbook 的统一用法:db 可以传入任一存储后端,示例脚本的对应完整实现见 cookbook/06_storage/gcs/gcs_json_for_agent.py。
四、GcsJsonDb 构造参数全解析
将配置从"最小可用"扩展到"生产可用",需要理解每个参数的含义。对照构造函数源码 gcs_json_db.py#L44-L104 与基类 base.py#L330-L355,参数表如下:
| 参数 | 必填 | 默认值 | 作用 |
|---|---|---|---|
bucket_name |
✅ 是 | 无 | GCS 桶名,JSON Blob 的存放位置 |
prefix |
否 | "agno/" |
桶内对象路径前缀,用于组织文件;若结尾缺少 / 会自动补上 |
session_table |
否 | agno_sessions |
会话表对应 JSON 文件名(不含扩展名) |
runs_table |
否 | agno_runs(或 {session_table}_runs) |
run 记录文件,一次 run 一条记录 |
memory_table |
否 | agno_memories |
用户记忆(memory)文件 |
metrics_table |
否 | agno_metrics |
每日指标统计文件 |
eval_table |
否 | agno_eval_runs |
评估运行记录文件 |
knowledge_table |
否 | agno_knowledge |
知识库内容文件 |
traces_table |
否 | agno_traces |
追踪(trace)记录文件 |
spans_table |
否 | agno_spans |
span 记录文件 |
project |
否 | 无 | GCP 项目 ID;不传时使用客户端默认项目 |
credentials |
否 | 无 | GCP 凭据对象;不传时使用 ADC 默认凭据 |
id |
否 | 自动生成 | 数据库实例 ID,内部由 bucket_name + project + prefix 作为种子生成,通常无需手动指定 |
几个值得注意的实现细节:
- 表名 = 文件名:
_get_blob_name()(见 gcs_json_db.py#L110-L112)将文件名拼接为f"{prefix}{filename}.json",即每个逻辑表对应桶中一个形如agno/agno_sessions.json的 JSON 对象。 - 存储形态是"列表 JSON":
_write_json_file()(gcs_json_db.py#L153-L175)用json.dumps(data, indent=2, default=str)把整张"表"(一个行字典列表)序列化后写入 Blob,content-type 为application/json;新建文件时先写入空列表[]。 - 全量读改写模型:从源码可以看出,每次 upsert 都是"整文件读出 → 内存中增删改 → 整文件写回"。这在会话数据量级下简单可靠,但意味着 GcsJsonDb 的批量写入(
upsert_sessions、upsert_memories)不支持高效批量原语,源码会记录日志并退化为逐条 upsert(见 gcs_json_db.py#L761-L776)。 - 错误不静默:读写 Blob 时,除
NotFound(文件不存在)按空数据处理外,任何认证失败、网络错误、配额问题都会记录日志后向上抛出,避免"调用方误以为写入成功"。
五、认证(Authentication)
GCS 客户端需要有效凭据才能访问桶。官方 README 提供了两种标准方式:
# 方式一:使用 gcloud CLI 的应用默认凭据
gcloud auth application-default login
# 方式二:使用服务账号 JSON 文件的环境变量
export GOOGLE_APPLICATION_CREDENTIALS="path/to/service-account.json"
两种方式均通过 ADC(Application Default Credentials) 生效。GcsJsonDb 构造时若不显式传入 credentials,其内部的 gcs.Client(project=project, credentials=credentials)(gcs_json_db.py#L103)会自动读取环境中的默认凭据。
示例脚本 gcs_json_for_agent.py#L22-L34 演示的是另一种显式写法——用 google.auth.default() 一次性取回凭据与项目 ID,再显式传给 GcsJsonDb:
credentials, project_id = google.auth.default()
db = GcsJsonDb(
bucket_name=unique_bucket_name,
prefix="agent/",
project=project_id,
credentials=credentials,
)
六、IAM 权限(Permissions)
即使认证通过,账号也需要对目标桶具备读写权限。README 给出的 IAM 授权方式是为用户绑定 roles/storage.admin:
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="user:your-email@example.com" \
--role="roles/storage.admin"
实际落地时建议按最小权限原则收敛为 roles/storage.objectAdmin(读写对象)或 roles/storage.objectViewer(只读),并确认:
- 目标 GCS 桶已存在(GcsJsonDb 不会自动建桶,只读写桶内对象);
- 账号对该桶有
storage.objects.get/list/create/delete权限; - 若使用服务账号,需要保证其被授予相应角色的成员权限,并导出对应的 key 到
GOOGLE_APPLICATION_CREDENTIALS。
七、示例脚本深度拆解:多 Agent 会话延续 + 桶内容 Dump
cookbook/06_storage/gcs/gcs_json_for_agent.py 是该目录的核心示例,比 README 的最小片段更完整。它展示了三件事:生成唯一桶名、两个 Agent 共享同一会话、按开关导出桶内会话数据。
7.1 调试开关 DEBUG_MODE
脚本顶部定义了一个模块级全局变量:
DEBUG_MODE = False
README 明确说明:把它设为 True 后,脚本会在执行结束时打印桶内各会话的 memory 内容,方便你确认数据确实落到了 GCS。对应代码段(gcs_json_for_agent.py#L68-L74)为:
if DEBUG_MODE:
print(f"\nBucket {db.bucket_name} contents:")
sessions = db.get_sessions(session_type=SessionType.AGENT)
for session in sessions:
print(f"Session {session.session_id}:\n\t{session.memory}")
print("-" * 40)
db.get_sessions() 是 BaseDb 定义的标准查询入口,支持按 session_type(AGENT / TEAM / WORKFLOW,枚举定义于 libs/agno/agno/db/base.py)过滤,返回的每条 session 携带其 memory 字段。
7.2 用 UUID 生成彼此隔离的桶名
在多租户或测试场景,手动指定桶名容易冲突。脚本的做法是用一个基础名加 UUID4 前 12 位生成唯一桶名:
import uuid
base_bucket_name = "example-gcs-bucket"
unique_bucket_name = f"{base_bucket_name}-{uuid.uuid4().hex[:12]}"
这避免了多次运行脚本时反复读写同一个桶造成数据串扰,是沙箱/演示环境的好习惯。脚本同时以 prefix="agent/" 组织目录前缀,使会话相关 JSON 集中在 agent/ 前缀下。
7.3 跨 Agent 延续同一个会话
脚本先创建 agent1,用两个问题开启对话:
agent1 = Agent(
db=db,
tools=[WebSearchTools()],
add_history_to_context=True,
debug_mode=DEBUG_MODE,
)
agent1.print_response("How many people live in Canada?")
agent1.print_response("What is their national anthem called?")
随后创建 agent2,并显式传入 session_id=agent1.session_id:
agent2 = Agent(
db=db,
session_id=agent1.session_id,
tools=[WebSearchTools()],
add_history_to_context=True,
debug_mode=DEBUG_MODE,
)
agent2.print_response("What's the name of the country we discussed?")
agent2.print_response("What is that country's national sport?")
这正是本示例最有价值的部分:agent2 是一个全新的 Agent 对象,但因为指向同一个 session_id 且共享同一个 db,它能够基于 agent1 产生的历史上下文回答"we discussed"所指的国家。后续问题即使换一台机器、重启进程,只要桶内会话数据还在,就可以继续对话。这验证了会话持久化对"Agent 状态可恢复"的意义。
7.4 执行流程
gcloud init
gcloud auth application-default login
python gcs_json_for_agent.py
gcloud init 用于初始化项目配置,gcloud auth application-default login 建立本地 ADC。运行结束后,会话与 run 数据便持久化在桶中。
八、本地联调:用 fake-gcs-server 模拟 GCS
没有真实 GCS 账号或不想产生云成本时,README 推荐使用开源的 fake-gcs-server(一个兼容 GCS 对象存储 API 的本地模拟器,通过 Docker 运行)完成存储功能联调。
8.1 创建 docker-compose.yml
先在项目根目录创建 docker-compose.yml(README 原样内容):
version: '3.8'
services:
fake-gcs-server:
image: fsouza/fake-gcs-server:latest
ports:
- "4443:4443"
command: ["-scheme", "http", "-port", "4443", "-public-host", "localhost"]
volumes:
- ./fake-gcs-data:/data
要点说明:
- 本地模拟器以
http协议监听4443端口; -public-host localhost保证客户端访问地址解析正确;- 宿主机目录
./fake-gcs-data挂载到容器/data,模拟器的数据落在本地,便于检查/清理。
8.2 启动与指向模拟器
docker-compose up -d
启动后,通过环境变量 STORAGE_EMULATOR_HOST 让 google-cloud-storage 客户端把 API 调用全部指向本地端点:
export STORAGE_EMULATOR_HOST="http://localhost:4443"
python gcs_json_for_agent.py
README 特别提醒了两点使用体验:
- 使用 fake-gcs-server 时不会强制校验认证,无需配置真实凭据;
google-cloud-storage客户端会自动探测STORAGE_EMULATOR_HOST,因此上面的代码无需做任何改动即可切换到模拟环境——这正是存储层抽象带来的便利:同一份GcsJsonDb代码,真实环境读真实桶,测试环境读本地模拟桶。
小提示:示例脚本与 README 中的文件名略有差异(脚本实为
gcs_json_for_agent.py),运行时以目录内实际存在的脚本为准。
九、从源码看 GcsJsonDb 的读写可靠性设计
当把"数据库"交给一个对象存储服务时,可靠性取决于客户端的异常处理。GcsJsonDb 的读写实现值得借鉴:
- 读取路径(
_read_json_file,gcs_json_db.py#L114-L151):先download_as_bytes().decode("utf-8")再json.loads;捕获NotFound视为"文件尚不存在",若create_table_if_not_found=True则自动上传[]初始化;其余异常(权限、配额、网络)与 JSON 解析错误都会记录日志后原样抛出,注释中明确写道"一个被吞掉的空列表返回会让调用方误以为表为空"; - 写入路径(
_write_json_file,gcs_json_db.py#L153-L175):json.dumps(...)后upload_from_string,任何失败都会记录并抛出,绝不静默——否则调用方会误以为数据已经落盘; - 会话与 run 分离存储:会话记录与其 run 记录分别存放在不同 JSON 文件(
agno_sessions.json与agno_runs.json),查询会话时会从 run 文件回填该会话的历史 run 并与旧版runs字段合并(gcs_json_db.py#L631-L636),这也是删除会话时级联清理 run 记录的由来。
此外,GcsJsonDb 还提供了与基类一致的会话过滤、排序、分页能力(按 user_id、agent_id、时间戳过滤,sort_by/sort_order 排序,limit/page 分页),由于没有查询引擎,这些都在内存中完成;排序辅助逻辑集中在 libs/agno/agno/db/gcs_json/utils.py 中(含 updated_at 缺失时回退 created_at 的处理)。指标统计(calculate_metrics)也由 utils.py 提供的日期/会话数据聚合工具支撑。
十、生产使用建议与限制小结
综合官方文档与源码,将 GcsJsonDb 用于生产或团队项目时,建议关注以下事实:
- 桶需预先创建:GcsJsonDb 只负责桶内对象的读写,不负责建桶;
- 权限按最小化授予:认证上优先用服务账号 +
GOOGLE_APPLICATION_CREDENTIALS,权限上从roles/storage.objectViewer/objectAdmin起步,而非直接使用roles/storage.admin; - "全量读改写"的适用边界:每次 upsert 都会重写对应表的整个 JSON 文件,适合会话级数据量;高频、超大批量写入场景请优先考虑 SQL/NoSQL 类后端;
- 调试手段丰富:既可用
DEBUG_MODE=True在脚本内打印桶内会话与 memory,也可在本地用 fake-gcs-server 把整个读写链路跑通后再切换到真实桶; - 关联代码路径:本方案的实现位于 libs/agno/agno/db/gcs_json/gcs_json_db.py,辅助排序/指标函数位于 libs/agno/agno/db/gcs_json/utils.py,导出入口在 libs/agno/agno/db/gcs_json/init.py,示例与测试说明分别见 cookbook/06_storage/gcs/gcs_json_for_agent.py 与 cookbook/06_storage/gcs/TEST_LOG.md,可在此基础上扩展自己的多 Agent 共享会话、用户记忆或指标上报场景。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00