| import torch |
| import locale |
| import os |
| from typing import Dict, List, Any |
| from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, BitsAndBytesConfig |
| from langchain.llms import HuggingFacePipeline |
| from langchain.retrievers.document_compressors import LLMChainExtractor |
| from langchain.retrievers import ContextualCompressionRetriever |
| from langchain.vectorstores import Chroma |
| from langchain import PromptTemplate, LLMChain |
| from langchain.chains import RetrievalQA, ConversationalRetrievalChain |
| from langchain.prompts import PromptTemplate |
| from langchain.prompts.prompt import PromptTemplate |
| from langchain.memory import ConversationBufferMemory |
| from langchain.embeddings import HuggingFaceBgeEmbeddings |
| from langchain.document_loaders import WebBaseLoader |
| from langchain.text_splitter import RecursiveCharacterTextSplitter |
| from langchain.chains.qa_with_sources import load_qa_with_sources_chain |
| from langchain.chains.combine_documents import create_stuff_documents_chain |
| from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder |
| from langchain_core.messages import HumanMessage |
| from langchain_core.output_parsers import StrOutputParser |
| from langchain_core.runnables import RunnableLambda, RunnableBranch, RunnablePassthrough |
| from operator import itemgetter |
| from langchain.schema import format_document |
| from langchain.memory import ConversationBufferMemory |
| from langchain_core.messages import AIMessage, HumanMessage, get_buffer_string |
| from langchain_core.runnables import RunnableParallel |
| from typing import Optional |
| from langchain.vectorstores import FAISS |
| from langchain_community.embeddings import HuggingFaceEmbeddings |
| from langchain_community.vectorstores.utils import DistanceStrategy |
|
|
| EMBEDDING_MODEL_NAME = "mixedbread-ai/mxbai-embed-large-v1" |
| MARKDOWN_SEPARATORS = [ |
| "\n#{1,6} ", |
| "```\n", |
| "\n\\*\\*\\*+\n", |
| "\n---+\n", |
| "\n___+\n", |
| "\n\n", |
| "\n", |
| " ", |
| "", |
| ] |
|
|
| class EndpointHandler(): |
|
|
| def __init__(self, path=""): |
| |
| urls = [ |
| "https://scholars.cityu.edu.hk/en/persons/man-hon-michael-cheung(0f913a96-a28d-47ea-848c-f444804c16f2).html", |
| "https://scholars.cityu.edu.hk/en/persons/man-hon-michael-cheung(0f913a96-a28d-47ea-848c-f444804c16f2)/publications.html", |
| "https://www.cityu.edu.hk/media/press-release/2022/05/17/cityu-council-announces-appointment-professor-freddy-boey-next-president", |
| "https://www.cityu.edu.hk/media/press-release/2023/05/18/professor-freddy-boey-installed-5th-president-cityu" |
| ] |
| |
| loader = WebBaseLoader(urls) |
| docs = loader.load() |
| |
| text_splitter = RecursiveCharacterTextSplitter.from_huggingface_tokenizer( |
| AutoTokenizer.from_pretrained(EMBEDDING_MODEL_NAME), |
| chunk_size=512, |
| chunk_overlap=int(512 / 10), |
| add_start_index=True, |
| strip_whitespace=True, |
| separators=MARKDOWN_SEPARATORS, |
| ) |
|
|
| docs_processed = [] |
| for doc in docs: |
| docs_processed += text_splitter.split_documents([doc]) |
|
|
| |
| unique_texts = {} |
| docs_processed_unique = [] |
| for doc in docs_processed: |
| if doc.page_content not in unique_texts: |
| unique_texts[doc.page_content] = True |
| docs_processed_unique.append(doc) |
|
|
| embedding_model = HuggingFaceEmbeddings( |
| model_name=EMBEDDING_MODEL_NAME, |
| multi_process=True, |
| model_kwargs={"device": "cuda"}, |
| encode_kwargs={"normalize_embeddings": True}, |
| ) |
| |
| self.vectorstore = FAISS.from_documents( |
| docs_processed_unique, embedding_model, distance_strategy=DistanceStrategy.COSINE |
| ) |
|
|
| |
| READER_MODEL_NAME = path |
|
|
| bnb_config = BitsAndBytesConfig( |
| load_in_4bit=True, |
| bnb_4bit_use_double_quant=True, |
| bnb_4bit_quant_type="nf4", |
| bnb_4bit_compute_dtype=torch.bfloat16, |
| ) |
| model = AutoModelForCausalLM.from_pretrained(READER_MODEL_NAME, quantization_config=bnb_config) |
| tokenizer = AutoTokenizer.from_pretrained(READER_MODEL_NAME) |
|
|
| |
| |
| |
| self.READER_LLM = pipeline( |
| model=model, |
| tokenizer=tokenizer, |
| task="text-generation", |
| do_sample=True, |
| temperature=0.2, |
| repetition_penalty=1.1, |
| return_full_text=False, |
| max_new_tokens=256, |
| ) |
| |
| prompt_in_chat_format = [ |
| { |
| "role": "system", |
| "content": """Using the information contained in the context. |
| Respond only to the question asked, response should be concise and relevant to the question. |
| If the answer cannot be deduced from the context, do not give an answer.""", |
| }, |
| { |
| "role": "user", |
| "content": """Context: {context} |
| Now here is the question you need to answer. |
| Question: {question}""", |
| }, |
| ] |
| |
| self.RAG_PROMPT_TEMPLATE = tokenizer.apply_chat_template( |
| prompt_in_chat_format, tokenize=False, add_generation_prompt=True |
| ) |
|
|
| def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]: |
| |
| inputs = data.pop("inputs",data) |
| date = data.pop("date", None) |
|
|
| retrieved_docs = self.vectorstore.similarity_search(query=inputs, k=2) |
| |
| retrieved_docs_text = [ |
| doc.page_content for doc in retrieved_docs |
| ] |
| context = "\nExtracted documents:\n" |
| context += "".join([f"Document {str(i)}:::\n" + doc for i, doc in enumerate(retrieved_docs_text)]) |
|
|
| final_prompt = self.RAG_PROMPT_TEMPLATE.format( |
| question=inputs, context=context |
| ) |
| |
| |
| answer = self.READER_LLM(final_prompt)[0] |
|
|
| return answer |
| |