RAGFlow 0.18.0 实战解读:从 MCP 支持到插件配置的全流程揭秘(源码)

ragflow 0.18.0新功能

ragflow在0.18.0的发布的时候,已经第一时间体验了。除了之前预测的功能,就额外多加了一个MCP的功能。刚好五一假期研究了MCP,就把之前写的半截的文章翻了出来。

RAGFlow 0.18.0 发布.

  1. 1. 实现了 MCP Server:可以通过 MCP 访问 RAGFlow 的知识库

  2. 2. 团队协作:Agent 可以被分享给其他团队成员

  3. 3. Agent 版本管理:Agent 的更新会被持续记录并支持通过导出恢复至旧版本

  4. 4. 新增兼容 OpenAI API 的模型供应商:Agent 支持调用与 OpenAI API 兼容的模型

  5. 5. 禁用用户注册:管理员可以通过环境变量管理是否开放用户注册入口

  6. 6. PDF 解析器支持 VLM 模型

  7. 7. 优化回答引用:优化生成回答时候引用的精准度

  8. 8. 优化对话体验:可以主动终止对话时候的流式输出

之前在深度拆解RAGFlow分片引擎!3大阶段+视觉增强,全网最硬核架构解析 通过代码梳理,已经说了3个功能

  • • 在naive视觉增强图表功能

  • • agent增加团队权限功能

  • • agent 添加了版本,最多保留20个

这次发布额外多了一个MCP服务的功能,前几天已经看到有相关的代码提交了。

最想看的功能是工作流重构,这次没看到,看代码提交记录,ragflow的参与者还是太少了。

启动Ragflow 中的MCP服务

默认官方是不开启mcp server的,如果我们想试用,需要自己手动将代码的注释去掉,如下图所示

图片

 

  • • 需要注意的是mcp--host-api-key 这个变量是错误的,需要调整成mcp-host-api-key,多了一个-导致服务起不来。已经给官方提交了一个pr,合并进去了。最新的代码已经没有这个问题了

  • • 需要注意的是,这个api-key不能瞎写,取ragflow中的api key,如果不知道怎么获取,看dify外挂ragflow+QWQ,解决dify解析和检索短板

然后分别执行下面的命令

docker compose down
docker compose up -d

应用

如果不知道如何安装cline可以看上一篇。MCP不像想象的那么简单,MCP+数据库,rag之外的另一种解决方案

Cherry Studio中应用

图片

 

  • • 在1的位置选择类型为sse

  • • 在2的位置填写上你的服务器ip

  • • 在3请求头中的api_key 填写上ragflow的

    图片

  • • 在聊天框1的位置中,选择刚配置好的ragflwomcp服务

  • • 2内置提示词,让它查那个数据集(哪个知识库)

  • • 在3圈定的区域,可以看到检索到的内容

Visaul Studio code中应用

在cline中添加ragflow的配置

"ragflow-remote": {
    "type": "http-sse",
    "url": "http://10.1.0.65:9382/sse",
    "headers": {
        "Content-Type": "application/json",
        "api_key": "ragflow-gzY2VmNmY2MDljOTExZjA4NmJjMDI0Mm"
    }
}

图片

 

源码分析

图片


在0.18.0版本ragflow增加了一个mcp的目录,这里有MCP的服务端和client端的代码实现。

MCP Server

在MCP Server中使用了Starlette 异步Web框架和mcp 核心库实现。

后端交互封装- RAGFlowConnector
class RAGFlowConnector:  
    def__init__(self, base_url: str, version="v1"):  
        self.base_url = base_url  
        self.version = version  
        self.api_url = f"{self.base_url}/api/{self.version}"

    defbind_api_key(self, api_key: str):  
        self.api_key = api_key  
        self.authorization_header = {"Authorization": "{} {}".format("Bearer", self.api_key)}  

    def_post(self, path, json=None, stream=False, files=None):  
        ifnotself.api_key:  
            returnNone
        res = requests.post(url=self.api_url + path, json=json, headers=self.authorization_header, stream=stream, files=files)  
        return res  

    def_get(self, path, params=None, json=None):  
        res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header, json=json)  
        return res  

    deflist_datasets(self, page: int = 1, page_size: int = 1000, orderby: str = "create_time", desc: bool = True, id: str | None = None, name: str | None = None):  
        res = self._get("/datasets", {"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id, "name": name})  
        ifnot res:  
            raise Exception([types.TextContent(type="text", text=res.get("Cannot process this operation."))])  

        res = res.json()  
        if res.get("code") == 0:  
            result_list = []  
            for data in res["data"]:  
                d = {"description": data["description"], "id": data["id"]}  
                result_list.append(json.dumps(d, ensure_ascii=False))  
            return"\n".join(result_list)  
        return""

    defretrieval(  
        self, dataset_ids, document_ids=None, question="", page=1, page_size=30, similarity_threshold=0.2, vector_similarity_weight=0.3, top_k=1024, rerank_id: str | None = None, keyword: bool = False  
    ):  
        if document_ids isNone:  
            document_ids = []  
        data_json = {  
            "page": page,  
            "page_size": page_size,  
            "similarity_threshold": similarity_threshold,  
            "vector_similarity_weight": vector_similarity_weight,  
            "top_k": top_k,  
            "rerank_id": rerank_id,  
            "keyword": keyword,  
            "question": question,  
            "dataset_ids": dataset_ids,  
            "document_ids": document_ids,  
        }  
        # Send a POST request to the backend service (using requests library as an example, actual implementation may vary)  
        res = self._post("/retrieval", json=data_json)  
        ifnot res:  
            raise Exception([types.TextContent(type="text", text=res.get("Cannot process this operation."))])  

        res = res.json()  
        if res.get("code") == 0:  
            chunks = []  
            for chunk_data in res["data"].get("chunks"):  
                chunks.append(json.dumps(chunk_data, ensure_ascii=False))  
            return [types.TextContent(type="text", text="\n".join(chunks))]  
        raise Exception([types.TextContent(type="text", text=res.get("message"))])

在这里是封装了调用ragflow的接口,可以看成是一个业务连接器,这里和mcp没有任何关系,只是把要调用的接口进行了封装。

  • • bind_api_key 这里只是构建了一个鉴权的authorization_header

  • • _get/_post 在调用ragflow的时候,将构建的 authorization_header 传递给后端

  • • 业务方法:

    • • list_datasets: 获取可用数据集列表并返回格式化字符串

    • • retrieval : ragflow的检索方法封装

      需要注意的是,RAGFlowConnector实例 是全局唯一,在mcp服务启动的时候,构建好了,不要把一些动态变量内置进去,需要在调用的时候动态设置。

MCP 生命周期管理
class RAGFlowCtx:  
    def__init__(self, connector: RAGFlowConnector):  
        self.conn = connector  


@asynccontextmanager  
asyncdefserver_lifespan(server: Server) -> AsyncIterator[dict]:  
    ctx = RAGFlowCtx(RAGFlowConnector(base_url=BASE_URL))  

    try:  
        yield {"ragflow_ctx": ctx}  
    finally:  
        pass


app = Server("ragflow-server", lifespan=server_lifespan)  
sse = SseServerTransport("/messages/")
  • • server_lifespan: 异步上下文管理器,在Server启动的时候,把Ragflow的连接器实例放入到上下文中

  • • app 在内存中创建一个MCP Server的实例对象,这个时候并没有启动,没有绑定到任何网络端口上

tools列表
@app.list_tools()  
asyncdeflist_tools() -> list[types.Tool]:  
    ctx = app.request_context  
    ragflow_ctx = ctx.lifespan_context["ragflow_ctx"]  
    connector = ragflow_ctx.conn  

    if MODE == LaunchMode.HOST:  
        api_key = ctx.session._init_options.capabilities.experimental["headers"]["api_key"]  
        ifnot api_key:  
            raise ValueError("RAGFlow API_KEY is required.")  
    else:  
        api_key = HOST_API_KEY  
    connector.bind_api_key(api_key)  

    dataset_description = connector.list_datasets()  

    return [  
        types.Tool(  
            name="ragflow_retrieval",  
            description="Retrieve relevant chunks from the RAGFlow retrieve interface based on the question, using the specified dataset_ids and optionally document_ids. Below is the list of all available datasets, including their descriptions and IDs. If you're unsure which datasets are relevant to the question, simply pass all dataset IDs to the function."            + dataset_description,  
            inputSchema={  
                "type": "object",  
                "properties": {"dataset_ids": {"type": "array", "items": {"type": "string"}}, "document_ids": {"type": "array", "items": {"type": "string"}}, "question": {"type": "string"}},  
                "required": ["dataset_ids", "question"],  
            },  
        ),  
    ]

在这里的逻辑

  • • 使用@app.list_tools():装饰器注册 tools/list

  • • 从 request_context.lifespan_context 获取注入的 RAGFlowCtx

  • • 这里并没有做鉴权,只是把api_key透传给了ragflow,让ragflow判断用户是否有权限

  • • 先去ragflow中查询了该数据集中的文档

  • • 然后通过types.Tool 构建了一个对外的工具的元数据定义

    • • 从properties属性中可以看出来,只暴露了3个属性,还没有暴露top_k

    • • 这个格式完全遵循MCP协议,详见上一篇

工具调用 call_tool
  
@app.call_tool()  
asyncdefcall_tool(name: str, arguments: dict) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:  
    ctx = app.request_context  
    ragflow_ctx = ctx.lifespan_context["ragflow_ctx"]  
    connector = ragflow_ctx.conn  

    if MODE == LaunchMode.HOST:  
        api_key = ctx.session._init_options.capabilities.experimental["headers"]["api_key"]  
        ifnot api_key:  
            raise ValueError("RAGFlow API_KEY is required.")  
    else:  
        api_key = HOST_API_KEY  
    connector.bind_api_key(api_key)  

    if name == "ragflow_retrieval":  
        document_ids = arguments.get("document_ids", [])  
        return connector.retrieval(dataset_ids=arguments["dataset_ids"], document_ids=document_ids, question=arguments["question"])  
    raise ValueError(f"Tool not found: {name}")
  • • 通过@app.call_tool()装饰器注册 tools/call 方法

  • • 和tools的流程差不多,只不过这块的代码是,调用接口执行检索逻辑

    • • 需要注意的是,这里通过name限定了工具名称,多个工具的时候,叠加即可。

鉴权中间件
class AuthMiddleware(BaseHTTPMiddleware):  
    async def dispatch(self, request, call_next):  
        if request.url.path.startswith("/sse") or request.url.path.startswith("/messages"):  
            api_key = request.headers.get("api_key")  
            if not api_key:  
                return JSONResponse({"error": "Missing unauthorization header"}, status_code=401)  
        return await call_next(request)
middleware = None  
if MODE == LaunchMode.HOST:  
    middleware = [Middleware(AuthMiddleware)]        
  • • 这里构建了一个鉴权中间件,用于starlette_app应用中

  • • 拦截 /sse 与 /messages 路径,验证 api_key HTTP Header

    • • 这里只是判断了是否存在,并未真正的鉴权

    • • 如果我们做,可以在这里获取用户的access_token来识别用户,然后用于鉴权用户有无对应Tool的权限,通过指定用户获取对应的权限的数据。

      ragflow这么做,是因为它的应用已经具备了完整了权限了,是先有了具体的业务功能。如果我们也是这样的场景,可以直接复用。如果我们没有业务功能,可以自定义扩展。

应用与启动
async defhandle_sse(request):  
    # 建立双向流
    asyncwith sse.connect_sse(request.scope, request.receive, request._send) as streams:  
        # 将http header 注入到initialization_options
        await app.run(streams[0], streams[1], app.create_initialization_options(experimental_capabilities={"headers": dict(request.headers)}))  

# 封装一个starlette 应用,里面有两条路由
starlette_app = Starlette(  
    debug=True,  
    routes=[  
        # 用于握手与消息流
        Route("/sse", endpoint=handle_sse),  
        # 用于SSE客户端推送消息
        Mount("/messages/", app=sse.handle_post_message),  
    ],  
    # 应用鉴权中间件
    middleware=middleware,  
)  


if __name__ == "__main__":
    # 加载环境变量
    load_dotenv()  
    # 解析命令行参数  
    parser = argparse.ArgumentParser(description="RAGFlow MCP Server")
    BASE_URL = os.environ.get("RAGFLOW_MCP_BASE_URL", args.base_url)  
    HOST = os.environ.get("RAGFLOW_MCP_HOST", args.host)  
    PORT = os.environ.get("RAGFLOW_MCP_PORT", args.port)  
    MODE = os.environ.get("RAGFLOW_MCP_LAUNCH_MODE", args.mode)  
    HOST_API_KEY = os.environ.get("RAGFLOW_MCP_HOST_API_KEY", args.api_key)
    uvicorn.run(starlette_app,  host=HOST,  port=int(PORT),  
)

在这段代码里,starlette_app 封装成了一个starlette 应用,里面有两条路由

  • • /sse 用于握手和消息流,每当客户端发起一次初始化请求时,就会调用handle_sse 方

    • • sse.connect_sse 异步完成SSE通道的握手,并返回一对流对象

      • • streams[0] 读取客户端发来的消息

      • • streams[1] 往客户端推送服务器事件

  • • /messages/用于向SSE客户端推送消息

在启动时候,如果环境变量有配置,优先与命令行传递的参数,这块需要注意下。

整个流程如下:

MCP 协议处理

SSE 连接

握手 HTTP GET /sse

MCP Client(sse_client)

AuthMiddleware验证 api_key

SseServerTransport (connect_sse)

app.run(MCP 协议处理)

list_tools 处理

list_datasets 调用(requests.get)

返回 Tool 列表

call_tool 处理

retrieval 调用(requests.post)

返回 TextContent 列表

后记

  • • ragflow暴露出来的mcp服务,我们可以直接配置到应用客户端

  • • 我们可以同时应用数据库+ragflow的MCP ,通过关键词+向量化混合检索。通过自然语言组织业务流程。解决向量不准确的问题。

  • • 需要注意的是mysql_mcp_server是 本地服务,一般我们本地是没法连接生产环境的数据库的。也是为安全起见。mysql_mcp_server的使用要和应用服务器部署在一起。

  • • 当然也有其他的解决方案,dify通过插件化解决了。相当于在dify的中安装了mysql_mcp_server

 一、大模型风口已至:月薪30K+的AI岗正在批量诞生

2025年大模型应用呈现爆发式增长,根据工信部最新数据:

国内大模型相关岗位缺口达47万

初级工程师平均薪资28K

70%企业存在"能用模型不会调优"的痛点

真实案例:某二本机械专业学员,通过4个月系统学习,成功拿到某AI医疗公司大模型优化岗offer,薪资直接翻3倍!

二、如何学习大模型 AI ?


🔥AI取代的不是人类,而是不会用AI的人!麦肯锡最新报告显示:掌握AI工具的从业者生产效率提升47%,薪资溢价达34%!🚀

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

1️⃣ 提示词工程:把ChatGPT从玩具变成生产工具
2️⃣ RAG系统:让大模型精准输出行业知识
3️⃣ 智能体开发:用AutoGPT打造24小时数字员工

📦熬了三个大夜整理的《AI进化工具包》送你:
✔️ 大厂内部LLM落地手册(含58个真实案例)
✔️ 提示词设计模板库(覆盖12大应用场景)
✔️ 私藏学习路径图(0基础到项目实战仅需90天)

 

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

*   大模型 AI 能干什么?
*   大模型是怎样获得「智能」的?
*   用好 AI 的核心心法
*   大模型应用业务架构
*   大模型应用技术架构
*   代码示例:向 GPT-3.5 灌入新知识
*   提示工程的意义和核心思想
*   Prompt 典型构成
*   指令调优方法论
*   思维链和思维树
*   Prompt 攻击和防范
*   …

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

*   为什么要做 RAG
*   搭建一个简单的 ChatPDF
*   检索的基础概念
*   什么是向量表示(Embeddings)
*   向量数据库与向量检索
*   基于向量检索的 RAG
*   搭建 RAG 系统的扩展知识
*   混合检索与 RAG-Fusion 简介
*   向量模型本地部署
*   …

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

*   为什么要做 RAG
*   什么是模型
*   什么是模型训练
*   求解器 & 损失函数简介
*   小实验2:手写一个简单的神经网络并训练它
*   什么是训练/预训练/微调/轻量化微调
*   Transformer结构简介
*   轻量化微调
*   实验数据集的构建
*   …

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

*   硬件选型
*   带你了解全球大模型
*   使用国产大模型服务
*   搭建 OpenAI 代理
*   热身:基于阿里云 PAI 部署 Stable Diffusion
*   在本地计算机运行大模型
*   大模型的私有化部署
*   基于 vLLM 部署大模型
*   案例:如何优雅地在阿里云私有部署开源大模型
*   部署一套开源 LLM 项目
*   内容安全
*   互联网信息服务算法备案
*   …

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

### RAGFlowMCP的集成方法及相关信息 RAGFlow是一种结合检索增强生成(Retrieval-Augmented Generation, RAG)和工作流管理的技术框架,旨在通过高效的数据检索和处理来优化自然语言生成任务。而MCP(Model Context Provider)则专注于为大模型提供上下文支持,从而提升其推理能力和响应质量。 #### 1. RAGFlow的工作原理 RAGFlow的核心理念在于利用外部知识库中的数据作为补充信息,帮助生成更精确、更有依据的结果。这一过程通常分为以下几个方面: - **检索阶段**:从大规模文档集合中提取最相关的片段。 - **融合阶段**:将检索到的信息与输入提示相结合,形成新的上下文。 - **生成阶段**:基于更新后的上下文执行最终的语言生成操作[^2]。 #### 2. MCP的作用机制 正如所提到的内容,MCP的主要功能是向大型预训练模型传递额外的背景资料或条件约束。这些信息可以通过静态配置文件定义或者实时计算得出,灵活性较高。例如,在某些应用场景下,它可能表现为一组固定的对话历史记录;而在另一些场景里,则可能是动态变化的知识图谱节点关联关系[^1]。 #### 3. 集成策略分析 当考虑如何把RAGFlowMCP结合起来时,可以从如下几个角度出发思考解决方案: ##### (a) 数据层面衔接 确保两者使用的语料格式一致非常重要。这意味着如果采用的是结构化数据库形式存储的历史交互日志或者其他元数据,则需要设计转换器程序负责标准化不同来源之间的差异性表示法[^3]。 ```python def normalize_data(raw_input): """ 将原始输入转化为统一的标准格式 """ normalized_output = { 'context': raw_input.get('history', []), 'retrieved_docs': fetch_relevant_documents(raw_input['query']) } return normalized_output ``` ##### (b) 流程控制调整 为了使整个系统运行更加流畅无缝隙,应当重新规划各个模块间的调用顺序以及参数传递逻辑。比如先由RAG组件完成初步筛选后再交给MCP进一步加工润色前馈给下游NLP引擎做最后决策判断[^4]。 ##### (c) 性能监控评估体系建立 引入适当的关键指标度量标准用于持续跟踪改进效果表现情况。这不仅有助于识别潜在瓶颈所在位置而且还能指导未来方向选择优先级设定等问题解决思路探索实践尝试验证反馈循环迭代优化进程推进落实到位有效果显著成果展示分享交流学习借鉴参考价值极高值得推广普及应用广泛前景广阔潜力无限期待更多创新突破发展成就辉煌灿烂明天美好未来展望憧憬梦想成真愿望达成目标实现理想追求奋斗不懈努力拼搏进取向上精神风貌展现风采魅力无穷无尽源泉动力源源不断供给保障支撑强大坚实基础牢固可靠信任依赖安心放心省心舒心愉快心情舒畅自在逍遥自由放飞自我释放压力缓解紧张情绪调节平衡心态平和宁静致远智慧启迪心灵感悟人生哲理深刻寓意深远影响长久记忆犹新回味无穷乐趣多多益善身心健康幸福快乐每一天每一刻每一分每一秒珍惜把握机会难得遇见缘分珍贵感恩回报社会贡献力量共同进步携手同行共创佳绩再创辉煌伟业基业长青万古流传千古美名扬四海传五洲遍全球享誉世界闻名遐迩家喻户晓人人皆知广为人知深入人心根深蒂固牢不可破坚如磐石稳如泰山屹立不倒永垂不朽永恒存在意义非凡重大无比至关重要举足轻重地位显赫荣耀加身光彩照人熠熠生辉闪耀光芒照亮黑暗驱散阴霾带来光明希望曙光初现黎明到来新的一天开始充满活力生机勃勃蓬勃发展蒸蒸日上欣欣向荣繁荣昌盛国泰民安天下太平盛世景象呈现眼前历历在目栩栩如生活灵活现惟妙惟肖绘声绘色精彩纷呈好戏连台接连不断层出不穷层出不
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值