✦ 大道至简 · 时光是画在卷上的河流 · 行到水穷处,坐看云起时

OSS2026.09.06 · 24 分钟阅读

对象存储,AI Agent 文件的存储底座, OSS

AI Agent 运行时会持续产生、读取大量文件——用户上传的 PDF、程序生成的图表、多模态的音视频,普通本地文件夹根本扛不住高并发读写。这篇先讲清「向量库、关系库、对象存储」三个库在 RAG 里各管什么,再带你用 30 秒拉起本地私有化存储,学会用 @aws-sdk/client-s3 一套 SDK 对接阿里云 OSS、MinIO、RustFS,最后讲透从文件上传到溯源展示的完整闭环,适合所有在搭知识库或多模态 Agent 的开发者

L

Leo

2026.09.06 · 更新于 2026.09.10

2 次浏览
对象存储,AI Agent 文件的存储底座, OSS

对象存储,AI Agent 文件的存储底座:RAG 溯源到多模态, S3 与三种落地方案

为什么写这篇:Agent 不落地文件,知识就永远是「看过就忘」

先说一个 Agent 开发里逃不掉的现象:AI Agent 运行起来,就是在不停地生产文件和消费文件。

  • 用户往知识库里丢一份 PDF,系统要读它、切它、回溯源时还要再读一次;
  • 你的 Agent 跑个数据分析,程序自动生成一张图表要存下来;
  • 语音 Agent 合成一段回答,生成的音频要给前端播放;
  • 多模态 Agent 更是图片、视频一起上。

这些文件有大有小,但有一个共同点:它们不是几行文字,是实实在在的大体积二进制数据。问题来了——把文件存哪?

大多数新手的第一反应是「放本地文件夹」。但本地文件夹有三个硬伤:

  • 并发扛不住:海量文件被多个进程同时读写时,本地磁盘很快就成了性能瓶颈;
  • 容量没有上限:本地磁盘是固定的,而 Agent 产出的文件是无上限的;
  • 水平扩展难:服务一旦横向扩到多台机器,A 机写的文件 B 机根本读不到。

所以做 AI 知识库、多模态 Agent 项目,必须上对象存储。它才是支撑各种 Agent 文件读写的底层地基。这篇的目的,就是帮你把「为什么非得是对象存储、对象存储怎么选、代码怎么写」一次讲透。

先说结论:

AI Agent 的文件存储,正确姿势是「三个库各管一段」:原始大文件 → 对象存储长期归档;文件元数据 → 关系库;文本切片 → 向量库。只有对象存储能统一承载图片、PDF、音视频这类大容量二进制素材。


先看全景:RAG 里三个「库」到底谁管什么

新手最容易犯的错,是以为一个数据库就能搞定一切。实际上一条 RAG 链路走下来,至少涉及三个存储,它们分工完全不同:

存储存什么拿什么找为什么是它
向量库(Milvus 等)文本切片的向量语义相似度只擅长「按意思找」,存不了大文件
关系库(PostgreSQL 等)文件元数据:文件名、来源、切片位置SQL、主键、JOIN擅长结构化记录,但不存二进制内容
对象存储(OSS/MinIO/RustFS)图片、PDF、音视频等原始大文件对象 Key(路径)唯一能统一承载大体积素材的底座

把这三个库串起来,就是一条完整的 RAG 链路:

把这条链路拆开看,对象存储出现在两个关键节点上:

  1. 入库时:解析完的原始文件完整存入对象存储,长期归档不动——这是所有溯源工作的前提;
  2. 回答时:用户提问,向量库返回匹配片段并带上文件 ID,程序拿 ID 去关系库读文件基础信息,再通过对象存储拉回完整原始文档做溯源展示。

为什么向量库和数据库都顶不上这个位置?一句话:向量库只存向量、数据库只存文字信息,它们都存不了大体积二进制文件。 只有对象存储能同时承载图片、PDF、音视频,所以在多模态时代它几乎是唯一解。


先搞懂对象存储本身:Bucket、Key、预签名 URL

动手对接前,先花两分钟把对象存储的几个核心概念建立起来——它们和你熟悉的「文件夹」很不一样。

对象存储不是文件系统

普通文件系统是「目录树 + 文件」:一层层文件夹套下去,可以原地改文件内容。对象存储是扁平的——里面只有一个个「对象」(Object),每个对象就是一个文件加上它的元数据。

那为什么我们看到的对象 Key 都长得像路径,比如 docs/meeting/2026-09/raw.pdf?因为对象存储允许你用 / 模拟出层级关系,方便人看,但它本质是一个超大的 KV 存储:Key 是地址,Value 是文件二进制。这也是它天然适合海量并发的根本原因——读写一个对象和读写十万个对象没有本质区别。

三个必须记住的概念:

  • Bucket(桶):最顶层的命名空间,相当于「一个仓库」。通常一个项目/一个用途开一个桶,桶名全局唯一;
  • Object Key(对象名):桶内每个对象的唯一地址,用 / 模拟目录层级,例如 uploads/meeting-1/source.pdf;
  • 预签名 URL(Presigned URL):给文件生成一个带时效的临时下载链接。因为对象默认是私有的,你不想把文件设为公开读,就靠它把「某段时间内可下载」的权限临时交出去。

要点:Key 即路径,存的时候随手把逻辑路径组织好,溯源时会省很多事;Bucket 是权限边界,公开桶和私有桶要分清;私有桶 + 预签名 URL 才是正规做法,别为图省事把所有文件设成公开读。

为什么不是 MySQL 存大字段、也不是本地文件

有人会问:那把文件塞进 MySQL 的 BLOB 字段不就行了?能存,但不该存:数据库是为结构化记录优化的,往里塞几 MB 的二进制会把整张表拖慢,备份、扩容都跟着遭殃。文件就该交给专门管文件的存储,数据库只留一行元数据记着「文件在哪」。

而对象存储里的文件不能像文件夹那样「原地改一行」,它是整存整取的——上传即覆盖、整体读写。这个特性对「原始素材只读、需要追溯」的 RAG 场景反而刚刚好。


三类主流方案怎么选:阿里云 OSS / MinIO / RustFS

方案层面,现在主流是这三类:阿里云 OSS、MinIO、RustFS。它们底层全部遵循 S3 标准协议,核心读写能力基本一致——真正拉开差距的是「谁来维护」和「商用约束」。

维度阿里云 OSSMinIORustFS
形态公有云托管服务可 Docker 私有化部署专为私有化海量文件场景打造
谁来维护阿里云运维,按量自动扩容自己部署自己管自己部署自己管
典型场景线上 SaaS,不想投入运维人力的团队本地测试、小型知识库多模态国产化、海量文件私有化项目
可视化后台完善的控制台新版社区版已阉割可视化管理自带 Web 控制台
商用版权商业授权,无风险AGPL 开源协议,商用有版权风险商用无约束
底层技术阿里云自研Go 语言Rust 底层,内存占用低、并发稳定

选型用一句话粗暴概括:要省运维就上云 OSS,要私有部署就二选一——图省事图免费选 MinIO,图商用无风险、图海量并发选 RustFS。

具体到你该选哪个,可以顺着这张决策图走:

要点:OSS 的优势是免运维 + 按量自动扩容,代价是数据在云上、有成本;MinIO 的优势是上手最快,社区版有 AGPL 商用风险且新版阉割了可视化管理;RustFS 的优势是 Rust 底层内存占用低、并发稳定、商用无约束,适合国产化和海量文件场景。


上手第一步:30 秒在本地拉起一个私有化存储

选型定了,先别管代码,先把存储跑起来。以 RustFS 为例,一个 docker-compose.yml 就够,用的是 S3 协议、Web 控制台、日志三件套齐全的最小配置:

# docker compose up -d  一条命令拉起一个 S3 兼容对象存储
services:
  rustfs:
    image: rustfs/rustfs:latest
    container_name: rustfs-server
    restart: always
    ports:
      - "9000:9000"   # S3 API 端口(程序对接用)
      - "9001:9001"   # Web 控制台端口(浏览器用)
    environment:
      # S3 接口 / 控制台登录账号密钥
      RUSTFS_ACCESS_KEY: admin
      RUSTFS_SECRET_KEY: Admin@123456
      # 开启 Web 可视化管理控制台
      RUSTFS_CONSOLE_ENABLE: "true"
    volumes:
      # 数据持久化到本地文件夹
      - ./rustfs-data:/data
      - ./rustfs-logs:/logs
    command: server /data

启动后浏览器打开 http://localhost:9001 就能看到管理后台。想先试 MinIO 也简单,把镜像换成 minio/minio 即可——端口、账号体系、S3 接口全一样,这也是「都遵循 S3 协议」带来的好处。

要点:9000 是程序连的 API 口,9001 是给人看的控制台口,别搞混;RustFS/MinIO 都认一套 AccessKey + SecretKey;本地测试选哪个都行,上生产前先过一遍商用版权那关。


接入第二步:一套 SDK 对接所有 S3 服务

存储起来了,就该写代码了。好消息是:核心能力都遵循 S3 协议,所以对接方式高度统一。 安装 @aws-sdk/client-s3 这一个 AWS 官方的包,就能对接 OSS/MinIO/RustFS 所有 S3 兼容服务;当然也可以用 ali-oss、minio 各自的 SDK,更贴合厂商习惯。

姿势一:统一用 @aws-sdk/client-s3(RustFS / MinIO 通吃)

这是最推荐的姿势——一份代码,换环境变量就能切换存储。核心就三件事:配 endpoint、开 forcePathStyle、发 PutObjectCommand。

// npm i @aws-sdk/client-s3 dotenv
import 'dotenv/config';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import fs from 'fs';

// 一套 SDK,私有化部署的 RustFS / MinIO 通吃
const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,     // 例如 http://localhost:9000
  region: 'auto',                        // 本地私有存储随便填,不影响
  forcePathStyle: true,                  // 私有化存储必须 true
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  },
});

// 流式上传:大文件用 fs 流,内存不爆炸
async function upload(key, filePath, contentType) {
  await s3.send(new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,                            // 对象名,用 / 组织层级
    Body: fs.createReadStream(filePath), // 读文件为流
    ContentType: contentType,            // 图片 / PDF / 音频 各自类型
  }));
  console.log('上传完成:', key);
}

// 例:归档一份用户上传的会议纪要 PDF
await upload('docs/meeting/2026-09/raw.pdf', './raw.pdf', 'application/pdf');

要点:forcePathStyle: true 是本地私有存储的关键开关,不开会连不上;Key 就是对象地址,存的时候把业务路径编好;Body 传流而不是整个 Buffer,大文件也能低内存上传;别把密钥硬编码,走环境变量。

姿势二:用 ali-oss 官方 SDK(阿里云 OSS 专用)

如果选了阿里云 OSS,用官方 ali-oss SDK 最顺手——它替你封装好了 OSS 的签名、分片、断点续传等云上能力。

// npm i ali-oss
import OSS from 'ali-oss';
import fs from 'fs';

const client = new OSS({
  region: 'oss-cn-hangzhou',            // Bucket 所在地域
  accessKeyId: process.env.OSS_ACCESS_KEY_ID,
  accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
  bucket: 'my-agent-bucket',            // 桶名,创建时全局唯一
});

async function upload() {
  // putStream 流式上传,返回结果里带着可访问地址
  const res = await client.putStream(
    'docs/meeting/2026-09/raw.pdf',   // 对象名(路径)
    fs.createReadStream('./raw.pdf')
  );
  console.log('上传完成:', res.url);
}

要点:region 必须是 Bucket 真实所在地域,写错连不上;桶名全局唯一,创建后不好改;官方 SDK 对云上复杂场景(分片、续传、防盗链)支持最全,线上 OSS 优先用它。

姿势三:用 minio SDK(MinIO 专用)

想用 MinIO 时,官方 minio SDK 也有一套几乎一样的写法,只是域名格式稍有差异(不能带 http://)。

// npm i minio
import Minio from 'minio';
import fs from 'fs';

const client = new Minio.Client({
  endPoint: 'localhost',      // 注意:不带协议前缀
  port: 9000,
  useSSL: false,
  accessKey: process.env.MINIO_ACCESS_KEY,
  secretKey: process.env.MINIO_SECRET_KEY,
});

async function upload() {
  // putObject:桶 + 对象名 + 文件流
  await client.putObject('my-bucket', 'docs/meeting/2026-09/raw.pdf',
    fs.createReadStream('./raw.pdf'));
  console.log('上传完成');
}

三种姿势放在一起看,会发现骨架完全一样:都是「配账号密钥 → 指到桶 → 把流塞给某个对象路径」。差异只在每个 SDK 的字段命名上——这正是「底层全是 S3 协议」带给开发者的红利:学一次,处处用。


溯源闭环:怎么把文件再「拿回来」

RAG 里对象存储的另一半使命,是回答时的溯源展示——向量库给了你「这段话来自某份文件」,你得有能力把那份完整原文拉出来。这一步分两种诉求,各有各的取法。

场景一:程序内部要读文件内容(Agent 继续处理)

程序拿到 Key 后用 GetObjectCommand 直接拉流,比如把原始 PDF 交给下一个处理环节:

import { GetObjectCommand } from '@aws-sdk/client-s3';

async function download(key) {
  const { Body } = await s3.send(new GetObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
  }));
  // Body 是流,可以直接写盘、转 Buffer、或再喂给下游处理
  return Body;
}

场景二:要给前端做展示 / 下载(溯源查看原始文档)

前端不能直接用 AccessKey 去拉文件。正规做法是生成一个几分钟内有效的预签名 URL,把它交给前端去展示——文件本身保持私有,权限却按需放出:

// 统一 SDK 生成预签名 URL
// npm i @aws-sdk/s3-request-presigner
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const url = await getSignedUrl(s3,
  new GetObjectCommand({ Bucket: process.env.S3_BUCKET, Key }),
  { expiresIn: 300 }   // 300 秒 = 5 分钟后过期
);
console.log('临时下载链接:', url);

如果用阿里云 OSS,官方 SDK 一句 signatureUrl 就搞定,原理一样:

// ali-oss 生成预签名 URL,expires 单位是秒
const url = client.signatureUrl('docs/meeting/2026-09/raw.pdf', { expires: 300 });

要点:程序内处理用 GetObjectCommand 拉流,给用户展示用预签名 URL,两种场景别混;预签名 URL 自带过期时间,是私有桶对外提供临时读权限的标准姿势;拿到的 URL 有隐私风险,别把它永久存进数据库,要用时再生成。


结尾总结:一张表想清楚「怎么选、怎么放」

兜了一大圈,其实你只需要记住两件事:文件放对象存储、元数据放关系库、切片向量放向量库;对象存储选哪个,看你的「运维投入」和「商用约束」。

你的处境建议方案一句话理由
线上 SaaS,不想管服务器阿里云 OSS免运维、按量扩容,只操心业务
本地开发测试、小知识库MinIODocker 一条命令起来,够用
私有化、商用合规、海量并发RustFSRust 底层内存低、并发稳,无版权负担
多模态国产化项目RustFS私有化 + 商用无约束,最省心

存储落位则始终是这套铁三角,缺一不可:

对象存储不是什么高深的技术,它只是把「文件放哪儿」这件事,从脆弱的本地磁盘,搬到了一个专为海量并发而生的底座上。理解了它和向量库、关系库的各自分工,再面对 RAG 溯源、多模态素材归档,你就不会再纠结「文件到底该塞进哪个库」——大文件永远先问一句:对象存储安排上了吗?

标签 / TAGSOSS
L

Leo

博主

独立开发者 / Blogger,原博客「大道至简」维护者。正在把 WordPress 上攒了几年的文章与拾语迁移到 Next.js。

读者留言

COMMENTS · 0

发表留言

评论经审核后展示 · 请友善发言0/100