ragflow 0.18.0新功能
ragflow在0.18.0的发布的时候,已经第一时间体验了。除了之前预测的功能,就额外多加了一个MCP的功能。刚好五一假期研究了MCP,就把之前写的半截的文章翻了出来。
RAGFlow 0.18.0 发布.
-
1. 实现了 MCP Server:可以通过 MCP 访问 RAGFlow 的知识库
-
2. 团队协作:Agent 可以被分享给其他团队成员
-
3. Agent 版本管理:Agent 的更新会被持续记录并支持通过导出恢复至旧版本
-
4. 新增兼容 OpenAI API 的模型供应商:Agent 支持调用与 OpenAI API 兼容的模型
-
5. 禁用用户注册:管理员可以通过环境变量管理是否开放用户注册入口
-
6. PDF 解析器支持 VLM 模型
-
7. 优化回答引用:优化生成回答时候引用的精准度
-
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
的位置中,选择刚配置好的ragflwo
mcp服务 -
•
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%免费】