【博客文章2025】Oracle Database 23ai:AI Vector Search笔记与实验6---在Oracle Database 23.5中编写一个通用函数为库内数据生成向量
1. 利用外部REST Embedding模型:
在Oracle Database 23ai中,可以使用加载到数据库中的ONNX嵌入向量模型,来生成嵌入向量。如果要使该设置生效,那么必须先将ONNX格式的嵌入模型加载到数据库中。然而,不是随便一个ONNX的模型都可以用于这项工作,链接给出的这篇文章提供了这样一个小型化的可用的ONNX模型(22.57M参数bert架构)。互联网上能够提供最多的ONNX模型下载的网站http://github.com/onnx/models的embedding模型都是不能直接用于加载到Oracle Database 23ai到库内的。因此,如果您想要使用一个中文的嵌入模型,比如bge-large-zh-v1.5,那么首先,您需要自己将其他格式的模型,比如bge-large-zh-v1.5.zip进行复杂的转换,变成ONNX格式,才能使用。 我们想到第三方嵌入模型。但是,在第三方嵌入模型作为嵌入向量服务提供商的情况下,Oracle 23.5中,不支持本地服务供应商Ollama(注:Oracle Database 23.6才支持)进行REST API调用。就是说:Oracle Database 23.5还不能直接支持使用UTL_TO_EMBEDDING去调用Ollama作为JSON参数provider的值来运行。需要将版本升级到Oracle Database 23.6才支持。目前,我们希望在Oracle Database 23.5中实现本地服务供应商Ollama为库内数据生成向量,因为Ollama的确提供了丰富的Embedding模型,比如能很好地支持中文向量化的bge-m3: 所以我们想在Oracle Database 23.5利用它来作为嵌入向量服务提供商。做法如下: 第1步,我们要下载这个模型:
[root@station1 ~]# ollama pull bge-m3 pulling manifest pulling daec91ffb5dd... 100% ▕█████████████████████████████████████████████████████████████████████████▏ 1.2 GB pulling a406579cd136... 100% ▕█████████████████████████████████████████████████████████████████████████▏ 1.1 KB pulling 0c4c9c2a325f... 100% ▕█████████████████████████████████████████████████████████████████████████▏ 337 B verifying sha256 digest writing manifest success |
第2步,我们来测试这个Ollama模型的生成向量的能力(向量的头几节和尾几节都做了标记,以便与本文后面我们编写的函数返回的向量作对比):
[oracle@station1 ~]$ curl http://localhost:11434/api/embeddings -d '{ "model" : "bge-m3:latest", "prompt": "中文"}' {"embedding":[-1.4133219718933105,0.5260910391807556,-0.3658573627471924,-0.7420063614845276,,,.......,,-1.269050121307373,-0.028804821893572807, 0.2674172818660736,-0.2554948031902313,-1~] |
2. 在Oracle Database 23.5中编写一个通用函数为库内数据生成向量:
设计思想是:把文本请求通过REST协议提交给Ollama,然后通过接收Ollama所返回的嵌入向量作为该函数的输出。 2.1 使用apex的试错过程: 我们想到使用apex 24.1来实现上面的设计思想: 以hr用户在根容器cdb1中的pdb1_1插件数据库中执行,实验环境来自这篇博文:
create or replace function func_text_input_ollama(p_text clob) return vector is v_ollama_in clob; v_apex_out clob; begin apex_web_service.g_request_headers(1).name := 'Content-Type'; apex_web_service.g_request_headers(1).value := 'application/json'; v_ollama_in := '{ "model": "bge-m3", "prompt": "' || p_text || '" }'; v_apex_out := apex_web_service.make_rest_request( p_url => 'http://localhost:11434/api/embeddings', p_http_method => 'POST', p_body => v_ollama_in, p_transfer_timeout => 300000 ); return to_vector(replace(substr(v_apex_out,14),'}','')) ; end; /
|
上面代码中的v_ollama_in变量实现设计思想中的“把文本请求通过REST协议提交给Ollama”;v_apex_out变量实现设计思想中的“然后通过接收Ollama所返回的嵌入向量作为该函数的输出”。最后通过TO_VECTOR保证了VECTOR的格式正确。 设计思路没有问题,而且代码也能顺利编译。但是,运行它来生成向量时,由于未知的BUG,猜测:可能是当apex24.1返回一个向量型的大文本时,会出现未知的、错误的和不可避免的格式转换导致以下错误:
ORA-01841: (完整) 年份值必须介于 -4713 和 +9999 之间, 且不为 0 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEB_SERVICES", line 664 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEB_SERVICES_INVOKER", line 982 ORA-06512: 在 "SYS.UTL_HTTP", line 945 ORA-06512: 在 "SYS.UTL_HTTP", line 1443 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEB_SERVICES_INVOKER", line 256 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEB_SERVICES_INVOKER", line 972 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEB_SERVICES_INVOKER", line 1310 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEB_SERVICES", line 624 ORA-06512: 在 "APEX_240100.WWV_FLOW_WEBSERVICES_API", line 661 ORA-06512: 在 "HR.FUNC_TEXT_INPUT_OLLAMA", line 13 01841. 00000 - "(full) year must be between -4713 and +9999, and not be 0" *Cause: Illegal year entered *Action: Input year in the specified range
|
2.2 使用utl_http成功实现: 以hr用户在根容器cdb1中的pdb1_1插件数据库中执行,实验环境来自这篇博文: 如果hr用户要使用utl_http访问网页,那么首先要使用pdb1_1的sys用户对他进行授权:
grant execute on utl_http to hr;
grant execute on dbms_lock to hr;
BEGIN
DBMS_NETWORK_ACL_ADMIN.create_acl (
acl => 'local_sx_acl_file.xml',
description => 'A test of the ACL functionality',
principal => 'HR',
is_grant => TRUE,
privilege => 'connect',
start_date => SYSTIMESTAMP,
end_date => NULL);
end;
begin
DBMS_NETWORK_ACL_ADMIN.assign_acl (
acl => 'local_sx_acl_file.xml',
host => 'localhost',
lower_port => 11434,
upper_port => NULL);
end; |
create or replace function func_text_input_ollama(p_text clob) return vector is req utl_http.req; res utl_http.resp; v_url varchar2(4000) := 'http://localhost:11434/api/embeddings'; content clob := '{"model": "bge-m3:latest", "prompt": "'||p_text ||'"}'; v_data clob; begin req := utl_http.begin_request(v_url, 'POST'); utl_http.set_header(req, 'user-agent', 'mozilla/4.0'); utl_http.set_header(req, 'content-type', 'application/json'); utl_http.set_header(req, 'Content-Length',length(utl_url.escape(content,true,'UTF-8')) ); UTL_HTTP.SET_BODY_CHARSET(req,'UTF-8'); utl_http.write_text(req, content); res := utl_http.get_response(req); utl_http.read_text(res, v_data); utl_http.end_response(res); utl_http.CLOSE_PERSISTENT_CONNS; return to_vector(replace(substr(v_data,14),'}','')); exception when utl_http.end_of_body then utl_http.end_response(res); when others then utl_http.end_response(res); utl_http.CLOSE_PERSISTENT_CONNS; return null; end; |
上面代码中的content变量存储设计思想中的“把文本请求通过REST协议提交给Ollama”;v_data变量存储设计思想中的“然后通过接收Ollama所返回的嵌入向量作为该函数的输出”。 需要特别注意的是utl_http.set_header(req, 'Content-Length',length(utl_url.escape(content,true,'UTF-8')) )。这里要用utl_url处理UTF-8字符,否则由于字符长度计算错误。导致函数永远返回空值。 UTL_HTTP.SET_BODY_CHARSET(req,'UTF-8')声明返回值是UTF-8编码。这一点也非常重要。
接着,通过replace(substr(v_data,14),'}','')把函数返回结果处理成一个中括号包围的合法向量。 最后,通过TO_VECTOR保证了VECTOR的格式正确。非常重要地,为了保证在会话中,不超出http连接请求的上限,需要注意下面摘抄的这些代码:
create or replace function func_text_input_ollama(p_text clob) return vector is ...... begin ...... utl_http.end_response(res); utl_http.CLOSE_PERSISTENT_CONNS; return to_vector(replace(substr(v_data,14),'}','')); exception when utl_http.end_of_body then utl_http.end_response(res); when others then utl_http.end_response(res); utl_http.CLOSE_PERSISTENT_CONNS; return null; end; |
为了应对极端情况,在when others时,要返回null,避免不作任何返回。
2.3 函数运行结果和curl结果的验证: 由于to_vector的转换原因,函数运行结果和本文之前第1节curl结果虽然不是完全一样,但是能够验证得上。函数返回结果是一个中括号包围的合法向量。
select func_text_input_ollama('中文') from dual;
FUNC_TEXT_INPUT_OLLAMA('中文') [-1.41332197E+000,5.26091039E-001,-3.65857363E-001,-7.42006361E-001,...........,-1.26905012E+000,-2.88048219E-002, 2.67417282E-001,-2.55494803E-001, -1.30930769E+000,1.2536118E+000,-2.93488562E-001,-3.11095715E-001,-5.09210527E-001,1.40319693E+000,2.90714562E-001,-1.05767047E+000,2.87050396E-001]
|
|