Day30下 - RAG系统
一、入库文档,问答查询
1. 数据读取
from langchain_core.documents import Document
import random
file_name="knowledge/熬夜救星护肤霜.txt"
with open(file=file_name, mode="r", encoding="utf-8") as f:
data = f.read()
# 随机生成角色
def get_role():
if random.random() >= 0.5:
return "admin"
else:
return "user"
chunks = [chunk.strip() for chunk in data.split(sep="###") if chunk.strip()]
documents = []
for idx, chunk in enumerate(chunks, start=1):
print(chunk)
doc = Document(page_content=chunk, metadata={"role":get_role(),
"file_name":"熬夜救星护肤霜.txt",
"segment":f"第{idx}段"})
documents.append(doc)
len(documents)
2. 数据入库
from chromadb import HttpClient
from langchain_chroma import Chroma
from models import get_embed
client = HttpClient(host="localhost",port=8000)
embed = get_embed()
db = Chroma(embedding_function=embed, client=client)
db.get()
db.add_documents(documents=documents)
3. 读取查询
results = db.similarity_search_with_relevance_scores(query="熬夜救星护肤霜是谁研发的?",
k=4,
score_threshold=0.7)
my_context = []
my_docs = []
for doc, score in results:
if doc.metadata["role"] == "user":
my_docs.append(doc)
my_context.append(doc.page_content)
final_context = "\n\n".join(my_context)
def get_retrieve_result(question, role="user"):
"""
- 关于 role:
- 如果是 admin 角色,所有内容都能看
- 如果是 user 角色,不能看admin的内容
"""
# 1. 先做检索
# 粗排 100个
raw_docs = db.similarity_search_with_relevance_scores(query=question,
k=100,
score_threshold=0.5)
# 2. 结果筛选:a)处理权限 b)关键词过滤(如敏感词等)
my_docs=[]
if role == "user":
for doc, score in raw_docs:
if doc.metadata["role"] == "admin":
continue
my_docs.append(doc)
else:
my_docs = [doc for doc, score in raw_docs]
# print(my_docs)
# 精排
# 使用 rerank 模型重新计算 docs 和 question 的相似度
# 筛选出最终的几个
# 3. 拼接
context = "\n\n".join([doc.page_content for doc in my_docs])
# 4. 返回最终的结果
return context, my_docs
get_retrieve_result(question="熬夜救星护肤霜多少钱?",role="user")
4. RAG
from models import get_chat
from langchain_core.prompts import HumanMessagePromptTemplate
from langchain_core.prompts import ChatPromptTemplate
user_prompt = HumanMessagePromptTemplate.from_template("""
请根据用户从私有知识库检索出来的上下文来回答用户的问题。
请注意:
1. 如果用户的问题不在上下文中,请直接使用你的知识回答。
2. 不要做任何解释,直接输出最终结果即可。
检索出的上下文为:
{context}
用户的问题为:
{question}
答案为:
""")
prompt = ChatPromptTemplate.from_messages(messages=[user_prompt])
model = get_chat()
chain = prompt | model
question = "熬夜救星护肤霜多少钱?"
role = "admin"
context, docs = get_retrieve_result(question=question, role=role)
chain.invoke(input={"context":context,"question":question})
二、注意事项
1. 文档切分
- 前期:自动切分
- 后期:手动切分
2. 权限角色设计
- metadata 中设计一套简单易用的权限角色
3. 溯源设计
-
metadata 中设计一套简单易用的溯源策略
三、自己部署所有模型
1. 下载模型
大语言模型:https://modelscope.cn/models/Qwen/Qwen2.5-0.5B-Instruct/files
向量化模型:https://modelscope.cn/models/AI-ModelScope/bge-large-zh-v1.5
reranker模型:https://modelscope.cn/models/BAAI/bge-reranker-large
2. 安装相关环境
参考 推理开源平台:https://inference.readthedocs.io/en/latest/
pip install "xinference[all]"
pip install "xinference[transformers]"
3. 启动服务
xinference-local --host 0.0.0.0 --port 6006
4. LLM的加载
vllm 参考文档:https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html
base_url = "https://u526261-9782-276934b1.nmb1.seetacloud.com:8443/v1"
api_key = "abc123"
model = "Qwen2.5-0.5B-Instruct"
from langchain_openai import ChatOpenAI
model = ChatOpenAI(base_url=base_url, api_key=api_key, model=model)
model.invoke(input="你好")
这里的 api_key 服务端不校验,因为要传,所以必须得写,可以随意赋值。
5. 向量化模型
from langchain_community.embeddings import XinferenceEmbeddings
server_url = "https://u526261-9782-276934b1.nmb1.seetacloud.com:8443"
model_uid = "bge-base-zh-v1.5"
embed = XinferenceEmbeddings(server_url=server_url, model_uid=model_uid)
embed.embed_query(text="你好")
6. 重排序
from xinference.client import Client
rerank_url = "https://u526261-9782-276934b1.nmb1.seetacloud.com:8443"
rerank_model_uid = "bge-reranker-large"
client = Client(base_url=rerank_url)
rerank = client.get_model(model_uid=rerank_model_uid)
query = "A man is eating pasta."
corpus = [
"A man is eating food.",
"A man is eating a piece of bread.",
"The girl is carrying a baby.",
"A man is riding a horse.",
"A woman is playing violin."
]
results = rerank.rerank(corpus, query, top_n=3)
type(results)
results.keys()
results["results"]
selected_idxes = []
for result in results["results"]:
# print(result)
if result["relevance_score"] > 0.3:
selected_idxes.append(result["index"])
selected_idxes
四、思考问题
1. 已考虑私有知识库,如何再结合多轮对话呢?
每次查询的时候先去参考私有知识库,就不需要微调。
我们的知识分为三类:长生命周期的需要通过微调注入、固定期限(如:2024年的产品说明)和实时资讯。用新知识代替旧知识的时候,是存在一定风险的,因为无法精准的消除已经记住的信息。固定期限的知识,需要通过RAG系统,外挂的方式增删改查。大模型记忆的东西,很难随意更改。
启动私有知识库后,上下文会变得很大,而且是看不见的大。所有的过程都是内部执行的。
考虑多少轮合适呢?
大量无效输入的问题,如何避免呢?
2. 使用 Streamlit 构建一个简单的聊天机器人
前端应用,给客户使用的。
3. 知识管理端
给客户的管理员使用,可以增删改查里面的知识。可以修改每段文本的内容,修改里面的meta信息。
五、知识的分类
知识按生命周期分:
- 长生命周期的:
- 行业固定知识,教科书,课本,定理,公式
- 微调,注入模型
- 固定期限的:
- 2024年报销规定、法规、特定的产品信息
- RAG
- 实时信息:
- Agent