Dify+Oracle+MCP打造企业级RAG智能问答应用实战
2026/9/17 10:42:44 网站建设 项目流程

说实话,做 RAG 应用最让我头疼的往往不是模型本身,而是“业务数据怎么跟大模型打通”。最近一个项目里,客户的数据全在 Oracle 老库里,十几年的表结构、各种存储过程、敏感字段还不少。我最后拼出来的方案是:Dify 负责应用编排和知识库,Oracle 继续承担业务数据存储,MCP 把 Oracle 的查询、存储过程、甚至运维操作包装成标准工具交给 Agent 调用。这套 Dify + Oracle + MCP 组合,既满足了客户“不动数据库”的底线,又让大模型真的能查数、能干活。

这篇就把我从环境准备到联调通过的全过程写出来,包括 Dify 社区版部署、Oracle 安装与监听排查、MCP Server 编写、RAG 知识库流水线、以及 Oracle MCP Agent 的完整实现。适合正在做企业知识库、智能客服、内部数据问答这类项目的朋友参考。你会看到完整的架构选型思路、关键代码、提示词模板,还有我踩过的坑。

1. 项目概述:为什么要拼这一套

1.1 一个很现实的场景

很多人一开始会问:直接用 LangChain 写个 RAG 不就行了吗?问题在于,真实企业环境里,“数据在哪里”比“模型怎么调”难得多。客户告诉我,他们的产品资料分散在 Word、PDF 和 Oracle 业务系统里,以前的智能问答只能回答文档里的内容,一涉及到“上个月华东区交了多少货”这种要查库的问题就歇菜。于是需求就变成:既要能基于文档回答问题,又要能实时查 Oracle 数据库,还不能让 AI 直接操作生产库造成风险。

Dify 社区版在这个项目里承担的事很清晰:知识库管理、检索、工作流编排、对外 API 和前端应用界面。Oracle 就是那个“真实数据源”,业务数据不搬迁、不复制,还在原库。MCP 则是中间的“协议桥梁”,Agent 不会直接拼 SQL 字符串连库,而是通过标准化的 MCP 工具去调用 Oracle 能力。三个角色各管一摊,互不越权,这个边界非常重要。

这个组合适合谁?如果你手头正好有以下几种情况之一,可以参考这套方案:企业内部已经有 Oracle,并且短期内不可能迁移;需要做一个能“查文档 + 查库”的智能助手;团队不想从零开发 RAG 全链路,希望用 Dify 这种平台快速落地,同时保留一定的代码扩展能力。

1.2 选型取舍:为什么不是全部自研

决定用 Dify 而不是纯代码方案,我纠结过一阵。LangGraph + FastAPI + pgvector 这套热词组合我其实也搭过,灵活性高,但知识库管理、分段、召回评估、用户界面这些都要自己写。项目周期两周,我不想把时间花在重复造轮子上。Dify 把知识库流水线、检索、工作流、Agent 节点都做好了,我只需要把 Oracle MCP Server 写好,然后拖几个节点串起来。

Oracle 的选择更不需要纠结,客户资产在那里,数据库不能动,那就让技术栈去适配数据库。很多人一听到 Oracle 就觉得老、重、不好搞,但实际上它承载的是企业最核心的交易数据。做一个数据问答系统,最大的价值恰恰来自这些历史数据。MCP 的出现让 Oracle 对接大模型这件事变得标准化了——不需要为每个客户端单独写 API 适配,写一个 MCP Server,Dify、Claude Desktop 这类支持 MCP 的客户端都能直接用。

1.3 Agentic RAG 与传统 RAG 的区别

这里必须区分一个概念。传统 RAG 是一次固定的“检索 -> 拼接 -> 生成”流程,模型本身不决定要不要检索,只是被动接收 Top K 片段。而 Agentic RAG 让模型变成“决策者”:它可以根据问题判断是走知识库检索,还是调数据库工具,还是先查库再结合文档回答,甚至可以多轮调用工具直到信息足够。

在 Dify 里实现 agentic RAG 并不难,工作流本身就是一个有限状态机。LLM 节点负责意图判断,知识检索节点负责找文档,工具节点负责调 Oracle MCP,最后再让 LLM 汇总。真正难的是让模型知道“什么时候该查库,什么时候该翻知识库”,这就需要在提示词里把工具边界写清楚。后面第四节我会给出具体的提示词模板。

2. 环境准备:从零搭好三件套

2.1 Dify 社区版部署与升级要点

Dify 社区版部署是我每次都要强调“别想太多,先跑起来”的环节。官方文档给的是 Docker Compose 方式,拉到项目目录后执行 docker compose up -d 就能起一套完整环境,包含了 API 服务、Worker、Web 前端、PostgreSQL、Redis、以及默认的向量数据库 Weaviate。

我使用的版本正好赶上 Dify 1.17.1 的更新。这个版本里工作流节点编排更顺手,Agent 节点对工具调用的控制也更细,还带了多租户方面的改进。社区版本来就有多租户的概念,只是不同工作空间的数据是隔离的,1.17.1 在权限和成员管理上做了增强。如果你在 Windows 上部署,建议直接用 Docker Desktop,然后注意文件挂载路径权限,尤其是 docker-compose.yaml 里的 volumes 目录,Windows 下经常因为权限问题导致容器启动后又退出。

部署中最容易翻车的是拉取镜像失败。Docker Hub 在国内网络环境下经常超时,解决方案是给 Docker 配置 registry mirror,或者干脆多试几次。注意,docker compose pull 之后一定要 docker compose up -d 而不是 docker-compose up,有些旧脚本版本会忽略新镜像。启动完成后,浏览器访问 http://localhost/apps 初始化管理员账号,就能看到 Dify 的控制台了。

2.2 Oracle 安装与监听服务排查

Oracle 数据库的安装是个体力活。我这次是在一台 CentOS 服务器上装的 Oracle 19c,基本步骤是:创建 oracle 用户、配置内核参数、设置环境变量 ORACLE_HOME、用 OUI 图形化安装、然后跑 DBCA 建库。如果你用官方的容器镜像 oracle/database:19c,可以跳过一大半步骤,环境变量设好,监听和实例会自动起来。

但“监听服务无法启动”是热词里出现频率极高的问题,我也没逃过。常见原因有三个:第一,listener.ora 里的 HOST 写的是主机名,但 DNS 解析不了,改成服务器的 IP 地址或 localhost 立刻就好;第二,1521 端口被别的进程占用,用 netstat -tlnp | grep 1521 查一下,杀掉冲突进程或者改 LISTENER_PORT;第三,防火墙拦了端口,firewall-cmd 里放行 1521 即可。排查顺序建议:lsnrctl status 看报错信息,再看 listener.ora,再看网络。

如果你想进入 Oracle ASM 实例管理,需要用 grid 用户执行 asmcmd,而且要确保环境变量 ORACLE_SID=+ASM 已经设置。新建用户和授权别忘了几件事:默认表空间、临时表空间、connect/resource 角色,以及给后续 MCP Server 使用的只读账号一定不要授 DBA 权限。数据库层面我还顺手整理了分页查询的写法,Oracle 12c 之前只能拼 ROWNUM,12c 之后有 OFFSET FETCH,Agent 生成的 SQL 里如果涉及分页,我会刻意提示模型用 OFFSET FETCH。

2.3 MCP Server 快速搭建

MCP 的全称是 Model Context Protocol,它解决的是“大模型如何安全地调用外部工具”这个问题。你可以把它理解成 USB-C 接口——以前每个设备都有自己的充电线,现在统一成一个标准接口,任何支持 MCP 的客户端都能插上即用。对一个开发者来说,写 MCP Server 比想象中简单。

我建议直接用 Python 的 FastMCP 库,它对标 FastAPI 的开发体验,注册工具就是装饰器的事。一个最小的 Oracle MCP Server 大概长这样:

from mcp.server.fastmcp import FastMCP import oracledb mcp = FastMCP("oracle-agent") DB_DSN = "localhost:1521/ORCLPDB1" DB_USER = "app_ro" DB_PASSWORD = "your_password" @mcp.tool() def query_oracle(sql: str, limit: int = 50) -> str: """执行只读 SQL 查询并返回结果集,仅允许 SELECT 语句。""" sql = sql.strip().rstrip(";") if not sql.lower().startswith("select"): return "Error: only SELECT statements are allowed." with oracledb.connect(user=DB_USER, password=DB_PASSWORD, dsn=DB_DSN) as conn: cur = conn.cursor() cur.execute(sql) cols = [d[0] for d in cur.description] rows = cur.fetchmany(limit) return "\t".join(cols) + "\n" + "\n".join( "\t".join(str(c) if c is not None else "NULL" for c in row) for row in rows ) if __name__ == "__main__": mcp.run(transport="streamable-http")

注意 FastMCP 的默认传输方式有很多,Dify 调用 MCP Server 一般走 HTTP 方式,也就是启动后提供 /mcp 端点。如果你只是本地调试,也可以改成 transport="stdio",这样 Claude Desktop 或命令行客户端可以直接通过标准输入输出交互。工具函数的 docstring 一定要写清楚用途,因为 MCP 协议会把函数名、参数描述、docstring 一起暴露给大模型,模型靠这些描述来决定是否调用工具。工具描述写得越具体,Agent 的意图识别准确率越高。

3. RAG 应用核心实现

3.1 知识库流水线设计

知识库的质量直接决定 RAG 应用的天花板。我遇到很多项目,模型明明很强,但回答还是稀碎,原因多半是文档没处理好。这次客户给了一批产品手册和业务规则文档,格式五花八门,有 PDF、Word、Markdown。Dify 知识库支持直接上传,但直接传 PDF 的效果往往不好,因为 PDF 里可能是扫描件或者复杂表格。

我的流水线是这样设计的:先把所有文档统一转成 Markdown 或纯文本,用脚本做清洗,去掉页眉页脚、目录、重复空行。然后按章节和段落拆分,Dify 里可以设置分段标识符和最大分段长度。分段长度我一般设 500 到 800 个字符,重叠量设 50,这样既能保留上下文,又不会让向量检索粒度太粗。分段之后,Dify 会自动调用嵌入模型把每个分段向量化,然后写入向量数据库。

有一点需要特别提醒:Dify 的知识库支持为每个分段打元数据,比如文档来源、更新时间、产品线。这些元数据可以在检索时用来过滤,效果非常明显。比如用户只问 A 产品线的问题,那检索时就带上 metadata filter,避免 B 产品线的片段干扰回答。如果你是自动同步 Oracle 里的文本字段到知识库,也建议把主键和更新时间记录下来,方便增量更新。

3.2 嵌入模型与检索策略

嵌入模型的选择很关键。如果你所在的项目环境允许调用外部大模型 API,那直接让 Dify 接入 OpenAI 兼容接口就行;如果数据敏感,必须本地化,我建议部署一个开源嵌入模型,Dify 支持通过 Ollama 或 Xinference 接入本地模型。嵌入模型的维度、语言能力会直接影响召回效果,中文场景下用国产中文嵌入模型往往比通用英文模型好一截。

检索策略不要一上来就堆高级功能。先把基础召回调通,再逐步加过滤和重排。我的实践步骤是先设置 top_k 为 5,相似度阈值 0.25 左右(不同模型分数分布不一样,要实测调整),跑几个典型问题看召回效果。如果发现检索结果里混了很多不相关片段,就调高阈值;如果漏检,就降低阈值。Dify 的检索设置里还有“多路召回”能力,可以同时命中关键词和向量,再通过 Rerank 模型重新排序。实测下来,加了 Rerank 之后,答案准确率提升非常明显,代价只是多一次模型调用延迟。

3.3 Oracle 与向量存储的取舍

这里要面对一个现实问题:Oracle 老版本没有原生向量检索能力。Oracle 23ai 推出了 AI Vector Search,支持 VECTOR 数据类型和向量索引,但客户的生产库还是 19c,不可能为了这个项目升级。我的方案是让 Oracle 继续承担业务数据存储,向量数据放到单独的向量数据库里,二者通过定时任务或实时接口同步。

具体布局是这样的:Oracle 表里的核心业务字段通过同步任务抽取到 Dify 知识库的文档分段里,或者直接通过接口写入向量库;用户问文档类问题走知识库检索,问数据类问题走 Oracle MCP 查询。如果 Oracle 表本身就是文档型数据,比如存了大量文本描述,那么可以写一个定时任务,把新增记录转成文档分段,更新到 Dify 知识库。这样 Oracle 和向量库的边界就清晰了:Oracle 是源、是事实,向量库是检索加速层。

如果你就是想少维护一套系统,另一个可行方案是用 Oracle 23ai 免费版做验证,把 VECTOR 字段建在业务表旁边,用 SQL 直接做相似度检索。但我不建议在现有生产 19c 上强行做,数据库大版本升级的风险远大于引入一个外部向量库的风险。生产环境求稳,技术选型上不必追求“一个库干所有事”。

4. Oracle MCP Agent 实现细节

4.1 工具定义与安全设计

Agent 要能操作 Oracle,首先得把 Oracle 的能力封装成一个个 MCP 工具。我封装了四个工具:query_oracle 负责只读查询,get_tables 负责列出用户有权限的表,get_schema 负责获取某张表的结构,call_procedure 负责调用经过白名单的存储过程。每个工具的描述、入参都要经过精心设计,这直接关系到模型会不会用、用得好不好。

安全设计是我在这篇文章里最想强调的部分。Agent 自动生成 SQL 去查库,这件事本身就带着风险。我的做法是给 MCP Server 单独创建一个 Oracle 只读账号,只授予 SELECT 权限;query_oracle 工具内部会先判断 SQL 是否以 SELECT 或 WITH 开头,不是就拒绝执行;同时设置 SQL 超时和返回行数上限,防止模型生成笛卡尔积或者超大查询把数据库拖垮。对于存储过程,不是所有过程都能调用,只在 call_procedure 里维护一个白名单列表,白名单之外的一律拒绝。

还有一个容易被忽略的点:返回数据里的敏感字段要脱敏。客户的库里身份证号、手机号是明文的,Agent 查询结果如果直接返回给大模型再生成回答,敏感信息就泄露了。我在 MCP Server 里加了一层字段脱敏,对 column name 里的 id_card、phone 这类字段,在返回前做掩码处理,只露出前几位和后几位。

4.2 在 Dify 编排 Agent 工作流

Dify 里实现这个 Agent,我推荐用工作流而不是直接选“Agent 应用”,因为工作流能看清楚每一步在做什么,出问题也好排查。工作流的骨架是:

  1. 开始节点接收用户问题;
  2. LLM 节点做意图判断,输出 JSON,包含 needs_db、needs_kb、db_topic 等字段;
  3. 条件分支:如果 needs_db 为 true,走 MCP 工具节点,传入从意图节点提取的表名、条件;
  4. 如果 needs_kb 为 true,走知识检索节点,把检索结果存入变量;
  5. 把工具结果和知识片段都传给最终的 LLM 节点,让它整合答案;
  6. 输出。

Dify 的 HTTP 工具节点可以直接配置成调用我本地起的 MCP Server 的 HTTP 端点,也可以直接用工具节点里的 MCP 类型,填写 endpoint 和协议。1.17.1 版本对工具的入参映射做了改进,工作流里可以把用户问题变量直接传给工具参数。这里要注意,MCP 工具入参最好用字符串模板拼接,比如把用户问题放进“请根据问题生成 SQL”的提示词里,再让一个专门写 SQL 的 LLM 节点输出 SQL,最后把 SQL 字符串传给 query_oracle。不要把原始问题直接塞给工具。

4.3 实战:一次完整的对话式数据库查询

拿一个真实问题来走一遍完整链路。用户问:“上个月 A 类产品销量前五的是哪些?顺便说说趋势。”

第一步,Dify 工作流开始节点拿到问题,传给意图识别 LLM。我的提示词模板大概是这样:

你是数据库问答助手。请判断以下问题是否需要查询数据库、是否需要检索知识库。 只输出 JSON,格式: {"needs_db": true/false, "needs_kb": true/false, "question_type": "sales_query"/"document_query"/"hybrid_query", "entities": ["上月", "A类产品", "销量"]} 问题:{{query}}

如果模型返回 needs_db 为 true,就走“生成 SQL”节点。这个节点的系统提示词里,我会把 Oracle 的关键表结构摘要放进去,比如:

表 sales_summary 字段: month VARCHAR2(7),格式 YYYY-MM product_category VARCHAR2(50) product_name VARCHAR2(100) sales_qty NUMBER sales_amount NUMBER(12,2) 请根据用户问题生成只读 SELECT 语句。注意:当前日期是 2025 年某月,'上个月'需要基于 DATE 函数或 TO_CHAR(SYSDATE, 'YYYY-MM') 计算。只输出 SQL,不要解释。

这里强调日期动态计算非常关键。如果提示词里写死“例如上月是 2024 年 12 月”,模型一到下个月就生成错误日期。用 SYSDATE 提示模型是一条稳定有效的路。模型生成的 SQL 会经过 MCP Server 的校验,然后执行,最后返回表格文本。

第三步,汇总节点拿到 query_oracle 返回的文本,把它和知识库检索到的产品介绍片段一起放进最终提示词,要求模型用自然语言总结前十名并分析趋势。实测结果基本符合预期,模型会直接把数据表转成“第一名某某产品多少件”的描述,再结合文档里的产品定位给出趋势解读。整条链路跑通后,我给客户演示时,他们最惊讶的就是“它居然真的在用我们的数据库口径回答”。

5. 常见问题与排查技巧实录

5.1 Dify 部署与升级高频问题

Dify 部署时很多人卡在 docker 镜像拉取。我常用的处理办法是设置 Docker 的 registry mirror 或者切换镜像源,然后再 docker compose pull。如果拉下来之后容器一直重启,先看日志 docker compose logs api,多半是环境变量问题,比如 SECRET_KEY 没设置或者 PostgreSQL 连接失败。Dify 的管理后台入口不是根路径,而是 /apps,登录后创建应用。遇到登录页加载不出来,先检查 Web 容器是否正常监听 3000 端口。

升级 Dify 社区版时,我强烈建议先备份 docker volume。具体做法是 docker compose down 之后,把挂载目录整个复制一份,再进行目录更新和 docker compose up -d。1.17.1 这次更新里我对工作流节点变化印象很深,旧版本创建的部分自定义节点在新版本里可能需要重新配置参数映射,所以线上环境的升级要在测试环境先跑一遍。多租户方面,不同工作空间的数据隔离是天然支持的,管理员在“设置-成员”里邀请用户加入不同空间即可。

5.2 Oracle 连接与数据格式问题

Oracle 连接出问题,先看监听。lsnrctl status 如果显示 “The listener supports no services”,说明数据库实例没有注册到监听,原因可能是数据库没启动、或者 REMOTE_LISTENER 配置不对、或者是本地服务注册延迟。此时可以用 sqlplus 登录数据库执行 ALTER SYSTEM REGISTER; 强制注册。

分页查询是另一个高频需求。Agent 生成的 SQL 里如果带分页,我提醒模型用 OFFSET FETCH 语法,因为它是标准 SQL:2008 风格,Oracle 12c 以后都支持。老项目里常见的 ROWNUM 写法在复杂查询中容易出错。

还有一个很典型的“坑”:从 Oracle 导出数据到 Excel 时,身份证号变成科学计数法。这个问题的根源是 Excel 把长数字当数值处理了。解决方案有两种:在 SQL 查询时就把身份证字段转成字符串,比如 TO_CHAR(id_card),导出 CSV 时再加一个不可见的前缀;或者在 Excel 里把列设置为文本再粘贴。在 Agent 回答场景中同样要注意,如果返回的身份证号是 NUMBER 类型,模型拿到后会显示成科学计数法,所以查字段时就要在 SQL 层面转换成字符型。

存储过程的调用我也提一句:Agent 直接调 CALL procedure_name(...) 这种语法在 Python 的 oracledb 里通常用 cursor.callproc 更稳。MCP Server 里封装 call_procedure 工具时,不要真的拼一个 SQL 字符串去执行,而是解析出过程名和参数,然后用 callproc 调用,既安全又能正确拿到 OUT 参数。

5.3 MCP 与 Agent 联调错误排查

联调阶段最容易出现的问题有三个。第一,工具调用后 Agent 长时间不返回,十有八九是 SQL 执行太慢导致超时。我给 MCP Server 设置了 10 秒超时,超过就返回错误信息,提示 Agent 换一种查询方式或缩小数据范围。第二,工具返回了结果但 Agent 只回复“无法生成回答”,这种一般是返回内容太长,把上下文窗口塞满了。解决方法是限制返回行数和字段数,或者让 MCP Server 先做一层摘要再返回。第三,权限问题,客户端拿到的 MCP 令牌过期或者没有访问该工具的权限,Dify 工具节点里配置 MCP endpoint 时记得测试连接,多数平台会直接提示错误码。

关于“Agent 画图”这类需求,也经常有人问。用 MCP Server 接一个图表生成工具其实不难,FastMCP 里再加一个函数,返回 Base64 编码的 PNG 图片,前端就能展示。但要注意,Dify 的默认输出对图片的支持有限,最好把图表生成的结果存成文件链接,再在回答里引用。这种方式适合做数据可视化报表,比如用户问“画一下近六个月销量趋势图”,Agent 先从 Oracle 查数据,再调用画图工具生成图表。

5.4 提示词与结果的持续优化

整套系统跑起来不难,难的是让回答一直准。我维护了一份“字段字典”提示词,把 Oracle 表里的业务字段、枚举值、单位都写成说明,在生成 SQL 节点里作为上下文注入。比如“sales_qty 单位是件,不含退款;product_category 的枚举值有 A/B/C 三类”。模型有了这份字典后,生成 SQL 的准确率大幅提升。

Rerank 也是值得投入的点。Dify 知识库的检索设置里可以开启 Rerank,选择一个 rerank 模型,让系统在召回后重新计算相关性。实测下来,加 Rerank 后回答的相关性打分能提升不少,特别是文档主题杂、容易互相干扰的场景。最后还要建立回归测试集,把客户常问的 30 个问题存成一个数据集,每次调整提示词或模型后跑一遍,防止改了一个问题的效果,把另外几个问题改坏了。

6. 实操心得与后续扩展

项目收尾后我复盘了一下,最值得保留的经验还是那三条:边界清晰、安全前置、提示词驱动。Dify 管编排,Oracle 管数据,MCP 管连接,三个组件各司其职,出问题的时候能很快定位到具体环节。安全不是最后才补的,而是在工具设计的第一版就考虑进去,只读账号、白名单、脱敏这三板斧缺一不可。提示词不是写一次就完事,它需要随着测试不断迭代,特别是生成 SQL 的节点,字段字典要持续维护。

这套架构后续还能扩展的方向也很多。比如在 MCP Server 里加一个执行写操作的“可控写工具”,配合审批流程,就能让 Agent 具备简单的业务办理能力;再比如把多个 Oracle 实例都封装成不同的 MCP Server,通过 Dify 的 Agent 节点统一调度,就变成了一个多数据源的企业数据助手。我后来在另一个项目里把 MCP Server 换成 LangGraph 重写,调度逻辑更灵活,但核心思路完全一致,迁移成本很低。

如果让我给还没动手的朋友一个建议:不要一上来就追求复杂,先按这篇文章搭一条最简单的链路,用 Dify 的默认配置跑通一个“文档问答 + 单表查询”的场景,再逐步加入多表、存储过程、意图分类。跑通一条链路给你带来的信心,比看十篇架构分析都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询