Agent 接收用户上传文件

1. 问题与目标

前面的工作已经实现了用户端文件上传,但目前文件主要用于用户侧操作。下一步需要让 Agent 本身能够接收这些文件。

对于通用 Agent 来说,文件不应该只限制在图片。例如用户上传一个 Word 文档,希望 Agent 阅读并分析其中的内容;或者上传 Excel、PDF、Markdown 等文件,再让 Agent 根据文件执行任务。

LangGraph CLI 创建的工程默认已经支持文件上传,但前端组件支持的类型主要是:

复制代码
pjp, jfif, pjpeg, jpeg, jpg, png, gif, webp, pdf

这些类型本质上都是图片格式,PDF 在默认情况下也会作为图片处理。

如果项目只要求 Agent 接收图片,那么后端只需要将模型替换成支持视觉输入的 VLM 即可,其他部分基本不需要调整。

但本项目后续还需要处理 Word、Excel、PDF、Markdown 等文件,因此这里先解决一个更基础的问题:

让 Agent 能够接收任意类型的用户文件,并将文件保存到当前 Agent 工作目录。

文件内容的具体解析、转换和处理属于后续工作。


2. 实现方案

整体流程比较简单:

复制代码
用户上传文件
      ↓
Agent Chat UI
      ↓
文件转换为 Base64
      ↓
AgentState.upload_files
      ↓
before_agent Middleware
      ↓
Base64 解码
      ↓
user_uploads/
      ↓
Agent 读取文件

具体分为两部分:

  1. 修改 Agent Chat UI,使上传组件支持任意文件,并将文件信息放入 upload_files

  2. 在后端 before_agent 中读取 upload_files,将 Base64 文件恢复成实际文件。


3. 前端文件数据

前端接收到用户上传的文件后,不直接将二进制文件放进 JSON,而是使用 Base64 字符串表示文件内容。

例如,一个上传文件的数据结构如下:

复制代码
{
    "type": "file",
    "mimeType": "image/jpeg",
    "data": "DQoNCiMjIEFJIEFnZW50...",
    "metadata": {
        "filename": "a.md",
        "size": 8238,
        "lastModified": 1757389979338
    }
}

其中:

  • type:固定为 file

  • mimeType:文件 MIME 类型

  • data:文件内容对应的 Base64 字符串

  • metadata.filename:原始文件名

  • metadata.size:文件大小

  • metadata.lastModified:文件最后修改时间

多个文件则组成一个列表,并通过 Agent State 传递:

复制代码
from langchain.agents.middleware import AgentState

class CustomState(AgentState):
    upload_files: list

3.1 为什么使用 Base64

Base64 的作用只是将二进制数据转换成字符串,方便放进 JSON 等文本格式中进行传输。

例如:

复制代码
原始文件
   ↓
二进制数据
   ↓ Base64 编码
字符串
   ↓
JSON
   ↓
后端
   ↓ Base64 解码
原始文件

需要注意,Base64 不是加密,同时会使数据体积增加约三分之一,因此这里只把它作为前后端之间的传输方式,而不是文件存储方式。


4. 后端实现

4.1 Base64 文件处理

content/utils 下新增 base64_util.py,负责将 Base64 数据恢复成文件。

复制代码
import base64
import os


def save_base64_file_from_content_block(content_block, save_dir):
    """
    从前端文件内容块中提取 Base64 数据并保存为文件
    """
    return save_base64_file(
        base64_data=content_block["data"],
        filename=content_block["metadata"]["filename"],
        save_dir=save_dir
    )


def save_base64_file(
        base64_data: str,
        filename: str,
        save_dir: str
):
    """
    将 Base64 数据保存为文件
    """
    file_path = os.path.join(save_dir, filename)

    file_data = base64.b64decode(base64_data)

    with open(file_path, "wb") as f:
        f.write(file_data)

    return {
        "success": True,
        "file_path": str(file_path),
        "filename": filename,
    }

这里将具体的 Base64 解码逻辑独立出来,Middleware 只负责处理 Agent 生命周期和状态,不直接处理文件编码细节。


4.2 配置上传目录

base/configs.py 中增加:

复制代码
USER_UPLOAD_PATH = "user_uploads"

后续如果需要调整目录,只修改配置即可,不需要再修改 Middleware。


4.3 在 Middleware 中处理上传文件

文件上传发生在 Agent 开始执行之前,因此放在 FileMiddlewareabefore_agent 中处理。

首先增加相关状态和依赖:

复制代码
from base.configs import USER_UPLOAD_PATH
from content.utils import base64_util as bu


class CustomState(AgentState):
    upload_files: list
    start_work_time: float
    file_update_time: float

然后修改 abefore_agent

复制代码
async def abefore_agent(self, state, runtime):

    file_update_time = time.time()

    # 当前线程对应的工作目录
    dir_path = os.path.join(
        rt.get_root_thread_dir(),
        USER_UPLOAD_PATH
    )

    # 创建用户上传目录
    await asyncio.to_thread(
        os.makedirs,
        dir_path,
        exist_ok=True
    )

    files = state.get("upload_files")

    if files:
        content = "用户上传了如下文件,文件路径如下:"

        for file in files:

            # Base64 解码并保存文件
            result = await asyncio.to_thread(
                bu.save_base64_file_from_content_block,
                file,
                dir_path
            )

            # 返回给 Agent 相对路径
            content += f"\n{rt.get_out_path(result['file_path'])}"

        return {
            "messages": [
                ToolMessage(
                    content=content,
                    tool_call_id=get_uuid(),
                    name="file_upload"
                )
            ],
            "upload_files": None,
            "start_work_time": time.time(),
            "file_update_time": file_update_time
        }

    return {
        "start_work_time": file_update_time,
        "file_update_time": file_update_time
    }

这里有两个需要注意的地方。

第一,文件操作没有直接阻塞异步流程。

os.makedirs 和文件写入属于同步 I/O,因此使用:

复制代码
await asyncio.to_thread(...)

放到线程中执行,避免直接阻塞 Agent 的异步执行流程。

第二,处理完成后清空 upload_files

文件已经落盘后,就没有必要继续将完整的 Base64 数据保存在 Agent State 中:

复制代码
"upload_files": None

这样可以避免后续 Agent 状态继续携带较大的文件数据。

此时 Agent 实际拿到的是类似下面的信息:

复制代码
用户上传了如下文件,文件路径如下:
./user_uploads/a.md
./user_uploads/test.pdf
./user_uploads/data.xlsx

后续 Agent 可以根据路径继续调用文件处理工具。


5. 测试

前端选择多个不同类型的文件进行上传,例如:

复制代码
test.pdf
test.docx
data.xlsx
demo.md
image.png

上传后检查当前线程对应的工作目录:

复制代码
<thread_work_dir>/
└── user_uploads/
    ├── test.pdf
    ├── test.docx
    ├── data.xlsx
    ├── demo.md
    └── image.png

确认文件能够正常生成,并且文件内容与原文件一致,即可说明前后端文件传输链路已经打通。


6. 最终目录结构

本次只列出新增和修改的部分:

复制代码
├─ base
│   └─ configs.py
│      # 添加 USER_UPLOAD_PATH
│
├─ content
│   ├─ middles
│   │   └─ file_manager_middle.py
│   │      # 处理 Agent 接收用户文件
│   │
│   └─ utils
│       ├─ runtime_util.py
│       └─ base64_util.py
│          # 新增,负责 Base64 文件解码

7. 小结

这一阶段没有处理文件内容,只解决了 Agent 接收文件 这个基础问题。

目前链路为:

复制代码
前端上传任意文件
        ↓
Base64
        ↓
AgentState.upload_files
        ↓
FileMiddleware.abefore_agent
        ↓
Base64 解码
        ↓
当前线程 / user_uploads/
        ↓
Agent 获取文件路径

至此,Agent 已经可以接收任意类型的用户文件。

下一步只需要在这个基础上继续增加文件解析能力,例如:

复制代码
Word / PDF / Markdown
        ↓
文本提取
        ↓
Markdown / 纯文本
        ↓
Agent 分析

而图片、Excel 等文件则可以根据实际类型交给对应的工具或 SubAgent 处理。

相关推荐
奔跑的痕迹15 分钟前
如何删除引力画报?
笔记
可乐鸡翅yeah_18 分钟前
hls.js 内存泄漏实战排查,直播 M3U8 长时间播放异常定位方案
开发语言·javascript·python·django·ecmascript·m3u8·m3u8在线
可乐鸡翅yeah_24 分钟前
第三方接口对接 M3U8 流媒体排坑实战,解决外部服务商流兼容难题
前端·javascript·python·django·html·m3u8·m3u8在线
Huazhongzhanhui24 分钟前
极智防护与绿色表改:2026中国(武汉)国际表面处理展览会涂装涂料展会前瞻
人工智能·云计算
金融小师妹28 分钟前
多因子智能推演:黄金震荡回升,杰克逊霍尔“沃什首秀”政策如何重塑金价路径的AI预测框架
大数据·人工智能·python·线性回归
阿童木写作30 分钟前
跨马翻译:AI批量图片翻译与视频字幕翻译工具推荐
人工智能·python·音视频
zbyyd31 分钟前
Linux 进程间通信学习笔记
linux·笔记·学习
阿kun要赚马内36 分钟前
MCP(Model Context Protocol,模型上下文协议)
人工智能·python