ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

agno 会话媒体怎么迁移到外部存储(S3/本地):media storage 为何是单向门与全量升级顺序

2026/9/12 23:46:40 拓冰建站 浏览量
agno 会话媒体怎么迁移到外部存储(S3/本地):media storage 为何是单向门与全量升级顺序 agno 会话媒体怎么迁移到外部存储S3/本地media storage 为何是单向门与全量升级顺序【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno如果你在 agno 中用 Agent 处理图片、音频、视频或文件并且把会话持久化到数据库就会遇到一个问题媒体字节直接存在会话行里数据库会越撑越大。agno 的 media storage 机制可以把媒体外置到 S3 兼容对象存储或本地文件系统数据库里只保留一个轻量的MediaReference。但这个开关不能随手打开cookbook/06_storage/README.md 明确写道开启 media storage 是一扇单向门one-way door必须先让整条部署链路上的所有节点跑上新版本再启用media_storage。本文按先理解为什么不能回滚 → 配置外置存储 → 运行验证 → 清理边界的顺序给出完整操作路径。为什么是单向门先看数据形状变化启用 media storage不会发生任何 schema 变更——没有新表、没有新列、没有迁移。但同一列里媒体的数据形状变了外置后的图片携带media_reference而不再携带content。问题出在旧版本代码的校验逻辑上。可以查看 libs/agno/agno/media/media.py当媒体数据里没有media_reference字段时旧版本根本不认识这个字段会检查url、filepath、content三者至少有一个否则抛出ValueError(One of url, filepath, or content must be provided)。一个外置后的媒体对象三个字段全空旧代码不会跳过它而是直接抛异常。后果按文档描述是一条外置后的行就能让get_sessions()在整个会话列表上失败包括升级之前写入的、内容完好的会话。两个方向的结论在 README 中是明确的新版本代码读旧行没有问题所以旧版本 → 新版本这个升级方向是安全的回滚方向不安全。一旦行里已经携带了引用media_reference退回旧版本构建会让这些媒体不可读直到你再换回新版本。这就是升级顺序的全部依据先把发布版本在所有地方all nodes / whole fleet上线然后再启用media_storage。不要先在一台机器上启用、观察几天、再推其他机器——先启用的节点一旦写入了外置行其他还跑旧代码的节点读会话列表就会整体报错。准备条件安装依赖按后端选择可选依赖命令来自 cookbook/06_storage/README.mduv pip install agno[s3] # S3boto3 aioboto3 uv pip install agno[gcs] # GCSgoogle-cloud-storage本文不涉及S3 后端还需要环境变量来自 06_media_storage_s3.py 文件头注释AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_REGIONMEDIA_S3_BUCKET设为你自己拥有的桶名。关于 bucket 归属有一个文档特别指出的陷阱如果你填了一个不属于你的桶每次上传都会失败offload 会静默回退到内联 base64 存进数据库——运行仍然成功所以这个失败很容易漏掉。README 的建议是ask for the bucket up front即事先确认桶归属而不是事后发现。本地文件系统后端LocalMediaStorage无需云凭证但 05_media_storage_local.py 的注释写明它面向开发用途生产环境用S3MediaStorage或GCSMediaStorage。配置外置存储并接入 Agent以下代码取自 06_media_storage_s3.py把os.getenv(MEDIA_S3_BUCKET)/os.getenv(AWS_REGION)换成你的实际值或保留环境变量方式即可按原样运行import os import httpx from agno.agent import Agent from agno.db.sqlite import SqliteDb from agno.media import Image from agno.media.storage import S3MediaStorage from agno.models.openai import OpenAIResponses IMAGE_URL https://thumbs.dreamstime.com/b/mountain-landscape-pieniny-national-park-foot-tatra-mountains-mountain-landscape-pieniny-national-park-437239881.jpg?w768 bucket os.getenv(MEDIA_S3_BUCKET) if not bucket: raise ValueError(MEDIA_S3_BUCKET must be set to an S3 bucket you own) storage S3MediaStorage( bucketbucket, regionos.getenv(AWS_REGION), # 未设置时回退到 AWS_DEFAULT_REGION 或 ~/.aws/config prefixagno/media/, presigned_url_expiry3600, # 1 小时 ) agent Agent( modelOpenAIResponses(idgpt-5.5), media_storagestorage, dbSqliteDb(db_filetmp/data.db), ) if __name__ __main__: # 先自己下载媒体字节media storage 才能把它 offload 到 S3 # mime_type 决定存储对象的文件扩展名和 Content-Type image_bytes httpx.get(IMAGE_URL, follow_redirectsTrue).content agent.print_response( What do you see in this image?, images[Image(contentimage_bytes, formatjpeg, mime_typeimage/jpeg)], )本地后端则是同一套接口换成LocalMediaStorage路径取自 05_media_storage_local.pystorage LocalMediaStorage(base_path./tmp/media_storage)URL-only 媒体的处理两个行为必须知道否则迁移并不完整默认跳过。只带url的媒体在 offload 阶段被直接跳过不下载、不存储。想让 URL 媒体也进对象存储构造存储对象时加persist_remote_urlsTrue它会先把 URL 媒体全部下载再存储storage_with_persist S3MediaStorage( bucketbucket, regionos.getenv(AWS_REGION), prefixagno/media/, presigned_url_expiry3600, persist_remote_urlsTrue, )数据库里不存预签名 URL它会过期只存MediaReference。多轮复用的机制见 07_media_storage_multiturn.py第 1 轮上传图片、数据库只留引用第 2 轮不再重新附带图片读取时引用被重新签名re-signed模型直接从 S3 拉取原对象字节不再经过数据库。该示例还有一处针对OpenAIResponses的必要配置模型要设storeFalseOpenAIResponses(idgpt-5.5, storeFalse)并把历史留在客户端add_history_to_contextTrue、固定session_id。文档解释否则会走previous_response_id链式调用第 2 轮根本不会发送图片。结果验证cookbook/06_storage/TEST_LOG.md 记录了各示例对真实桶/本地目录运行后的结果以下均为文档示例供你对照自己的运行结果06_media_storage_s3.py退出码 0agno/media/前缀下出现 2 个内容寻址对象示例中各 65129 字节与源哈希一致URL-only 媒体默认被跳过持久化的 run 行持有media_reference而不是 base64。07_media_storage_multiturn.py两轮都正确回答了关于同一张图的问题无 offload 失败告警。第 1 轮上传 1 个对象示例中 113255 字节对第 2 轮出站请求的抓包显示它携带 1 个input_image内容是新签名的 S3 预签名 URLbase64 为 0 字符、download()调用 0 次run 行保持在示例中的 2897 字节含media_reference、无 base64。05_media_storage_local.pycontent 媒体 offload 到./tmp/media_storage2 个文件URL-only 媒体默认跳过、开启persist_remote_urlsTrue后下载并存储。验证要点归纳为两条一是检查数据库里外置行的媒体是media_reference而非内联字节二是检查对象存储对应前缀下确实存在对象桶归属错误时对象数为 0 但运行不报错。清理边界delete_media 与孤儿对象开启外置存储后媒体的生命周期与 session 解耦而且方向是危险方向行里的引用是唯一记录哪个对象属于哪个 session的地方。如果先删行对象就再也找不回来了。09_media_storage_delete.py 展示了对应的正确做法——删除 session 时传delete_mediaTrue实现上会先读取行里的对象 key再清扫对象该 flag 同时存在于Agent、Team、Workflow的同步与异步接口上# 不带 flag行删了对象留下成为孤儿 agent.delete_session(session_idkeeps-media) # 带 flag先从行里读出 key再清扫对象 agent.delete_session(session_idsweeps-media, delete_mediaTrue)验证方式是按前缀列出对象并数 key示例代码用 boto3client boto3.client(s3, region_nameos.getenv(AWS_REGION)) listing client.list_objects_v2(Bucketbucket, PrefixPREFIX) keys sorted(obj[Key] for obj in listing.get(Contents, []))TEST_LOG 中该示例的实际结果文档示例两个 session 各上传 1 个对象删第一个不带 flag对象数仍为 2删第二个带delete_mediaTrue只清扫自己的对象剩 1 个——正是那次未带 flag 删除留下的孤儿示例会把它按 key 打印出来。也就是说未带 flag 删除 session 是既定行为而非 bug需要你自己兜底清理。升级与回滚的操作顺序把 README 的约束落成具体顺序发布新版本到所有节点——此时旧数据内联媒体被新代码读取没有任何问题因为新代码读旧行是安全的全量确认新版本在跑之后再给 Agent/Team/Workflow 配置media_storageS3、GCS 或本地启用后写入的每一条外置行都携带media_reference。此后不要退回旧版本构建旧代码读到这些行时校验抛错且会使get_sessions()对整个会话列表失败这些媒体要回到新版本才可读。需要留意的边界没有 schema 迁移意味着也没有数据库层面的新旧兼容开关回滚窗口只存在于尚未写入任何外置行之前一旦有行携带引用单向门即已落下。参考资料cookbook/06_storage/README.md —— media storage 说明、依赖安装与先全量升级再启用的约束原文cookbook/06_storage/06_media_storage_s3.py —— S3 外置与persist_remote_urlscookbook/06_storage/05_media_storage_local.py —— 本地文件系统后端开发用途cookbook/06_storage/07_media_storage_multiturn.py —— 多轮复用与引用重签名cookbook/06_storage/09_media_storage_delete.py ——delete_mediaTrue与孤儿对象cookbook/06_storage/TEST_LOG.md —— 各示例的运行结果记录libs/agno/agno/media/media.py —— 媒体字段校验逻辑【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考