原创 秋山墨客 2025-07-02 18:32 江苏

MCP实操:构建端到端的PPT解析与查询工具

?k=fcfc129a&u=https%3A%2F%2Fmmbiz.qpic

,点击上方

蓝字

关注我们

?k=c4576a99&u=https%3A%2F%2Fmmbiz.qpic

在RAG管道创建时,PPT是一个比较麻烦的文档类型,因为它的信息表达不像纯文本那样直接,也不像表格有固定的结构,之前也介绍过利用第三方的解析工具来完成。本篇我们将直接借助视觉模型构建一个多PPT文档的RAG查询管道,并将其封装到MCP Server中以方便集成到其他客户端或Agent应用。

  • 整体架构

  • MCP Server

  • RAG引擎

  • 效果测试

  • 后续优化


源代码地址在文末。

01

整体架构

我们希望最终的MCP Server能够提供这样一些核心能力:

  • 端到端处理将输入的PPT文档做知识解析与向量索引后提供查询

  • 深度解析:完备的提取PPT中元素所表达的信息,包括文字、图表等

  • 可溯源的生成文字与源PPT页面截图相结合,回答对PPT的事实性提问

  • 动态索引能够动态的添加与删除需要索引的多个PPT文档

  • 缓存机制PPT的解析与索引有缓存机制,避免重复处理带来成本浪费


系统整体结构如下:

?k=c2eca2f2&u=https%3A%2F%2Fmmbiz.qpic

本文重点介绍后端RAG引擎与MCP Server的设计与实现。使用的工具包括:

  • RAG框架:LlamaIndex

  • 向量库:Chroma

  • PPT文档处理:LibreOffice、Pdfium

  • 大模型:Doubao-vision

  • 嵌入模型:OpenAI


02

MCP Server的实现

我们构建的MCP Server提供如下工具(Tool):

  • index_status:查询当前的索引详细信息。包括已索引的文档、页数、ID等

  • add_ppt:输入一个文档(本地路径/URL),将其添加到RAG引擎的索引中

  • chat_with_ppt:与RAG引擎进行对话,对已经被索引的PPTs进行查询提问

  • delete_ppt:删除一个文档,将其从RAG引擎的索引中移除


MCP Server本身的实现并不复杂:通过调用背后的RAG引擎可以轻松的完成,以add_ppt这个Tool举例:

......
@app.tool()
async def add_ppt(
    ctx: Context,
    file_path: str,
    force_reprocess: bool = False
)
 -> str:

    """将指定的PPT文档添加到RAG索引中
    
    Args:
        ctx: 上下文对象
        file_path: 要添加的PPT文件的绝对或相对路径
        force_reprocess: 是否强制重新处理,即使文档已存在于索引中
    
    Returns:
        操作结果的JSON字符串
    """

    try:
        ...
        # 从上下文获取RAG引擎
        rag_engine = ctx.request_context.lifespan_context.rag_engine
        result = await rag_engine.add_ppt_document(abs_path, force_reprocess=force_reprocess)
        
        return json.dumps(result, indent=2, ensure_ascii=False)
......

这里无非是调用rag_engine(通过lifespan进行初始化加载)的add_ppt_document方法而已。而chat_with_ppt也只是调用另一个query方法:

...
@app.tool()
async def chat_with_ppt(
    ctx: Context,
    query: str,
    file_path: Optional[str] = None,
    doc_id: Optional[str] = None
)
 -> str:

    try:
        ...
        # 从上下文获取RAG引擎
        rag_engine = ctx.request_context.lifespan_context.rag_engine
        result = await rag_engine.query(query, file_path=file_path, doc_id=doc_id)
        return json.dumps(result, indent=2, ensure_ascii=False)
...

所以,这个MCP Server的核心是一个针对PPT类型文档的RAG引擎,下面看如何构建这个引擎。(如果你对MCP一无所知,也不影响对下面RAG引擎的理解)

03

实现RAG引擎

一个RAG引擎至少包含两个环节,一个是索引环节,一个是生成(查询环节)。

【索引环节】

索引环节的处理流程设计如下:

?k=195eeb27&u=https%3A%2F%2Fmmbiz.qpic

  • PPT->图片:借助libreoffice(需要安装)将ppt文件转化为pdf格式;并借助开源pdfium库将pdf转化成与PPT页对应的多张图片。简单说就是把PPT的每一页转化为对应的一张图片。


    比如我们输入《2024AIGC创新应用洞察报告.pptx》这个文档,将会输出多张对应的页面图片:


?k=77e40180&u=https%3A%2F%2Fmmbiz.qpic

这个过程被封装到独立的PPTUtils模块,具体请参考本文源码。

  • 图片->Markdown:借助视觉模型(这里使用doubao的vision模型)对每个图片进行理解,并以Markdown格式输出。这个解析过程依赖于提示词及模型本身的能力,在Demo中我们使用如下的提示(详细过程请参考源码):

    # 图像解析提示
    self.parse_prompt = """
    用中文提取图片中的详细信息,并使用Markdown格式化输出。
    -- 对于其中的文字,使用OCR识别,并尽量保持原格式或类似格式输出。
    -- 对于其中的表格与统计图表信息,选择表格结合文字的方式进行描述。
    -- 对于其中的图形、图表、流程图等视觉元素,请用文字详细描述其内容和布局。
    -- 对于其他有意义的图像部分,请使用文字描述。
    -- 合理排版,使得输出内容清晰易懂。
    """


    看下最后的解析效果,比如原PPT文档的某一页如下:


    ?k=6cd4ae16&u=https%3A%2F%2Fmmbiz.qpic


    其解析结果如下,模型会把图表很好的转化为Markdown表格形式:

?k=5c57bc58&u=https%3A%2F%2Fmmbiz.qpic

    注意解析时为了提高速度,可借助异步并行任务来完成。

  • 准备嵌入节点:使用解析出的Markdown构造用来嵌入的文本块(即LlamaIndex中的Node),并在每个Node中保存必要的元数据,如源PPT文件、对应PPT页码等。其中最重要的元数据是源PPT文件的ID(后续要用来删除索引),以及节点对应的图片(在检索阶段用来关联获取)。大致处理如下:


...
#图片解析结果
parsed_contents = await asyncio.gather(*parse_tasks)
# 创建文本节点,添加文档唯一标识符(MD5)
doc_id = self._get_file_hash(ppt_path)
nodes = []
for i, (image_path, content) inenumerate(zip(image_paths, parsed_contents)):
    node 
= TextNode(
        text=content,
        metadata={
            "source": ppt_path,
            "source_file_id": doc_id,         # 文档唯一标识符
            "doc_name": Path(ppt_path).name, 
            "page_num": i + 1,
            "image_path": image_path,  #源图片
            "doc_type""ppt_slide"
        }
    )
    nodes.append(node)
...

此处对生成的Nodes要做缓存(使用pickle),防止后续重复解析。

  • 嵌入并索引:对这些文本块(Node)进行索引。通过嵌入模型生成向量,并与元数据一起存储到向量库(元数据管理根据向量库有所不同)。更新后的索引也需要在本地缓存信息,在后续启动时直接加载该索引。

    借助LlamaIndex,这个过程可以轻松完成。首先在开始时创建空索引:

...
ifself._index is None:
    # 初始化向量存储
    vector_store = self._initialize_vector_store()
    storage_context = StorageContext.from_defaults(vector_store=vector_store)
    
    # 创建空索引(没有节点)
    self._index = VectorStoreIndex(
        [], 
        storage_context=storage_context,
        show_progress=False
    )
...

后续只需要向索引里“添加”新的Nodes即可,剩下的由框架完成:

...
self._index.insert_nodes(nodes)
            
# 持久化索引用于下次直接加载
self._persist_index()
...

这样,索引阶段工作就完成了,这也是后续工作的前提。当然实际应用中,可能有一些更复杂的优化与增强。但这里我们暂时只完成一个最基本的实现。

?k=55bd9c3e&u=https%3A%2F%2Fmmbiz.qpic

?k=6642eb2f&u=https%3A%2F%2Fmmbiz.qpic

RAG引擎的很多环节都需要一个表示PPT文件的标识符。比如用来命名中间图片、缓存解析结果、检索时作为元数据过滤、删除对应索引等。这里我们使用输入文件内容的MD5哈希值来作为唯一ID,以确保唯一性及内容相关性。

?k=c04872b9&u=https%3A%2F%2Fmmbiz.qpic

【生成环节】

基于已有的向量索引,生成环节的处理流程如下:

?k=f2c441cc&u=https%3A%2F%2Fmmbiz.qpic

  • 检索:借助已经构建的索引与检索器,对输入问题进行检索,召回与输入问题最相关的top_K个节点。


  • 关联图片:借助于节点中的元数据,可以轻松获得这些节点对应的图片,即源PPT页面。


  • 生成答案:构造上下文与提示词,将其与图片一起输入给视觉模型,并生成答案,并可以要求模型给出答案所参考的源页面图。


    为了更好的定制生成过程,在我们的Demo中采用了定制查询引擎(从CustomQueryEngine派生),仍然借助豆包视觉模型来生成。提示如下:

# 默认问答提示模板
default_prompt = """
以下是PPT幻灯片中解析的Markdown文本和图片信息。Markdown文本已经尝试将相关图表转换为表格。优先使用图片信息来回答问题。在无法理解图像时使用Markdown文本信息。
---------------------
{context_str}
---------------------
-- 根据上下文信息并且不依赖先验知识, 回答查询。
-- 解释你是从解析的markdown、还是图片中得到答案的, 如果有差异, 请说明最终答案的理由。
-- 尽可能详细的回答问题。
-- 给出你重点参考的图片路径和页码。
查询: {query_str}
答案: """

将检索出的相关Nodes的内容组装到这个Prompt,然后借助模型生成答案:

response_text = self._doubao_llm.generate_response(
                prompt=fmt_prompt,
                image_paths=image_paths
)

这里的一个技巧是,如何在query方法中支持传入指定的PPT文件名或者ID来查询呢?答案是借助元数据过滤(需要向量库支持)。在LlamaIndex中,你可以构建这样带过滤条件的检索器:

...
# 创建带文档过滤的检索器
filters = MetadataFilters(
    filters=[
        MetadataFilter(
            key="source_file_id",
            value=filter_doc_id,
            operator=FilterOperator.EQ
        )
    ]
)
retriever = VectorIndexRetriever(
    index=self._index,
    similarity_top_k=self.top_k,
    filters=filters
)
...

这样在向量库中检索时,就会首先通过元数据过滤后再进行语义检索。当有巨量文档时,这可以帮助提高性能与检索精度。

另外实现两个辅助功能:

  • 索引删除:输入源PPT文件路径或者文件ID,将从向量库中删除对应节点,同时删除本地的文档解析缓存等。


  • 索引状态查询:查询当前已经索引的PPT信息,包括文件名、页数、缓存位置等。Demo中暂时通过一个简单的本地文件来保存。


04

效果测试

在构建完成RAG引擎与MCP Server后(可以先对RAG引擎做单独测试,确保其工作正常),将MCP Server启动。这里使用SSE模式:

?k=1b34128d&u=https%3A%2F%2Fmmbiz.qpic

接下来用一个交互式的MCP测试客户端来测试服务端的Tools。

首先添加两个PPT文件到索引,然后查询下索引状态:

?k=ddcef2d3&u=https%3A%2F%2Fmmbiz.qpic

现在你可以和这些PPT对话(暂时只支持事实性问题),比如:

?k=e7595d96&u=https%3A%2F%2Fmmbiz.qpic

可以看到chat_with_ppt工具给出了正确的答案,并且在答案中包含了详细的参考源信息,比如PPT名称及页码,这有利于在真实的客户端应用中提供更好的体验。

05

后续优化

以上实现了一个用于ChatPPT的RAG引擎,并发布成MCP Server,以工具的形式提供给客户端使用,用来构建更复杂的ChatBot/Agent等应用。

这里仅实现了基本功能,实际生产场景会面临更复杂的对话需求。可以在此基础上考虑如下一些优化:

  • 为每一页PPT生成摘要信息或假设性问题,并参与向量化与语义检索,以覆盖更多的输入问题,提高召回的准确率。

  • 对首次向量的召回结果进行相关性评估,如果相关性不够,则通过查询转换做多次检索,并借助Rerank提取最相关的PPT页。

  • 增加其他形式的索引与MCP Tools,以覆盖不同类型的输入任务,比如针对概括性问题的SummaryIndex等。

  • 在客户端应用引入Agentic RAG思想。借助Agent的动态推理,可在一次查询中多次使用RAG工具,完成更综合性的任务。

  • 性能方面:如果文件较多,可借助异步、并行、LLM批量调用等手段以提高性能(参考:从0到1构建异步 DeepResearch 工具,支持进度推送与超时控制


希望本文能够给你带来一定的参考价值!喜欢的话点个赞吧。

本文源码地址:

https://github.com/pingcy/app_chatppt

🚀 写在最后

过去我们发表了很多 MCP(模型上下文协议)的原理介绍与实操分享。如果你希望进一步系统的掌握 MCP 的底层原理与工程实践,不再只是“用工具”,而是能够“打造工具”,那么我真诚推荐你阅读本公众号全新创作的新书

📘《MCP原理揭秘与开发指南——构建可扩展的AI智能体》

本书基于 2025-03-26 最新 MCP 协议规范 与 1.9.0 版本的 SDK 编写,全面覆盖从核心设计理念协议机制解析MCP 开发实践与完整源码案例,帮助你从根本上理解 MCP,并掌握 SDK 的高阶开发能力。

请注意——这不是一本面向 MCP Server 使用者的“工具说明书”,而是一本为MCP开发者编写的、以MCP SDK解析与实战为核心的技术指南。

本书内容:

?k=5a6b6dad&u=https%3A%2F%2Fmmbiz.qpic

更多详情请点击以下链接:

识别以下名片

加入公众号交流群(说明来意)

有机会获得作者赠书

图片

?k=6effd26d&u=https%3A%2F%2Fmmbiz.qpic

?k=55545820&u=https%3A%2F%2Fmmbiz.qpic

?k=f7125dd2&u=https%3A%2F%2Fmmbiz.qpic

?k=600ea48f&u=https%3A%2F%2Fmmbiz.qpic

?k=add3f79e&u=https%3A%2F%2Fmmbiz.qpic

?k=91ee98eb&u=https%3A%2F%2Fmmbiz.qpic

?k=74ae3cef&u=https%3A%2F%2Fmmbiz.qpic

?k=55bd9c3e&u=https%3A%2F%2Fmmbiz.qpic

?k=6642eb2f&u=https%3A%2F%2Fmmbiz.qpic

?k=c04872b9&u=https%3A%2F%2Fmmbiz.qpic

?k=b8d5454c&u=https%3A%2F%2Fmmbiz.qpic

?k=70f6b9e5&u=https%3A%2F%2Fmmbiz.qpic

?k=a4d4c5ed&u=https%3A%2F%2Fmmbiz.qpic

?k=e2738e71&u=https%3A%2F%2Fmmbiz.qpic

?k=fe0b8dc6&u=https%3A%2F%2Fmmbiz.qpic

?k=b81c9053&u=https%3A%2F%2Fmmbiz.qpic

阅读原文

跳转微信打开