前面的内容中关于数据或者上下文的传递我们都是通过 Prompt 来完成的,而这样我们会面临以下挑战:
- Prompt 的内容大小限制:成本,处理耗时
- 使用大量数据的成本
- 并非所有数据都会用于解决当前问题
解决大量数据/文本的设想 - 分治
分割成数据块 -> 选取相关数据块(多次) -> 发送至大模型
如何根据用户的问题选择相关的数据块?
有一种办法就是:使用语义检索。(不同于关键词检索,关键词检索是要严格匹配上关键词才能检索,但语义检索可以检索到相关的文本)
语义检索是一种基于文本内容和意义的信息检索方法,它试图理解查询和文档的语义,
以便更准确地找到与查询相关地文档。
向量化(embedding) 是将文本数据转换为数值向量的过程。向量化后的文本可以用于计算文本之间的相似性,
如余弦相似度、欧几里得距离等度量。这使得语义检索能够根据查询和文档之间的语义相似性来对文档进行排序和检索,从而提高检索的准确性和效率。
embedding 示例 - 计算相似性
下面的例子中,我们会看到 np.dot(embedding1, embedding3)
的值是最大的,这说明 sentence1
和 sentence3
的相似性最高。
from langchain_openai import OpenAIEmbeddings import os os.environ['OPENAI_API_KEY'] = "your key" os.environ['OPENAI_BASE_URL'] = "https://api.openai-hk.com/v1" embedding = OpenAIEmbeddings() sentence1 = "我是一名软件工程师" sentence2 = "小张从事法律工作" sentence3 = "我是一名程序员" embedding1 = embedding.embed_query(sentence1) embedding2 = embedding.embed_query(sentence2) embedding3 = embedding.embed_query(sentence3) import numpy as np print(np.dot(embedding1, embedding2)) print(np.dot(embedding1, embedding3)) print(np.dot(embedding2, embedding3))
输出:
0.7987314936103257 0.9586440032651322 0.7990728512968016
numpy.dot
方法用于在机器学习中衡量两个向量的相似度。点积越大,两个向量越相似。
处理大文本
我们可以使用如下函数对我们的文本进行切割:
def split_file_into_chunks(file_path, chunk_size): chunks = [] with open(file_path, 'r', encoding='utf-8') as file: while True: chunk = file.read(chunk_size) if not chunk: break chunks.append(chunk) return chunks print(split_file_into_chunks('service_design.txt', 100))
向量化
然后,我们可以对每个文本块进行向量化:
from langchain_openai import OpenAIEmbeddings def embed(chunks): embedding = OpenAIEmbeddings() return [embedding.embed_query(chunk) for chunk in chunks] embeddings = embed(split_file_into_chunks('service_design.txt', 100))
向量检索
最后,我们可以使用向量检索来找到与查询最相关的文本块:
def find_k_largest_indices(input_list, k): """ input_list: 点积列表 k: 返回前 k 个最大值的索引 """ sorted_indices = sorted(range(len(input_list)), key=lambda i: input_list[i], reverse=True) print(input_list) print(sorted_indices) return sorted_indices[:k] def search(chunks, embeddings, top_k, txt): import numpy as np embedding = OpenAIEmbeddings(chunk_size=1) embedded_text = embedding.embed_query(txt) # 计算输入文本与每个分块的相似度 distances = [np.dot(embedded_text, emb) for emb in embeddings] # 返回前 k 个最相似的分块的索引 ret_idx = find_k_largest_indices(distances, top_k) return [chunks[i] for i in ret_idx] print(search(split_file_into_chunks('service_design.txt', 100), embeddings, 3, "字段长度"))
使用向量检索到的块来作为 prompt
上面通过向量检索拿到相关的文本块之后,我们可以将这一小部分文本块作为上下文传递给大模型。
然后就可以大模型就可以根据这些文本块来回答用户的问题。
这个时候,你就可以针对你的文档内容提问题了。
from openai import OpenAI client = OpenAI( api_key="your key", base_url="https://api.openai-hk.com/v1" ) def answer_question_with_doc(question, chunks, embeddeds): relevent_chunks = search(chunks, embeddeds, 2, question) prompt = """ 仅通过总结以下的文字片段回答用户问题,注意保持回答的语义通顺(字数在 30 字以内) --- """ for chunks in relevent_chunks: prompt = prompt + "\n'" + chunks + "'" response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": prompt}, {"role": "user", "content": question} ], temperature=0.9, max_tokens=200 ) return response.choices[0].message.content print(answer_question_with_doc("如何做字段取舍", split_chunks, embeddings))
输出:
尽可能减少字段暴露,考虑敏感信息如客户地址、手机号等,斟酌是否必要暴露,若必需则考虑脱敏和加解密。
总结
在本文中,我们讨论了如何通过对文本进行切割,然后对这些文本块进行向量化处理,
最后,再对这些文本块做向量化搜索,以找到与查询最相关的文本块。
在搜索到相关的文本块之后,我们可以将这些文本块作为上下文传递给大模型,以回答用户的问题。
优点:
- 不用传递大量的文本作为上下文给大模型。节省成本的同时也减少了大模型的处理时间。