Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	Commit 
							
							·
						
						66b0b12
	
1
								Parent(s):
							
							d161b1f
								
chainlit app
Browse files- .env.sample:Zone.Identifier +0 -0
- .gitignore +6 -0
- .gitignore:Zone.Identifier +0 -0
- Dockerfile +29 -0
- Dockerfile:Zone.Identifier +0 -0
- app.py +195 -0
- app.py:Zone.Identifier +0 -0
- chainlit.md +1 -0
- chainlit.md:Zone.Identifier +0 -0
- data/paul_graham_essays.txt +0 -0
- data/paul_graham_essays.txt:Zone.Identifier +0 -0
- pyproject.toml +22 -0
- pyproject.toml:Zone.Identifier +0 -0
- solution_app.py +190 -0
- solution_app.py:Zone.Identifier +0 -0
- uv.lock +0 -0
    	
        .env.sample:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        .gitignore
    ADDED
    
    | @@ -0,0 +1,6 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            .env
         | 
| 2 | 
            +
            __pycache__/
         | 
| 3 | 
            +
            .chainlit
         | 
| 4 | 
            +
            *.faiss
         | 
| 5 | 
            +
            *.pkl
         | 
| 6 | 
            +
            .files
         | 
    	
        .gitignore:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        Dockerfile
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # Get a distribution that has uv already installed
         | 
| 2 | 
            +
            FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # Add user - this is the user that will run the app
         | 
| 5 | 
            +
            # If you do not set user, the app will run as root (undesirable)
         | 
| 6 | 
            +
            RUN useradd -m -u 1000 user
         | 
| 7 | 
            +
            USER user
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # Set the home directory and path
         | 
| 10 | 
            +
            ENV HOME=/home/user \
         | 
| 11 | 
            +
                PATH=/home/user/.local/bin:$PATH        
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ENV UVICORN_WS_PROTOCOL=websockets
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # Set the working directory
         | 
| 16 | 
            +
            WORKDIR $HOME/app
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # Copy the app to the container
         | 
| 19 | 
            +
            COPY --chown=user . $HOME/app
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            # Install the dependencies
         | 
| 22 | 
            +
            # RUN uv sync --frozen
         | 
| 23 | 
            +
            RUN uv sync
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # Expose the port
         | 
| 26 | 
            +
            EXPOSE 7860
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            # Run the app
         | 
| 29 | 
            +
            CMD ["uv", "run", "chainlit", "run", "solution_app.py", "--host", "0.0.0.0", "--port", "7860"]
         | 
    	
        Dockerfile:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        app.py
    ADDED
    
    | @@ -0,0 +1,195 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import os
         | 
| 2 | 
            +
            import chainlit as cl
         | 
| 3 | 
            +
            from dotenv import load_dotenv
         | 
| 4 | 
            +
            from operator import itemgetter
         | 
| 5 | 
            +
            from langchain_huggingface import HuggingFaceEndpoint
         | 
| 6 | 
            +
            from langchain_community.document_loaders import TextLoader
         | 
| 7 | 
            +
            from langchain_text_splitters import RecursiveCharacterTextSplitter
         | 
| 8 | 
            +
            from langchain_community.vectorstores import FAISS
         | 
| 9 | 
            +
            from langchain_huggingface import HuggingFaceEndpointEmbeddings
         | 
| 10 | 
            +
            from langchain_core.prompts import PromptTemplate
         | 
| 11 | 
            +
            from langchain.schema.output_parser import StrOutputParser
         | 
| 12 | 
            +
            from langchain.schema.runnable import RunnablePassthrough
         | 
| 13 | 
            +
            from langchain.schema.runnable.config import RunnableConfig
         | 
| 14 | 
            +
            from tqdm.asyncio import tqdm_asyncio
         | 
| 15 | 
            +
            import asyncio
         | 
| 16 | 
            +
            from tqdm.asyncio import tqdm
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # GLOBAL SCOPE - ENTIRE APPLICATION HAS ACCESS TO VALUES SET IN THIS SCOPE #
         | 
| 19 | 
            +
            # ---- ENV VARIABLES ---- # 
         | 
| 20 | 
            +
            """
         | 
| 21 | 
            +
            This function will load our environment file (.env) if it is present.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            NOTE: Make sure that .env is in your .gitignore file - it is by default, but please ensure it remains there.
         | 
| 24 | 
            +
            """
         | 
| 25 | 
            +
            load_dotenv()
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            """
         | 
| 28 | 
            +
            We will load our environment variables here.
         | 
| 29 | 
            +
            """
         | 
| 30 | 
            +
            HF_LLM_ENDPOINT = os.environ["HF_LLM_ENDPOINT"]
         | 
| 31 | 
            +
            HF_EMBED_ENDPOINT = os.environ["HF_EMBED_ENDPOINT"]
         | 
| 32 | 
            +
            HF_TOKEN = os.environ["HF_TOKEN"]
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            # ---- GLOBAL DECLARATIONS ---- #
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            # -- RETRIEVAL -- #
         | 
| 37 | 
            +
            """
         | 
| 38 | 
            +
            1. Load Documents from Text File
         | 
| 39 | 
            +
            2. Split Documents into Chunks
         | 
| 40 | 
            +
            3. Load HuggingFace Embeddings (remember to use the URL we set above)
         | 
| 41 | 
            +
            4. Index Files if they do not exist, otherwise load the vectorstore
         | 
| 42 | 
            +
            """
         | 
| 43 | 
            +
            ### 1. CREATE TEXT LOADER AND LOAD DOCUMENTS
         | 
| 44 | 
            +
            ### NOTE: PAY ATTENTION TO THE PATH THEY ARE IN. 
         | 
| 45 | 
            +
            text_loader = TextLoader("./data/paul_graham_essays.txt")
         | 
| 46 | 
            +
            documents = text_loader.load()
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            ### 2. CREATE TEXT SPLITTER AND SPLIT DOCUMENTS
         | 
| 49 | 
            +
            text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=30)
         | 
| 50 | 
            +
            split_documents = text_splitter.split_documents(documents)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ### 3. LOAD HUGGINGFACE EMBEDDINGS
         | 
| 53 | 
            +
            hf_embeddings = HuggingFaceEndpointEmbeddings(
         | 
| 54 | 
            +
                model=HF_EMBED_ENDPOINT,
         | 
| 55 | 
            +
                task="feature-extraction",
         | 
| 56 | 
            +
                huggingfacehub_api_token=HF_TOKEN,
         | 
| 57 | 
            +
            )
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            async def add_documents_async(vectorstore, documents):
         | 
| 60 | 
            +
                await vectorstore.aadd_documents(documents)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            async def process_batch(vectorstore, batch, is_first_batch, pbar):
         | 
| 63 | 
            +
                if is_first_batch:
         | 
| 64 | 
            +
                    result = await FAISS.afrom_documents(batch, hf_embeddings)
         | 
| 65 | 
            +
                else:
         | 
| 66 | 
            +
                    await add_documents_async(vectorstore, batch)
         | 
| 67 | 
            +
                    result = vectorstore
         | 
| 68 | 
            +
                pbar.update(len(batch))
         | 
| 69 | 
            +
                return result
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            async def main():
         | 
| 72 | 
            +
                print("Indexing Files")
         | 
| 73 | 
            +
                
         | 
| 74 | 
            +
                vectorstore = None
         | 
| 75 | 
            +
                batch_size = 32
         | 
| 76 | 
            +
                
         | 
| 77 | 
            +
                batches = [split_documents[i:i+batch_size] for i in range(0, len(split_documents), batch_size)]
         | 
| 78 | 
            +
                
         | 
| 79 | 
            +
                async def process_all_batches():
         | 
| 80 | 
            +
                    nonlocal vectorstore
         | 
| 81 | 
            +
                    tasks = []
         | 
| 82 | 
            +
                    pbars = []
         | 
| 83 | 
            +
                    
         | 
| 84 | 
            +
                    for i, batch in enumerate(batches):
         | 
| 85 | 
            +
                        pbar = tqdm(total=len(batch), desc=f"Batch {i+1}/{len(batches)}", position=i)
         | 
| 86 | 
            +
                        pbars.append(pbar)
         | 
| 87 | 
            +
                        
         | 
| 88 | 
            +
                        if i == 0:
         | 
| 89 | 
            +
                            vectorstore = await process_batch(None, batch, True, pbar)
         | 
| 90 | 
            +
                        else:
         | 
| 91 | 
            +
                            tasks.append(process_batch(vectorstore, batch, False, pbar))
         | 
| 92 | 
            +
                    
         | 
| 93 | 
            +
                    if tasks:
         | 
| 94 | 
            +
                        await asyncio.gather(*tasks)
         | 
| 95 | 
            +
                    
         | 
| 96 | 
            +
                    for pbar in pbars:
         | 
| 97 | 
            +
                        pbar.close()
         | 
| 98 | 
            +
                
         | 
| 99 | 
            +
                await process_all_batches()
         | 
| 100 | 
            +
                
         | 
| 101 | 
            +
                hf_retriever = vectorstore.as_retriever()
         | 
| 102 | 
            +
                print("\nIndexing complete. Vectorstore is ready for use.")
         | 
| 103 | 
            +
                return hf_retriever
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            async def run():
         | 
| 106 | 
            +
                retriever = await main()
         | 
| 107 | 
            +
                return retriever
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            hf_retriever = asyncio.run(run())
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            # -- AUGMENTED -- #
         | 
| 112 | 
            +
            """
         | 
| 113 | 
            +
            1. Define a String Template
         | 
| 114 | 
            +
            2. Create a Prompt Template from the String Template
         | 
| 115 | 
            +
            """
         | 
| 116 | 
            +
            ### 1. DEFINE STRING TEMPLATE
         | 
| 117 | 
            +
            RAG_PROMPT_TEMPLATE = """\
         | 
| 118 | 
            +
            <|start_header_id|>system<|end_header_id|>
         | 
| 119 | 
            +
            You are a helpful assistant. You answer user questions based on provided context. If you can't answer the question with the provided context, say you don't know.<|eot_id|>
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            <|start_header_id|>user<|end_header_id|>
         | 
| 122 | 
            +
            User Query:
         | 
| 123 | 
            +
            {query}
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            Context:
         | 
| 126 | 
            +
            {context}<|eot_id|>
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            <|start_header_id|>assistant<|end_header_id|>
         | 
| 129 | 
            +
            """
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            ### 2. CREATE PROMPT TEMPLATE
         | 
| 132 | 
            +
            rag_prompt = PromptTemplate.from_template(RAG_PROMPT_TEMPLATE)
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            # -- GENERATION -- #
         | 
| 135 | 
            +
            """
         | 
| 136 | 
            +
            1. Create a HuggingFaceEndpoint for the LLM
         | 
| 137 | 
            +
            """
         | 
| 138 | 
            +
            ### 1. CREATE HUGGINGFACE ENDPOINT FOR LLM
         | 
| 139 | 
            +
            hf_llm = HuggingFaceEndpoint(
         | 
| 140 | 
            +
                endpoint_url=HF_LLM_ENDPOINT,
         | 
| 141 | 
            +
                max_new_tokens=512,
         | 
| 142 | 
            +
                top_k=10,
         | 
| 143 | 
            +
                top_p=0.95,
         | 
| 144 | 
            +
                temperature=0.3,
         | 
| 145 | 
            +
                repetition_penalty=1.15,
         | 
| 146 | 
            +
                huggingfacehub_api_token=HF_TOKEN,
         | 
| 147 | 
            +
            )
         | 
| 148 | 
            +
             | 
| 149 | 
            +
            @cl.author_rename
         | 
| 150 | 
            +
            def rename(original_author: str):
         | 
| 151 | 
            +
                """
         | 
| 152 | 
            +
                This function can be used to rename the 'author' of a message. 
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                In this case, we're overriding the 'Assistant' author to be 'Paul Graham Essay Bot'.
         | 
| 155 | 
            +
                """
         | 
| 156 | 
            +
                rename_dict = {
         | 
| 157 | 
            +
                    "Assistant" : "Paul Graham Essay Bot"
         | 
| 158 | 
            +
                }
         | 
| 159 | 
            +
                return rename_dict.get(original_author, original_author)
         | 
| 160 | 
            +
             | 
| 161 | 
            +
            @cl.on_chat_start
         | 
| 162 | 
            +
            async def start_chat():
         | 
| 163 | 
            +
                """
         | 
| 164 | 
            +
                This function will be called at the start of every user session. 
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                We will build our LCEL RAG chain here, and store it in the user session. 
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                The user session is a dictionary that is unique to each user session, and is stored in the memory of the server.
         | 
| 169 | 
            +
                """
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                ### BUILD LCEL RAG CHAIN THAT ONLY RETURNS TEXT
         | 
| 172 | 
            +
                lcel_rag_chain = {"context": itemgetter("query") | hf_retriever, "query": itemgetter("query")}| rag_prompt | hf_llm
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                cl.user_session.set("lcel_rag_chain", lcel_rag_chain)
         | 
| 175 | 
            +
             | 
| 176 | 
            +
            @cl.on_message  
         | 
| 177 | 
            +
            async def main(message: cl.Message):
         | 
| 178 | 
            +
                """
         | 
| 179 | 
            +
                This function will be called every time a message is recieved from a session.
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                We will use the LCEL RAG chain to generate a response to the user query.
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                The LCEL RAG chain is stored in the user session, and is unique to each user session - this is why we can access it here.
         | 
| 184 | 
            +
                """
         | 
| 185 | 
            +
                lcel_rag_chain = cl.user_session.get("lcel_rag_chain")
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                msg = cl.Message(content="")
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                async for chunk in lcel_rag_chain.astream(
         | 
| 190 | 
            +
                    {"query": message.content},
         | 
| 191 | 
            +
                    config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()]),
         | 
| 192 | 
            +
                ):
         | 
| 193 | 
            +
                    await msg.stream_token(chunk)
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                await msg.send()
         | 
    	
        app.py:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        chainlit.md
    ADDED
    
    | @@ -0,0 +1 @@ | |
|  | 
|  | |
| 1 | 
            +
            # FILL OUT YOUR CHAINLIT MD HERE WITH A DESCRIPTION OF YOUR APPLICATION
         | 
    	
        chainlit.md:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        data/paul_graham_essays.txt
    ADDED
    
    | The diff for this file is too large to render. 
		See raw diff | 
|  | 
    	
        data/paul_graham_essays.txt:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        pyproject.toml
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            [project]
         | 
| 2 | 
            +
            name = "15-app"
         | 
| 3 | 
            +
            version = "0.1.0"
         | 
| 4 | 
            +
            description = "Session 15 - Open Source Endpoints"
         | 
| 5 | 
            +
            readme = "README.md"
         | 
| 6 | 
            +
            requires-python = ">=3.09"
         | 
| 7 | 
            +
            dependencies = [
         | 
| 8 | 
            +
                "asyncio===3.4.3",
         | 
| 9 | 
            +
                "chainlit==2.2.1",
         | 
| 10 | 
            +
                "huggingface-hub==0.27.0",
         | 
| 11 | 
            +
                "langchain-huggingface==0.1.2",
         | 
| 12 | 
            +
                "langchain==0.3.19",
         | 
| 13 | 
            +
                "langchain-community==0.3.18",
         | 
| 14 | 
            +
                "langsmith==0.3.11",
         | 
| 15 | 
            +
                "python-dotenv==1.0.1",
         | 
| 16 | 
            +
                "tqdm==4.67.1",
         | 
| 17 | 
            +
                "langchain-openai==0.3.7",
         | 
| 18 | 
            +
                "langchain-text-splitters==0.3.6",
         | 
| 19 | 
            +
                "jupyter>=1.1.1",
         | 
| 20 | 
            +
                "faiss-cpu>=1.10.0",
         | 
| 21 | 
            +
                "websockets>=15.0",
         | 
| 22 | 
            +
            ]
         | 
    	
        pyproject.toml:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        solution_app.py
    ADDED
    
    | @@ -0,0 +1,190 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import os
         | 
| 2 | 
            +
            import chainlit as cl
         | 
| 3 | 
            +
            from dotenv import load_dotenv
         | 
| 4 | 
            +
            from operator import itemgetter
         | 
| 5 | 
            +
            from langchain_huggingface import HuggingFaceEndpoint
         | 
| 6 | 
            +
            from langchain_community.document_loaders import TextLoader
         | 
| 7 | 
            +
            from langchain_text_splitters import RecursiveCharacterTextSplitter
         | 
| 8 | 
            +
            from langchain_community.vectorstores import FAISS
         | 
| 9 | 
            +
            from langchain_huggingface import HuggingFaceEndpointEmbeddings
         | 
| 10 | 
            +
            from langchain_core.prompts import PromptTemplate
         | 
| 11 | 
            +
            from langchain.schema.output_parser import StrOutputParser
         | 
| 12 | 
            +
            from langchain.schema.runnable import RunnablePassthrough
         | 
| 13 | 
            +
            from langchain.schema.runnable.config import RunnableConfig
         | 
| 14 | 
            +
            from tqdm.asyncio import tqdm_asyncio
         | 
| 15 | 
            +
            import asyncio
         | 
| 16 | 
            +
            from tqdm.asyncio import tqdm
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # GLOBAL SCOPE - ENTIRE APPLICATION HAS ACCESS TO VALUES SET IN THIS SCOPE #
         | 
| 19 | 
            +
            # ---- ENV VARIABLES ---- # 
         | 
| 20 | 
            +
            """
         | 
| 21 | 
            +
            This function will load our environment file (.env) if it is present.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            NOTE: Make sure that .env is in your .gitignore file - it is by default, but please ensure it remains there.
         | 
| 24 | 
            +
            """
         | 
| 25 | 
            +
            load_dotenv()
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            """
         | 
| 28 | 
            +
            We will load our environment variables here.
         | 
| 29 | 
            +
            """
         | 
| 30 | 
            +
            HF_LLM_ENDPOINT = os.environ["HF_LLM_ENDPOINT"]
         | 
| 31 | 
            +
            HF_EMBED_ENDPOINT = os.environ["HF_EMBED_ENDPOINT"]
         | 
| 32 | 
            +
            HF_TOKEN = os.environ["HF_TOKEN"]
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            # ---- GLOBAL DECLARATIONS ---- #
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            # -- RETRIEVAL -- #
         | 
| 37 | 
            +
            """
         | 
| 38 | 
            +
            1. Load Documents from Text File
         | 
| 39 | 
            +
            2. Split Documents into Chunks
         | 
| 40 | 
            +
            3. Load HuggingFace Embeddings (remember to use the URL we set above)
         | 
| 41 | 
            +
            4. Index Files if they do not exist, otherwise load the vectorstore
         | 
| 42 | 
            +
            """
         | 
| 43 | 
            +
            document_loader = TextLoader("./data/paul_graham_essays.txt")
         | 
| 44 | 
            +
            documents = document_loader.load()
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=30)
         | 
| 47 | 
            +
            split_documents = text_splitter.split_documents(documents)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            hf_embeddings = HuggingFaceEndpointEmbeddings(
         | 
| 50 | 
            +
                model=HF_EMBED_ENDPOINT,
         | 
| 51 | 
            +
                task="feature-extraction",
         | 
| 52 | 
            +
                huggingfacehub_api_token=HF_TOKEN,
         | 
| 53 | 
            +
            )
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            async def add_documents_async(vectorstore, documents):
         | 
| 56 | 
            +
                await vectorstore.aadd_documents(documents)
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            async def process_batch(vectorstore, batch, is_first_batch, pbar):
         | 
| 59 | 
            +
                if is_first_batch:
         | 
| 60 | 
            +
                    result = await FAISS.afrom_documents(batch, hf_embeddings)
         | 
| 61 | 
            +
                else:
         | 
| 62 | 
            +
                    await add_documents_async(vectorstore, batch)
         | 
| 63 | 
            +
                    result = vectorstore
         | 
| 64 | 
            +
                pbar.update(len(batch))
         | 
| 65 | 
            +
                return result
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            async def main():
         | 
| 68 | 
            +
                print("Indexing Files")
         | 
| 69 | 
            +
                
         | 
| 70 | 
            +
                vectorstore = None
         | 
| 71 | 
            +
                batch_size = 32
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                batches = [split_documents[i:i+batch_size] for i in range(0, len(split_documents), batch_size)]
         | 
| 74 | 
            +
                
         | 
| 75 | 
            +
                async def process_all_batches():
         | 
| 76 | 
            +
                    nonlocal vectorstore
         | 
| 77 | 
            +
                    tasks = []
         | 
| 78 | 
            +
                    pbars = []
         | 
| 79 | 
            +
                    
         | 
| 80 | 
            +
                    for i, batch in enumerate(batches):
         | 
| 81 | 
            +
                        pbar = tqdm(total=len(batch), desc=f"Batch {i+1}/{len(batches)}", position=i)
         | 
| 82 | 
            +
                        pbars.append(pbar)
         | 
| 83 | 
            +
                        
         | 
| 84 | 
            +
                        if i == 0:
         | 
| 85 | 
            +
                            vectorstore = await process_batch(None, batch, True, pbar)
         | 
| 86 | 
            +
                        else:
         | 
| 87 | 
            +
                            tasks.append(process_batch(vectorstore, batch, False, pbar))
         | 
| 88 | 
            +
                    
         | 
| 89 | 
            +
                    if tasks:
         | 
| 90 | 
            +
                        await asyncio.gather(*tasks)
         | 
| 91 | 
            +
                    
         | 
| 92 | 
            +
                    for pbar in pbars:
         | 
| 93 | 
            +
                        pbar.close()
         | 
| 94 | 
            +
                
         | 
| 95 | 
            +
                await process_all_batches()
         | 
| 96 | 
            +
                
         | 
| 97 | 
            +
                hf_retriever = vectorstore.as_retriever()
         | 
| 98 | 
            +
                print("\nIndexing complete. Vectorstore is ready for use.")
         | 
| 99 | 
            +
                return hf_retriever
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            async def run():
         | 
| 102 | 
            +
                retriever = await main()
         | 
| 103 | 
            +
                return retriever
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            hf_retriever = asyncio.run(run())
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            # -- AUGMENTED -- #
         | 
| 108 | 
            +
            """
         | 
| 109 | 
            +
            1. Define a String Template
         | 
| 110 | 
            +
            2. Create a Prompt Template from the String Template
         | 
| 111 | 
            +
            """
         | 
| 112 | 
            +
            RAG_PROMPT_TEMPLATE = """\
         | 
| 113 | 
            +
            <|start_header_id|>system<|end_header_id|>
         | 
| 114 | 
            +
            You are a helpful assistant. You answer user questions based on provided context. If you can't answer the question with the provided context, say you don't know.<|eot_id|>
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            <|start_header_id|>user<|end_header_id|>
         | 
| 117 | 
            +
            User Query:
         | 
| 118 | 
            +
            {query}
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            Context:
         | 
| 121 | 
            +
            {context}<|eot_id|>
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            <|start_header_id|>assistant<|end_header_id|>
         | 
| 124 | 
            +
            """
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            rag_prompt = PromptTemplate.from_template(RAG_PROMPT_TEMPLATE)
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            # -- GENERATION -- #
         | 
| 129 | 
            +
            """
         | 
| 130 | 
            +
            1. Create a HuggingFaceEndpoint for the LLM
         | 
| 131 | 
            +
            """
         | 
| 132 | 
            +
            hf_llm = HuggingFaceEndpoint(
         | 
| 133 | 
            +
                endpoint_url=HF_LLM_ENDPOINT,
         | 
| 134 | 
            +
                max_new_tokens=512,
         | 
| 135 | 
            +
                top_k=10,
         | 
| 136 | 
            +
                top_p=0.95,
         | 
| 137 | 
            +
                temperature=0.3,
         | 
| 138 | 
            +
                repetition_penalty=1.15,
         | 
| 139 | 
            +
                huggingfacehub_api_token=HF_TOKEN,
         | 
| 140 | 
            +
            )
         | 
| 141 | 
            +
             | 
| 142 | 
            +
            @cl.author_rename
         | 
| 143 | 
            +
            def rename(original_author: str):
         | 
| 144 | 
            +
                """
         | 
| 145 | 
            +
                This function can be used to rename the 'author' of a message. 
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                In this case, we're overriding the 'Assistant' author to be 'Paul Graham Essay Bot'.
         | 
| 148 | 
            +
                """
         | 
| 149 | 
            +
                rename_dict = {
         | 
| 150 | 
            +
                    "Assistant" : "Paul Graham Essay Bot"
         | 
| 151 | 
            +
                }
         | 
| 152 | 
            +
                return rename_dict.get(original_author, original_author)
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            @cl.on_chat_start
         | 
| 155 | 
            +
            async def start_chat():
         | 
| 156 | 
            +
                """
         | 
| 157 | 
            +
                This function will be called at the start of every user session. 
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                We will build our LCEL RAG chain here, and store it in the user session. 
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                The user session is a dictionary that is unique to each user session, and is stored in the memory of the server.
         | 
| 162 | 
            +
                """
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                lcel_rag_chain = (
         | 
| 165 | 
            +
                    {"context": itemgetter("query") | hf_retriever, "query": itemgetter("query")}
         | 
| 166 | 
            +
                    | rag_prompt | hf_llm
         | 
| 167 | 
            +
                )
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                cl.user_session.set("lcel_rag_chain", lcel_rag_chain)
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            @cl.on_message  
         | 
| 172 | 
            +
            async def main(message: cl.Message):
         | 
| 173 | 
            +
                """
         | 
| 174 | 
            +
                This function will be called every time a message is recieved from a session.
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                We will use the LCEL RAG chain to generate a response to the user query.
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                The LCEL RAG chain is stored in the user session, and is unique to each user session - this is why we can access it here.
         | 
| 179 | 
            +
                """
         | 
| 180 | 
            +
                lcel_rag_chain = cl.user_session.get("lcel_rag_chain")
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                msg = cl.Message(content="")
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                for chunk in await cl.make_async(lcel_rag_chain.stream)(
         | 
| 185 | 
            +
                    {"query": message.content},
         | 
| 186 | 
            +
                    config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()]),
         | 
| 187 | 
            +
                ):
         | 
| 188 | 
            +
                    await msg.stream_token(chunk)
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                await msg.send()
         | 
    	
        solution_app.py:Zone.Identifier
    ADDED
    
    | 
            File without changes
         | 
    	
        uv.lock
    ADDED
    
    | The diff for this file is too large to render. 
		See raw diff | 
|  | 
