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 读取文件具体分为两部分:
修改 Agent Chat UI,使上传组件支持任意文件,并将文件信息放入
upload_files。在后端
before_agent中读取upload_files,将 Base64 文件恢复成实际文件。
3. 前端文件数据
前端接收到用户上传的文件后,不直接将二进制文件放进 JSON,而是使用 Base64 字符串表示文件内容。
例如,一个上传文件的数据结构如下:
{ "type": "file", "mimeType": "image/jpeg", "data": "DQoNCiMjIEFJIEFnZW50...", "metadata": { "filename": "a.md", "size": 8238, "lastModified": 1757389979338 } }其中:
type:固定为filemimeType:文件 MIME 类型data:文件内容对应的 Base64 字符串metadata.filename:原始文件名metadata.size:文件大小metadata.lastModified:文件最后修改时间
多个文件则组成一个列表,并通过 Agent State 传递:
from langchain.agents.middleware import AgentState class CustomState(AgentState): upload_files: list3.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 开始执行之前,因此放在FileMiddleware的abefore_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 处理。