Bo's Oracle Station

【博客文章2025】Oracle Database 23ai:AI Vector Search笔记与实验6---在Oracle Database 23.5中编写一个通用函数为库内数据生成向量

2025-1-28 11:05| 发布者: admin| 查看: 105| 评论: 0|原作者: Bo Tang

摘要: 在第三方嵌入模型作为嵌入向量服务提供商的情况下,Oracle 23.5中,不支持本地服务供应商Ollama。 Ollama的确提供了丰富的Embedding模型,比如能很好地支持中文向量化的bge-m3。所以我们想在Oracle Database 23.5利用它来作为嵌入向量服务提供商。为此目的,在Oracle Database 23.5中编写一个通用函数为的库内数据生成向量。设计思想是:把文本请求通过REST协议提交给Ollama,然后通过接收Ollama所返回的嵌入向量作为该函数的输出。
【博客文章2025】Oracle Database 23ai:AI Vector Search笔记与实验6---在Oracle Database 23.5中编写一个通用函数为库内数据生成向量


    Author: Bo Tang

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; 

    编写通用函数为的库内数据生成向量(hr用户):

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]








路过

雷人

握手

鲜花

鸡蛋

QQ|手机版|Bo's Oracle Station   

GMT+8, 2025-2-22 16:06 , Processed in 0.042577 second(s), 21 queries .

返回顶部