欢迎光临
我们一直在努力

Agent 文件传递

1. 问题

目前 Agent 已经可以在工作目录中创建和处理文件,但这些文件都保存在后端服务器上,前端无法直接访问。

因此需要增加一个中间环节,把 Agent 生成的文件交给用户。

这里采用对象存储的方式:

Agent生成文件

工作目录

打包

MinIO

生成下载链接

前端下载

这样 Agent 仍然只负责处理自己的工作目录,文件交付由后续流程完成。


2. 对象存储

**对象存储(Object Storage)**主要用于保存图片、视频、压缩包等非结构化文件。

和普通文件系统相比,对象存储更适合通过程序进行文件上传和访问。可以简单理解成一个可以通过 API 操作的网盘。

本项目使用 MinIO。

MinIO 是一个开源对象存储服务,可以直接部署在自己的服务器上,并提供 Python SDK。

这里使用 MinIO 主要是因为项目本身可以本地部署,没有必要为了文件存储再依赖第三方云存储。


3. MinIO 部署

项目使用 Docker 部署 MinIO。

docker pull minio/minio

mkdir -p /usr/local/minio-data

docker run -p 9000:9000 -p 9190:9090 \\
–name minio \\
-d –restart=always \\
-e "MINIO_ROOT_USER=minio" \\
-e "MINIO_ROOT_PASSWORD=minio123" \\
-v /usr/local/minio-data:/data \\
minio/minio server /data –console-address ":9090"

启动后可以通过:

http://服务器IP:9190

进入 MinIO 管理页面。

如果是在本机部署,则使用:

http://localhost:9190

登录后即可看到 MinIO 的管理界面。


4. Python SDK

安装 MinIO 的 Python SDK:

pip install minio

最基本的操作就是连接 MinIO、创建 Bucket、上传文件以及生成下载链接。

from minio import Minio

client = Minio(
"localhost:9000",
access_key="minio",
secret_key="minio123",
secure=False
)

项目实际只需要用到其中两个操作:

上传文件
生成下载链接

下载链接使用 presigned_get_object() 生成。

它返回的是一个带有效期的 URL,用户拿到链接后可以直接下载文件,不需要知道 MinIO 的账号密码。


5. 文件传递方案

5.1 为什么需要打包

一个 Agent 任务不一定只生成一个文件。

例如:

工作目录/
├── result.docx
├── data.xlsx
├── image.png
└── output.pdf

如果逐个上传并生成链接,前端还需要处理多个文件。

因此这里直接将整个工作目录打包成一个 ZIP:

工作目录

output.zip

MinIO

一个下载链接

用户只需要下载一个文件即可。

5.2 为什么放到 Agent 结束之后处理

文件上传不属于具体工具的功能。

例如 create_file 工具只负责创建文件,如果让它同时负责上传,就会让工具本身承担文件交付的工作。

因此将这部分放到 FileMiddleware 中,在 Agent 执行完成后统一处理。


6. 开始实现

6.1 添加 ZIP 压缩方法

在 utils/doc_utils 下增加 zip_files.py。

使用 Python 自带的 shutil 直接压缩目录:

import os
import shutil

def compress_dir(folder_path):
"""
将目录压缩成 zip
"""
output_path = shutil.make_archive(
base_name=folder_path,
format="zip",
root_dir=os.path.dirname(folder_path),
base_dir=os.path.basename(folder_path)
)

return output_path

这里没有额外引入压缩库,项目目前的需求比较简单,标准库就够用了。


6.2 添加 MinIO 配置

MinIO 的地址和认证信息放到 .env 中:

HOST_IP=localhost
MINIO_PORT=9000
MINIO_ACCESS_KEY=minio
MINIO_SECRET_KEY=minio123

然后在配置文件中统一读取:

HOST_IP = os.getenv("HOST_IP")
MINIO_PORT = os.getenv("MINIO_PORT")

MINIO_ENDPOINT = f"{HOST_IP}:{MINIO_PORT}"

MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY")
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY")

这样代码中不需要直接写 MinIO 的连接信息,部署到其他环境时只需要修改环境变量。


6.3 封装 MinIO 连接

在 conn 下新建 minio_conn.py。

不直接在 Middleware 中操作 MinIO SDK,而是简单封装一层:

from minio import Minio
from base import configs as cfg

class MinioConn:

def __init__(self):
self.client = self.create_client()
self.bucket_name = "stores"
self._init_bucket()

def create_client(self):
return Minio(
cfg.MINIO_ENDPOINT,
access_key=cfg.MINIO_ACCESS_KEY,
secret_key=cfg.MINIO_SECRET_KEY,
secure=False
)

def _init_bucket(self):
if not self.client.bucket_exists(self.bucket_name):
self.client.make_bucket(self.bucket_name)

def upload_obj(self, object_name, file_path):
self.client.fput_object(
self.bucket_name,
object_name,
file_path
)

def gen_presigned_url(self, object_name):
return self.client.presigned_get_object(
self.bucket_name,
object_name
)

这里暂时只保留项目需要的两个方法:

upload_obj()
gen_presigned_url()

Bucket 在初始化时检查,不存在就自动创建。


7. 接入 FileMiddleware

MinIO 部分准备好之后,最后需要把它接到 Agent 的执行流程中。

首先在 FileMiddleware 初始化时创建 MinIO 连接:

class FileMiddleware(AgentMiddleware[CustomState]):

def __init__(self):
super().__init__()
self.mc = MinioConn()

然后利用 before_agent 和 aafter_agent 两个生命周期节点完成文件处理。

7.1 记录开始时间

在 before_agent 中记录当前任务的开始时间。

原因是工作目录可能本身就存在一些文件,不能每次 Agent 执行结束都把整个目录当成新文件。

因此需要知道:

Agent 开始执行之后,有没有产生新的文件。


7.2 Agent 执行结束后处理文件

主要逻辑放在 aafter_agent:

async def aafter_agent(self, state, runtime):

dir_path = rt.get_root_thread_dir()

# 判断任务期间是否产生新文件
if not await self._check_if_new_file(
dir_path,
state["start_work_time"]
):
return None

# 压缩目录
local_path = await asyncio.to_thread(
compress_dir,
dir_path
)

# 上传 MinIO
object_name = os.path.basename(local_path)

await asyncio.to_thread(
self.mc.upload_obj,
object_name,
local_path
)

# 生成下载链接
url = self.mc.gen_presigned_url(object_name)

return {
"messages": [
AIMessage(content="可通过如下地址下载:"),
AIMessage(content=url)
]
}

这里压缩和上传都属于比较明显的文件 I/O 操作,因此使用:

asyncio.to_thread()

执行,避免直接阻塞当前异步流程。

如果任务期间没有产生文件,则直接返回,不进行压缩和上传。


8. 测试

可以直接在 Agent Chat UI 中测试,例如:

做个 a.txt,随便写点什么

Agent 完成后,可以看到返回结果中多了一条下载链接。

整个过程实际上就是:

创建 a.txt

Agent 执行结束

检测到新文件

工作目录压缩成 ZIP

上传 MinIO

生成 Presigned URL

返回给前端

点击链接即可下载 Agent 生成的文件。


9. 修改后的目录

本次主要涉及以下几个文件:

.env
# MinIO 配置

base/
└── configs.py
# 读取 MinIO 配置

conn/
└── minio_conn.py
# MinIO 连接、上传、生成下载链接

content/
└── middles/
└── file_manager_middle.py
# Agent 执行结束后的文件处理

utils/
└── doc_utils/
└── zip_files.py
# 工作目录压缩

10. 小结

这一部分实际上没有改变 Agent 原来的文件处理方式,只是在原来的工作目录后面增加了一个文件交付流程:

Agent工作目录

检测新文件

ZIP

MinIO

Presigned URL

用户

Agent 继续在自己的工作目录中操作文件,Middleware 负责在任务结束后把文件整理并交给用户。这样文件生成和文件传递就分开了。

赞(0)
未经允许不得转载:171主机测评 » Agent 文件传递
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址