dodo-agent

文件问答助手如何实现?

在前面的课程中,我们已经实现了一个具备 联网搜索能力的智能问答系统,其核心架构基于 ReAct Agent,通过工具调用的方式完成信息检索与推理。本节课我们继续在这个基础上扩展一个新的能力:文件问答助手。很多智能产品都支持“上传文件…

TL;DR

在前面的课程中,我们已经实现了一个具备 联网搜索能力的智能问答系统,其核心架构基于 ReAct Agent,通过工具调用的方式完成信息检索与推理。本节课我们继续在这个基础上扩展一个新的能力:文件问答助手。很多智能产品都支持“上传文件…

在前面的课程中,我们已经实现了一个具备 联网搜索能力的智能问答系统,其核心架构基于 ReAct Agent,通过工具调用的方式完成信息检索与推理。本节课我们继续在这个基础上扩展一个新的能力:文件问答助手。很多智能产品都支持“上传文件,然后直接提问”的功能,例如上传一份 PDF、Word 或图片,然后询问文件中的内容。本节课的目标,就是带大家实现这样一个完整的文件问答流程,并理解背后的工程实现思路。 从整体架构上来看,本节课实现的 FileReactAgent 与前面实现的 WebSearchReactAgent 在核心机制上是一致的,它们都基于 ReAct Agent 的执行流程,通过统一的 Agent 框架完成对话管理、上下文处理以及流式输出控制。因此在代码结构上,我们依然会继承之前实现的 BaseAgent,复用之前已经实现好的会话记忆、任务管理、流式输出等基础能力。 不过在课程中,我们将 文件问答 Agent 与联网搜索 Agent 分开实现。在真实的智能助手系统中,这两种能力通常会整合到同一个 Agent 中,由传入参数不同,自动决定是否调用搜索工具或读取文件内容。但在教学场景中,将它们拆分开来更有利于大家理解不同数据来源的处理方式,也更容易看清楚文件问答的完整流程。

整体流程

一个完整的文件问答系统通常包含以下几个步骤。 首先,用户需要上传文件,文件可以是 PDF、Word、TXT 等文本类文件,也可以是图片文件。文件上传后,后端服务会对文件进行解析,提取其中的文本内容,并将解析后的内容保存到数据库中。同时,原始文件也会上传到对象存储系统,例如 MinIO,用于后续下载或管理。文件解析完成后,系统会生成一个唯一的 fileId 并返回给前端。接下来,当用户在对话界面中提问时,前端会将 fileId 与用户问题一起发送给智能体接口。Agent 在执行时会根据 fileId 从数据库中读取之前解析好的文件内容,然后将这些内容注入到模型上下文中,让模型基于文件内容进行回答。 整个流程可以简单理解为:

用户上传文件
      ↓
服务器解析文件内容
      ↓
保存文本内容 + 上传文件到 MinIO
      ↓
返回 fileId
      ↓
用户提问(携带 fileId)
      ↓
 Agent加载文件内容
      ↓
注入模型上下文进行问答

这种方式的实现其实非常直接:模型并不需要自己去读取文件,而是由系统提前解析好内容,然后直接提供给模型。

为什么不用Function call?

在前面的联网搜索场景中,我们使用的是 ReAct Agent + 工具调用 的模式。模型在推理过程中如果发现信息不足,就会主动调用搜索工具,然后根据工具返回的结果继续生成答案。 例如智能体可能会产生这样的推理过程:

thinking: 我需要查询最新信息
text: 我查到了
text: OpenAI 最新发布模型

系统执行搜索工具后,再把搜索结果返回给模型。 但在第一个要讲的文件问答的方式中,我并没有采用这种,而是选择在对话开始之前,直接加载文件内容。例如在 Agent 执行过程中,我们会通过一个方法加载文件内容:

String fileContent = loadContent(fileId);

然后将文件内容与用户问题一起拼接到 Prompt 中:

String prompt = """
请根据以下文件内容回答问题:

%s

用户问题:
%s
""".formatted(fileContent, question);

这样模型在生成回答时,文件内容已经存在于上下文中,因此不需要再进行工具调用。 这种设计的主要原因是 性能考虑。 如果文件内容通过工具调用的方式获取,整个流程会变成:

Agent 推理
→ 调用文件工具
→ 工具加载文件内容
→ 返回结果
→ 注入上下文
→ 模型继续推理

这意味着模型至少需要 两次推理过程,整体响应时间会明显增加。而通过提前加载文件内容的方式,可以在 一次模型调用内完成整个问答过程,响应速度会更快。

文本解析策略

在实际系统中,文件内容解析通常有多种方式。本课程中采用的是一种比较简单但非常实用的策略:上传文件的时候直接解析文件文本,然后进行适当截断。 因为在前端上传文件时,通常会限制文件大小,例如限制在几 MB 以内。对于这样的文件规模,大多数大模型的上下文长度都是可以容纳的。如果文件内容稍微超出模型上下文限制,也可以进行适当截断,例如只保留前几万字符。 虽然这种方式看起来比较简单,但其实是很多 ToC 产品的实现方式,事实上确实也已经够用了,因为大多数用户上传的文件本身就不会特别大。

public


    log


                content


                content


                content


            log
            content


        log


        log


}

图片如何处理?

如果用户上传的是图片文件,那么系统就无法像文本文件一样直接读取内容,这时就需要借助 多模态模型 来识别图片中的信息。 这个在我们前面课程中的RAG章节已经介绍过了。直接使用 多模态模型(Multimodal Model) 来处理图片。所谓多模态模型,就是同时具备 视觉理解能力和文本生成能力 的模型。例如一些视觉语言模型可以直接接收图片作为输入,然后生成文字描述或回答问题。 多模态模型的优势是能够理解更复杂的图像信息,例如图表、布局结构甚至图片语义。但相应的,多模态模型的调用成本通常也更高,推理速度也会相对慢一些,因此在实际系统中需要根据场景进行选择。 我们在这节课中,基模(基础模型)可以直接用qwen3-vl-plus,兼具视觉理解和文本生成来实现。 通过下面的方式构造UserMessage提示词,注入模型上下文,qwen3-vl-plus 模型会自动识别图片,开始问答。

/**


private


}

/**


private


}

实践中的设计取舍

通过文件问答这个案例,其实可以看到一个非常典型的工程设计问题:性能优先还是架构统一。 如果从架构统一的角度来看,文件问答完全可以像联网搜索一样,通过工具调用的方式读取文件内容。但这样会增加一次模型推理,导致整体响应时间变长。 而通过在 Agent 执行前直接加载文件内容的方式,可以显著减少模型推理次数,从而提升用户体验。 因此在很多实际系统中,都会根据场景进行取舍,但是设计思维是大家都需要理解和掌握的。下节课我会使用架构统一的方式来加以改造。

思考

这种实现方式在很多 ToC 产品中已经完全够用,但如果是在 ToB 企业级场景 中,情况往往会有所不同。 企业客户上传的文件可能会比较大,并且对回答的要求和精准度都比较高,如果只是简单地进行暴力截断,很可能会丢失重要信息,从而影响问答质量,企业客户一般对此是无法接受的,因此这种方式就可能无法满足需求。 那么问题来了: 如果文件内容比较大,不能简单截断,我们应该如何实现文件问答? 这个问题的答案,其实就是下一节课要介绍的内容。

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。