Files
weiyu/modules/python/app/redisVector.py

195 lines
8.4 KiB
Python
Raw Normal View History

2024-12-14 10:43:18 +08:00
'''
Author: jackning 270580156@qq.com
Date: 2024-08-29 14:49:54
LastEditors: jackning 270580156@qq.com
LastEditTime: 2024-09-03 09:14:22
Description: bytedesk.com https://github.com/Bytedesk/bytedesk
Please be aware of the BSL license restrictions before installing Bytedesk IM
selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
仅支持企业内部员工自用严禁私自用于销售二次销售或者部署SaaS方式销售
Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
contact: 270580156@qq.com
技术/商务联系270580156@qq.com
Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
'''
#
import logging
from typing import List
from langchain_redis import RedisConfig, RedisVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores.redis.filters import RedisFilter
from langchain.docstore.document import Document
from redisvl.query.filter import Tag
from zhipuai import ZhipuAI
from app.config import get_settings
from app.redisPubsub import publishAnswerMessage, publishAnswerFinished
zhipuAi = ZhipuAI(api_key=get_settings().ZHIPU_API_KEY) # 填写您自己的APIKey
redis_index_schema = {
"tag": [{"name": "kb_uid"}, {"name": "file_uid"}],
}
# https://python.langchain.com/v0.2/docs/integrations/vectorstores/redis/
class MyRedisVector:
#
embeddings: HuggingFaceEmbeddings
vector_store: RedisVectorStore
#
def __init__(self):
self.embeddings = HuggingFaceEmbeddings(
model_name=get_settings().EMBEDDINGS_PATH)
#
config = RedisConfig(
index_name=get_settings().REDIS_INDEX_NAME,
key_prefix=get_settings().REDIS_KEY_PREFIX,
redis_url=get_settings().REDIS_URL,
redis_index_schema=redis_index_schema,
metadata_schema=[
{
"name": "kb_uid",
"type": "tag",
},
{
"name": "file_uid",
"type": "tag",
}
]
)
self.vector_store = RedisVectorStore(self.embeddings, config=config)
return
def add_docs(self, docs: List[Document]) -> List[str]:
# 将docs中的文档存储到vector_store中
results = self.vector_store.add_documents(docs)
logging.info(f"add_texts result: {results}")
return results
def delete_docs(self, docIds: List[str]) -> int:
# 将docs从vector_store中删除
# 判断docIds是否为空空则返回0
if not docIds:
return 0
# 返回被删除的文档数量
return self.vector_store.index.drop_keys(docIds)
# FIXME: RuntimeError: Index has not been created. Must be created before calling search
def search_docs(self, kbUid: str, query: str) -> List[Document]:
kb_filter = Tag("kb_uid") == kbUid
results = self.vector_store.similarity_search(
query, k=3, filter=kb_filter)
# logging.info(f'search_store results {results}, {len(results)}')
return results
# 4.2 使用Retriever在向量库中搜索问题
# https://python.langchain.com/docs/integrations/vectorstores/redis#redis-as-retriever
def search_as_retriever(self, kbUid: str, query: str) -> List[Document]:
# query向量化langchain文档demo中没有向量化
# kb_filter = RedisFilter.tag("kb_uid") == kbUid
kb_filter = Tag("kb_uid") == kbUid
# 有三种搜索算法
# 1 默认算法
retriever = self.vector_store.as_retriever(
search_type="similarity",
search_kwargs={
"k": 3,
"filter": kb_filter
}
)
# 2 similarity_distance_threshold retriever which allows the user to specify the vector distance
# retriever = self.vector_store.as_retriever(
# search_type="similarity_distance_threshold",
# search_kwargs={"k": 3, "distance_threshold": 0.1,
# "filter": kb_filter},
# )
# 3 the similarity_score_threshold allows the user to define the minimum score for similar documents
# retriever = self.vector_store.as_retriever(
# search_type="similarity_score_threshold",
# search_kwargs={"score_threshold": 0.9,
# "k": 3, "filter": kb_filter},
# )
#
results = retriever.get_relevant_documents(query)
print(f'search_as_retriever results {results}, {len(results)}')
return results
# 5.Generation 将问题和搜索结果传给大模型,返回答案
def query_llm(self, messageUid: str, threadTopic: str, kbUid: str, question: str, search_results: List[Document]):
# logging.info(f'query_llm search_results {search_results}, {len(search_results)}')
# 拼接
# context = "\n".join(search_results)
context = "\n".join([doc.page_content for doc in search_results])
# logging.info(f'query_llm context {context}')
# 基于本地知识问答的提示词模
# TODO: write an english version
# <问题 > {query} < /问题 >
prompt_template = f'''
### Human: <指令>根据已知信息,简洁和专业的来回答问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题”,不允许在答案中添加编造成分,答案请使用跟问题中同样的语言。 </指令>
<已知信息>{ context }</已知信息>###
Assistant:'''
print(f'prompt_template: { { prompt_template } }')
# https://open.bigmodel.cn/dev/api#sdk_example
response = zhipuAi.chat.completions.create(
model="glm-4-flash", # 免费版
# prompt=[{"role": "user", "content": prompt_template}],
messages=[
{"role": "system", "content": prompt_template},
{"role": "user", "content": question},
],
top_p=0.7,
temperature=0.3,
stream=True
)
#
counter = 0
for chunk in response:
# ChatCompletionChunk(id='202408291708554ae5d8eac6e94630', choices=[Choice(delta=ChoiceDelta(content='凡', role='assistant', tool_calls=None), finish_reason=None, index=0)], created=1724922535, model='glm-4-air', usage=None, extra_json=None)
# ChatCompletionChunk(id='202408291708554ae5d8eac6e94630', choices=[Choice(delta=ChoiceDelta(content='', role='assistant', tool_calls=None), finish_reason='stop', index=0)], created=1724922535, model='glm-4-air', usage=CompletionUsage(prompt_tokens=453, completion_tokens=34, total_tokens=487), extra_json=None)
# print(f'chunk: { chunk }')
# INFO: query_llm chunk: , stop, glm-4-air, CompletionUsage(prompt_tokens=453, completion_tokens=34, total_tokens=487)
#
counter += 1
id_to_publish = counter
#
answer = chunk.choices[0].delta.content
model = chunk.model
created = chunk.created
finish_reason = chunk.choices[0].finish_reason
logging.info(
f'query_llm: {counter} {chunk.choices[0].delta.content}, {chunk.choices[0].finish_reason}, {chunk.model}, {chunk.created}, {chunk.usage}')
if finish_reason == 'stop':
promptTokens = chunk.usage.prompt_tokens
completionTokens = chunk.usage.completion_tokens
totalTokens = chunk.usage.total_tokens
publishAnswerFinished(
id=id_to_publish,
uid=messageUid,
threadTopic=threadTopic,
kbUid=kbUid,
question=question,
answer=answer,
model=model,
created=created,
promptTokens=promptTokens,
completionTokens=completionTokens,
totalTokens=totalTokens
)
else:
publishAnswerMessage(
id=id_to_publish,
uid=messageUid,
threadTopic=threadTopic,
kbUid=kbUid,
question=question,
answer=answer,
model=model,
created=created
)
myredisVector = MyRedisVector()
#