PostgreSQL码农集散地

PG向量&全文检索玩转大模型RAG-自动提示微调(prompt tuning)

文中参考文档点击 阅读原文 打开, 同时推荐2个学习环境:

1、懒人Docker镜像, 已打包200+插件: 《最好的PostgreSQL学习镜像 》

2、有web浏览器就能用的云起实验室: 《免费体验PolarDB开源数据库 》 3、PolarDB开源数据库内核、最佳实践等学习图谱:  https://www.aliyun.com/database/openpolardb/activity 关注公众号, 持续发布PostgreSQL、PolarDB、DuckDB等相关文章.

PG向量搜索&全文检索实现RAG 自动提示微调(prompt tuning)

背景

系列文章:

未来计划输出文章:

  • fine tuning模型
  • 发布模型
  • 结合 向量+全文检索+tf/idf权重 提高相关文章搜索匹配度
  • 发布提供api服务
  • 自动抓取知识 + RAG
  • 知识库的优化和整理, 提高参考素材的质量

RAG 自动提示微调(prompt tuning)

本篇来到了RAG 自动提示微调(prompt tuning)的部分, 顾名思义, 就是把第二篇导入到向量数据库的文章作为辅助回答的知识库, 辅助回答问题前让模型参考.

1、使用向量相似搜索相似问题实现自动提示微调(prompt tuning)

脚本如下

vi q.py  
  
  
import psycopg2    
import ollama    
import sys   
    
def get_embedding(text):    
    # 调用 ollama.embeddings 获取嵌入值    
    embedding = ollama.embeddings(model='mxbai-embed-large', prompt=text)    
    return embedding    
    
def query_database(embedding):    
    # 连接到 PostgreSQL 数据库    
    conn = psycopg2.connect(    
        dbname="postgres",    
        user="postgres",    
        password="123",    
        host="localhost",    
        port="55008"    
    )    
    cursor = conn.cursor()    
        
    # SQL 查询语句, 查找与标题相似的文本  ORDER BY v_prompt ... , 查找与内容相似的文本用 ORDER BY v_answer ...   
    sql_query = """    
    set local hnsw.ef_search = 1000;     
    SELECT filename, prompt, answer     
    FROM markdown_files     
    ORDER BY v_prompt <=> %s::vector(1024)     
    LIMIT 1;     
    """    
        
    # 执行查询    
    cursor.execute(sql_query, (embedding['embedding'],))    
    results = cursor.fetchall()    
        
    # 打印结果, 返回answer     
    for row in results:     
        # print(f"Filename: {row[0]}, Prompt: {row[1]}, Answer: {row[2]}")    
        print(f"Filename: {row[0]}, Prompt: {row[1]}")    
        return row[1], row[2]  
        
    # 关闭连接    
    conn.close()    
    
def main():    
    # Get the file path from the command line argument      
    if len(sys.argv) < 2:      
      print("Usage: python script.py [model] ")      
      sys.exit(1)      
  
    model = sys.argv[1]   
  
    # 读取输入问题   
    text = input("请输入你的问题: ")    
        
    # 获取嵌入值    
    embedding = get_embedding(text)    
        
    # 查询数据库并打印结果    
    prompt, answer = query_database(embedding)    
  
    output_string = f"你是一位PostgreSQL数据库资深DBA, 熟练掌握PostgreSQL数据库原理, 并有大量实践经验, 同时你的学习能力非常强, 可以快速理解新知识. 你的任务是100%准确的解答我的问题, 要求先复述问题, 并且结构清晰通熟易懂.\n下面我会把问题放在 <<<<< >>>>> 符号里面, 把新知识示例的标题放在 [[[[[ ]]]]] 符号里面, 新知识示例的内容放在 ((((( ))))) 符号里面.\n"  
  
    output_string += f"<<<<<\n请回答这个问题: {text}\n>>>>>\n"   
    output_string += f"[[[[[\n{prompt}\n]]]]]\n(((((\n{answer})))))\n"    
  
    # print(output_string)  
  
    # 询问ollama   
    output = ollama.generate(      
      model=model,      
      prompt=output_string    
    )      
      
    print(output['response'])    
    
if __name__ == "__main__":    
    main()   

无论使用prompt还是answer与问题进行向量相似搜索, 搜索到的结果都不尽人意, 可能与embedding模型有关, 未来再优化.

使用举例:

python3 q.py mistral:7b  
请输入你的问题: 请介绍一下PostgreSQL bloom索引  
Filename: 20201128_04.md, Prompt: ## PostgreSQL bloom 索引原理  

发给大模型的内容如下:

你是一位PostgreSQL数据库资深DBA, 熟练掌握PostgreSQL数据库原理, 并有大量实践经验, 同时你的学习能力非常强, 可以快速理解新知识. 你的任务是100%准确的解答我的问题, 要求先复述问题, 并且结构清晰通熟易懂.  
下面我会把问题放在 <<<<< >>>>> 符号里面, 把新知识示例的标题放在 [[[[[ ]]]]] 符号里面, 新知识示例的内容放在 ((((( ))))) 符号里面.  
<<<<<  
请回答这个问题: 请介绍一下PostgreSQL bloom索引  
>>>>>  
[[[[[  
## PostgreSQL bloom 索引原理  
]]]]]  
(((((  
20201128_04.md 这篇文章的内容  
)))))  
模型返回如下:

这是关于 PostgreSQL 中的 Bloom 索引的查询计划和属性介绍。Bloom 索引主要用于在大表上执行多个字段搜索,而同时保持数据的空间效率。它比 BRIN 索引更小(可能几十兆字节),但有一项限制,即查询结果中存在错误的行数。Bloom 索引支持查找相等条件,但查找准确性不如 BRIN 索引。与 Bloom 索引类似,hash 索引仅支持相等检查操作,并且查找准确性要优于 Bloom 索引,但 hash 索引的大小要更大(在这个例子中,对单个字段构建 hash 索引所需的空间为数百兆字节,而 hash 索引不能在多个字段上创建)。

Bloom 索引具有以下属性:

amname |     name      | pg_indexam_has_property    
--------+---------------+-------------------------  
bloom  | can_order     | f                        # 不支持排序  
bloom  | can_unique    | f                        # 索引中不存在唯一键  
bloom  | can_multi_col | t                        # 可以在多个列上创建索引  
bloom  | can_exclude   | f                        # 不支持排除列  

只能执行位图扫描操作。由于整个索引总是被扫描,因此没有意义实现一个按照 TID 返回行的普通索引访问。

     name      | pg_index_has_property    
---------------+-----------------------  
 clusterable   | f                      # 不可分群集索引  
 index_scan    | f                      # 不支持索引扫描  
 bitmap_scan   | t                      # 只支持位图扫描  
 backward_scan | f                      # 后退扫描不可用  

只有空格,因为方法无法处理 NULL。

在未来新兴的索引类型出现时,可能会续写这篇文章,但是目前已经结束了。我感谢 Postgres Professional 的同事(其中一些人是讨论过的许多访问方法的作者)阅读草稿并提供建议。我也感激于你的患无为、有价值的回复,它们鼓励了我继续走下去。谢谢!

附:这是一个使用 Bloom 索引的查询计划示例。

Bitmap Heap Scan on flights_bi (actual time=872.593..877.915 rows=2 loops=1)  
  Recheck Cond: (((passenger_id)::text = '5864 006033'::text) AND (passenger_name = 'MIROSLAV SIDOROV'::text))  
  Rows Removed by Index Recheck: 357  
  Heap Blocks: exact=356  
  ->  Bitmap Index Scan on flights_bi_bloom (actual time=832.041..832.041 rows=359 loops=1)  
        Index Cond: (((passenger_id)::text = '5864 006033'::text) AND (passenger_name = 'MIROSLAV SIDOROV'::text))  
Planning time: 0.524 ms  
Execution time: 877.967 ms  

在这个示例中,查询计划使用了 Bloom 索引来检索乘客表(flights_bi)中的行。该查询只需要处理乘客的身份证号和姓名字段,因此使用 Bloom 索引可以节省空间并提高性能。

2、使用全文检索+ts_rank排序搜索相似问题实现自动提示微调(prompt tuning)

首先创建中文分词pg_jieba插件、全文检索带ranking排序的rum插件.

create extension pg_jieba;  
create extension rum;  

创建分词索引

create index on markdown_files using rum (to_tsvector('jiebacfg', lower(prompt)) rum_tsvector_ops);   
create index on markdown_files using rum (to_tsvector('jiebacfg', lower(answer)) rum_tsvector_ops);   

使用prompt或者answer对问题进行全文检索, 并按ts_ranking排序. SQL如下:

select filename, prompt,   
to_tsvector('jiebacfg', lower(prompt)) <=> to_tsquery(replace(plainto_tsquery('jiebacfg', lower('请介绍一下PostgreSQL bloom索引'))::text, '&', '|'))  
from markdown_files   
order by to_tsvector('jiebacfg', lower(prompt)) <=> to_tsquery(replace(plainto_tsquery('jiebacfg', lower('请介绍一下PostgreSQL bloom索引'))::text, '&', '|'))  
limit 10;   
  
    filename    |                                                      prompt                                                      | ?column?    
----------------+------------------------------------------------------------------------------------------------------------------+-----------  
 20160523_01.md | ## PostgreSQL 9.6 黑科技 bloom 算法索引,一个索引支撑任意列组合查询                                              | 5.0613356  
 20210605_07.md | ## 重新发现PostgreSQL之美 - 14 bloom 布隆过滤器索引                                                              |  5.483114  
 20210326_02.md | ## PostgreSQL 14 preview - BRIN (典型IoT 时序场景) 块级索引支持 bloom filter - 随机,大量distinct value, 等值查询 |  5.483114  
 20201128_04.md | ## PostgreSQL bloom 索引原理                                                                                     |  5.483114  
 20180323_05.md | ## PostgreSQL 11 preview - BRIN索引接口功能扩展(BLOOM FILTER、min max分段)                                       |  5.483114  
 20180316_02.md | ## [未完待续] PostgreSQL hash 索引结构介绍                                                                       |  5.483114  
 20151021_01.md | ## PostgreSQL Oracle 兼容性 - Oracle反转索引 迁移到PostgreSQL可用函数索引或哈希索引                              | 6.2997475  
 20190331_08.md | ## PostgreSQL 12 preview - GiST 索引支持INCLUDE columns - 覆盖索引 - 类聚簇索引                                  | 6.9667797  
 20200522_01.md | ## PostgreSQL 用条件索引(部分索引, partial index), 旁路某些索引的使用, 例如sort                                  | 6.9667797  
 20200429_01.md | ## PostgreSQL 索引算子下推扩展 - 索引内offset - 索引内过滤 - include index - 随机偏移                            | 6.9667797  
(10 rows)  

脚本如下

vi q1.py  
  
  
import psycopg2    
import ollama    
import sys   
    
def query_database(text):    
    # 连接到 PostgreSQL 数据库    
    conn = psycopg2.connect(    
        dbname="postgres",    
        user="postgres",    
        password="123",    
        host="localhost",    
        port="55008"    
    )    
    cursor = conn.cursor()    
        
    # SQL 查询语句, 查找与标题相似的文本  ORDER BY prompt ... , 查找与内容相似的文本用 ORDER BY answer ...   
    sql_query = """    
    select filename, prompt, answer   
    from markdown_files   
    order by to_tsvector('jiebacfg', lower(prompt)) <=> to_tsquery(replace(plainto_tsquery('jiebacfg', lower(%s))::text, '&', '|'))  
    limit 1;   
    """    
        
    # 执行查询    
    cursor.execute(sql_query, (text,))    
    results = cursor.fetchall()    
        
    # 打印结果, 返回answer     
    for row in results:     
        # print(f"Filename: {row[0]}, Prompt: {row[1]}, Answer: {row[2]}")    
        print(f"Filename: {row[0]}, Prompt: {row[1]}")    
        return row[1], row[2]  
        
    # 关闭连接    
    conn.close()    
    
def main():    
    # Get the file path from the command line argument      
    if len(sys.argv) < 2:      
      print("Usage: python script.py [model] ")      
      sys.exit(1)      
  
    model = sys.argv[1]   
  
    # 读取输入问题   
    text = input("请输入你的问题: ")    
        
    # 查询数据库并打印结果    
    prompt, answer = query_database(text)    
  
    output_string = f"你是一位PostgreSQL数据库资深DBA, 熟练掌握PostgreSQL数据库原理, 并有大量实践经验, 同时你的学习能力非常强, 可以快速理解新知识. 你的任务是100%准确的解答我的问题, 要求先复述问题, 并且结构清晰通熟易懂.\n下面我会把问题放在 <<<<< >>>>> 符号里面, 把新知识示例的标题放在 [[[[[ ]]]]] 符号里面, 新知识示例的内容放在 ((((( ))))) 符号里面.\n"  
  
    output_string += f"<<<<<\n请回答这个问题: {text}\n>>>>>\n"   
    output_string += f"[[[[[\n{prompt}\n]]]]]\n(((((\n{answer})))))\n"    
  
    # print(output_string)  
  
    # 询问ollama   
    output = ollama.generate(      
      model=model,      
      prompt=output_string    
    )      
      
    print(output['response'])    
    
if __name__ == "__main__":    
    main()   

使用举例:

python3 q1.py mistral:7b  
请输入你的问题: 请介绍一下PostgreSQL bloom索引  
Filename: 20160523_01.md, Prompt: ## PostgreSQL 9.6 黑科技 bloom 算法索引,一个索引支撑任意列组合查询  

发给大模型的内容如下:

你是一位PostgreSQL数据库资深DBA, 熟练掌握PostgreSQL数据库原理, 并有大量实践经验, 同时你的学习能力非常强, 可以快速理解新知识. 你的任务是100%准确的解答我的问题, 要求先复述问题, 并且结构清晰通熟易懂.  
下面我会把问题放在 <<<<< >>>>> 符号里面, 把新知识示例的标题放在 [[[[[ ]]]]] 符号里面, 新知识示例的内容放在 ((((( ))))) 符号里面.  
<<<<<  
请回答这个问题: 请介绍一下PostgreSQL bloom索引  
>>>>>  
[[[[[  
## PostgreSQL 9.6 黑科技 bloom 算法索引,一个索引支撑任意列组合查询  
]]]]]  
(((((  
20160523_01.md 这篇文章的内容  
)))))  
模型返回如下:

您提到的Bloom过滤器是一种数据结构,用于检查元素是否存在于集合中。与传统的数据库索引不同,Bloom过滤器可以使用较少的空间来保存大量数据,并且查询速度快。

当您需要在一个表中查找多种组合时,如果没有适当的索引,那么每种组合都需要创建单独的索引。这可能会导致存储和维护成本增加。

Bloom过滤器可以在一个数据库表上创建,以便快速查找满足特定条件的记录。例如,在您的示例中,可以为表 test1 创建一个Bloom过滤器索引来支持复杂查询:
CREATE INDEX bloom_idx ON test1 USING bloom(c5, c7, c8, c10, c12);  
然后,您可以使用 btree_get_with_filter 函数来查找满足指定条件的记录:
SELECT * FROM test1 WHERE btree_get_with_filter(bloom_idx, 'c8=68747920 and c10=68747921') AND c16=68747924 AND c7=68747913 AND c5=68747914 AND c12=68747922;  

注意,Bloom过滤器有一定的误判和丢失率。这意味着在查找记录时可能会返回不相关的结果或者遗漏某些满足条件的记录。因此,它们适合用于大规模数据处理、缓存系统等场景,而不适合对准确性要求很高的应用程序。

更多信息和示例可以参考 PostgreSQL Bloom Filter Documentation。

小结

要更好的利用知识库来提高回复质量, 知识库本身的质量也非常重要. 另外就是需要提高问题与相关知识的匹配准确度, 可以结合向量+全文检索+tf/idf权重等.

未来计划输出 文章 :
  • fine tuning模型
  • 发布模型
  • 结合 向量+全文检索+tf/idf权重 提高相关文章搜索匹配度
  • 发布提供api服务
  • 自动抓取知识 + RAG
  • 知识库的优化和整理, 提高参考素材的质量


本期彩蛋- 陈老师说可以坚持3分钟, 结果大家猜一猜多久...

再次恭喜 CUUG获得PostgreSQL数据库认证与培训合作伙伴




文章中的参考文档请点击 阅读原文 获得.
欢迎关注我的github (https://github.com/digoal/blog) , 学习数据库不迷路.  近期正在写公开课材料, 未来将通过视频号推出, 欢迎关注视频号: