RAG 都不用写程序了吗?
是的,Oracle 总是习惯把简单留给用户,目前已支持通过一条 SQL 就能实现基础的 RAG 需求。
本文就来深入剖析下这样的一条SQL,并展现下输出效果。
01|实现逻辑
这里先引用最近 AI 大会上的一张图,非常清晰地展现了实现逻辑:

从图片中可以看到,真的是就用这么一条SQL完成了RAG的整个流程。
02|技术拆解
虽然对于暴露给用户只有这么一条SQL,但实际上这条SQL里面调用了很多库内的函数,用户自然可以不关注,但作为技术从业者还是需要理解一下的。
① 用户问题向量化
将用户输入的问题向量化,这里直接调用 vector_embedding() 函数。
Embedding模型可以直接使用已经导入Oracle库内的onnx格式模型,比如我这里使用的是 BGE_BASE,SQL 就类似这样:
SQL
select vector_embedding(BGE_BASE USING :question_text AS DATA)
② 执行向量搜索
vector_distance() 函数
查询对应的库内文本信息,依据库内文本信息的向量 和 用户问题的向量 比较结果,选TOP N的记录出来。
实践动手时就会发现,图中有个细节是不对的,估计是工作人员的笔误。
具体在 vector_distance() 函数里面多写了 SELECT,实际应该去掉,正确SQL类似如下:
SQL
select content from t_history
order by vector_distance(v, vector_embedding(BGE_BASE USING :question_text AS DATA))
FETCH APPROX FIRST 5 ROWS ONLY
注:
- 笔者这里是Oracle 23.7的测试环境,以笔者这个版本的实际测试的结果为准
- 本文所有测试都改成笔者实际测试使用的一张表t_history
③ 组合LLM提示词
将自定义的提示词 + 用户的问题 + TOP N的记录作为上下文 组合成整体的LLM提示词。
SQL
WITH TOP5 AS (select content from t_history where type = 'English'
order by vector_distance(v, vector_embedding(BGE_BASE USING :question_text AS DATA))
FETCH APPROX FIRST 5 ROWS ONLY),
LLM_PROMPT AS (
SELECT (
'Answer this question using the following context,' || CHR(10) || CHR(10) ||
'QUESTION: ' || :question_text || CHR(10) || CHR(10) ||
'CONTEXT: ' || CHR(10) || CHR(10) ||
LISTAGG(content, CHR(10))
) AS prompt_text
FROM TOP5
)
注:
- 这里使用
||巧妙的拼接了所有需要的内容 - 使用
LISTAGG(content, CHR(10))函数将多行content数据合并成单个字符串 - 谓词条件
type = 'English'代表只关注t_history表中这个类型相关的内容
④ 获得LLM回复结果
最终调用 dbms_vector_chain.utl_to_generate_text() 获取LLM的最终回答。
理想情况下,只需要在上一步的SQL基础上,再加一段:
SQL
select dbms_vector_chain.utl_to_generate_text(prompt_text, json(:LLM_paras)) as answer
from LLM_PROMPT;
不过研究发现,这里的 dbms_vector_chain 是一个包,utl_to_generate_text 是包中的一个函数,遗憾的是目前还不能支持国内模型API直接调用。
03|照猫画虎
理解了上面的技术,我们只需要解决一个国内模型API的调用问题,就可以解决很多国内的RAG需求了。
所以这里先尝试自定义一个函数来实现:CHAT_LLM(),功能很简单就是实现调用deepseek官方的api来交互。
下面我们就模拟一个用户需求:
首先,用户已经通过系统记录了日常工作、学习内容;
现在要求实现以下需求:
- 支持用户输入自然语言描述问题
- 我们根据问题向量检索到最相关的前5条记录内容,并支持过滤指定分类
- 程序自动构造组合的提示词
- 用最终的提示词请求LLM获取最终结果
本质还是直接模仿开头的那张片子来具体实现,只需要:
最终SQL如下:
SQL
WITH TOP5 AS (select content from t_history where type = 'English'
order by vector_distance(v, vector_embedding(BGE_BASE USING :question_text AS DATA))
FETCH APPROX FIRST 5 ROWS ONLY),
LLM_PROMPT AS (
SELECT (
'Answer this question using the following context,' || CHR(10) || CHR(10) ||
'QUESTION: ' || :question_text || CHR(10) || CHR(10) ||
'CONTEXT: ' || CHR(10) || CHR(10) ||
LISTAGG(content, CHR(10))
) AS prompt_text
FROM TOP5
)
select CHAT_LLM(prompt_text) as Answer from LLM_PROMPT;
可以先在SQL命令行下测试效果:
sql
-- 首先定义变量
VARIABLE question_text VARCHAR2(100);
-- 给变量赋值
BEGIN
:question_text := '总结有关日常对话的英文对话';
END;
/
-- 完整的显示配置(新增SET ESCAPE OFF)
SET ESCAPE OFF
SET LINESIZE 32767
SET PAGESIZE 0
04|最终效果
因为TOP5格式还有点显示问题在调试ing,所以这里先用TOP1的内容输出效果展示下:

实话讲思路虽然很简单,但是自定义的函数对模型返回的格式处理还是相当头疼,即便有AI辅助也是被折腾的焦头烂额。
关于自定义的CHAT_LLM函数内容本文就先不贴了,因为调试过程中发现功能实现简单,但是细节处理还是需要太多考量的,建议等官方函数支持国内LLM,或者未来测试相对稳定了笔者再跟大家分享后续吧。