{"id":112123,"date":"2025-05-28T09:05:00","date_gmt":"2025-05-28T16:05:00","guid":{"rendered":"https:\/\/www.backblaze.com\/blog\/?p=112123"},"modified":"2025-05-27T13:51:38","modified_gmt":"2025-05-27T20:51:38","slug":"building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain","status":"publish","type":"post","link":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/","title":{"rendered":"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"6000\" height=\"3417\" src=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png\" alt=\"A decorative image showing a cloud with the Backblaze logo with code imagery. \" class=\"wp-image-112124\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png 6000w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer-300x171.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer-1024x583.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer-768x437.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer-1536x875.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer-2048x1166.png 2048w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer-1568x893.png 1568w\" sizes=\"auto, (max-width: 6000px) 100vw, 6000px\" \/><\/figure>\n\n\n\n<div style=\"height:15px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>In an earlier blog post, I explained <a href=\"https:\/\/www.backblaze.com\/blog\/how-to-build-your-own-llm-with-backblaze-b2-jupyter-notebook\/\" target=\"_blank\" rel=\"noreferrer noopener\">how to build your own LLM with Backblaze B2 + Jupyter Notebook<\/a>, implementing a simple conversational AI chatbot using the LangChain AI framework to implement retrieval-augmented generation (RAG). The notebook walks you through the process of loading PDF files from a Backblaze B2 Bucket into a vector store, running a local instance of a large language model (LLM) and combining those to form a chatbot that can answer questions on its specialist subject.<\/p>\n\n\n\n<p>That article generated a <em>lot<\/em> of interest, and a few questions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u201cCould you make this into a web app, like ChatGPT?\u201d<\/li>\n\n\n\n<li>\u201cCould you use this with OpenAI? DeepSeek?\u201d<\/li>\n\n\n\n<li>\u201cCould I load multiple collections of documents into this?\u201d<\/li>\n\n\n\n<li>\u201cCould I run multiple LLMs and compare them?\u201d<\/li>\n\n\n\n<li>\u201cCan I add new documents to the vector store as they are uploaded to the bucket?\u201d<\/li>\n<\/ul>\n\n\n\n<p>The answer to all of these questions is \u201cYes!\u201d<\/p>\n\n\n\n<p>Today, I\u2019ll present a simple conversational AI chatbot web app with a ChatGPT-style UI that you can easily configure to work with OpenAI, DeepSeek, or any of a range of other LLMs. In future blog posts, I\u2019ll extend this to allow you to configure multiple LLMs and document collections, and integrate with Backblaze B2\u2019s <a href=\"https:\/\/www.backblaze.com\/docs\/cloud-storage-event-notifications\" target=\"_blank\" rel=\"noreferrer noopener\">Event Notifications<\/a> feature to load documents into the vector store within seconds of them being uploaded.<\/p>\n\n\n\n<p>And, here\u2019s a very short video of the chatbot in action:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain\" width=\"750\" height=\"422\" src=\"https:\/\/www.youtube.com\/embed\/vBFwUGL_DaQ?feature=oembed&#038;enablejsapi=1&#038;origin=https:\/\/backblazeprod.wpenginepowered.com\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<div class=\"abstract\" style=\"line-height: 1.8; margin: 24px 12px; padding: 24px 12px 10px 12px;\">Editorial note: A version of <a href=\"https:\/\/thenewstack.io\/a-practical-guide-to-building-a-rag-powered-chatbot\/\" rel=\"noopener\" \"nofollow\" target=\"_blank\">this article was previously published<\/a> on the New Stack.<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">RAG basics<\/h2>\n\n\n\n<p><a href=\"https:\/\/www.backblaze.com\/blog\/ai-101-why-rag-is-all-the-rage\/\" target=\"_blank\" rel=\"noreferrer noopener\">Retrieval-augmented generation<\/a>, or RAG for short, is a technique that applies the generative features of an LLM to a collection of documents, resulting in a chatbot that can effectively answer questions based on the content of those documents.<\/p>\n\n\n\n<p>A typical RAG implementation splits each document in the collection into a number of roughly equal-sized, overlapping chunks, and generates an <a href=\"https:\/\/stackoverflow.blog\/2023\/11\/09\/an-intuitive-introduction-to-text-embeddings\/\" target=\"_blank\" rel=\"noreferrer noopener\">embedding<\/a> for each chunk. Embeddings are vectors (lists) of floating point numbers with hundreds or thousands of dimensions. The distance between two vectors indicates their similarity. Small distances indicate high similarity and large distances indicate low similarity.<\/p>\n\n\n\n<p>The RAG app then loads each chunk, along with its embedding, into a vector store. The vector store is a special-purpose database that can perform a similarity search\u2013given a piece of text, the vector store can retrieve chunks ranked by their similarity to the query text by comparing the embeddings.<\/p>\n\n\n\n<p>Let\u2019s put the pieces together:<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69e51bdd12a5f&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69e51bdd12a5f\" class=\"wp-block-image size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2515\" height=\"2119\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram.png\" alt=\"A diagram showing the logic of a RAG model chatbot. \" class=\"wp-image-112125\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram.png 2515w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram-300x253.png 300w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram-1024x863.png 1024w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram-768x647.png 768w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram-1536x1294.png 1536w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram-2048x1726.png 2048w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Diagram-1568x1321.png 1568w\" sizes=\"auto, (max-width: 2515px) 100vw, 2515px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<div style=\"height:10px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>Given a question from the user (1), the RAG app can query the vector store for chunks of text that are similar to the question (2). This will be the <strong>context<\/strong> that helps the LLM answer the user\u2019s question. Here\u2019s a concrete example using the Backblaze documentation collection: Given the question, \u201cTell me about object lock,\u201d the vector store returns four document chunks, each of about 170 words, to the app (3). Here is a link to the text of, and a short extract from, each chunk:<\/p>\n\n\n\n<ul style=\"background-color:#e6e3ff\" class=\"wp-block-list has-background\">\n<li><a href=\"https:\/\/www.backblaze.com\/docs\/en\/cloud-storage-object-lock#:~:text=before%20the%20set,on%20the%20file.\" target=\"_blank\" rel=\"noreferrer noopener\">Object Lock<\/a>: With Object Lock Legal Hold, files are prevented from being changed or deleted, but the immutability does not have a defined retention period.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.backblaze.com\/docs\/en\/cloud-storage-object-lock#:~:text=From%20a%20functionality,than%20I%20wanted%3F\" target=\"_blank\" rel=\"noreferrer noopener\">Object Lock<\/a>: You can enable Object Lock on a bucket when you create a new bucket or on an existing bucket.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.backblaze.com\/docs\/en\/cloud-storage-object-lock#:~:text=Backblaze%20B2%20SDKs,check%20the%20retention\" target=\"_blank\" rel=\"noreferrer noopener\">Object Lock<\/a>: Object Lock in Backblaze B2 was designed with the Immutability feature in Veeam Backup &amp; Replication in mind.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.backblaze.com\/docs\/en\/cloud-storage-enable-object-lock-with-the-s3-compatible-api#:~:text=Powered%20by-,Enable%20Object%20Lock%20with%20the%20S3%2DCompatible%20API,Backblaze%20API%20operations%20and%20their%20corresponding%20documentation%2C%20see%20API%20Documentation.,-Was%20this%20article\" target=\"_blank\" rel=\"noreferrer noopener\">Enable Object Lock with the S3 Compatible API<\/a>: To enable Object Lock on an existing S3 compatible bucket, add the x-amz-bucket-object-lock-token header with a value of 1 to the S3 Put Object Lock Configuration API call.<\/li>\n<\/ul>\n\n\n\n<p>Unsurprisingly, three of the four results are extracts from the main documentation article on Object Lock.<\/p>\n\n\n\n<p>In order to provide a conversational experience, the RAG app maintains a chat history in a message store. The RAG app retrieves the message history (4) to form part of the query it will send to the LLM.<\/p>\n\n\n\n<p>A system prompt sets the guidelines for the LLM to generate responses. In a RAG, the system prompt typically specifies that the response should be based on retrieved document data rather than model memorization, and attempts to prevent hallucinations by ensuring that the model does not fabricate information if no relevant documents are found. Here is the system prompt from the RAG app:<\/p>\n\n\n\n<p><code>Use the following pieces of context and the message history to answer the question at the end. If you don't know the answer,\u00a0 just say that you don't know, don't try to make up an answer.<\/code><\/p>\n\n\n\n<p>The RAG submits the system prompt, the context, the last few messages in the conversation, and the user\u2019s question to the LLM (5), which generates a, hopefully useful, response (6). Here\u2019s the answer it provided to the above question:<\/p>\n\n\n\n<div class=\"wp-block-group has-background\" style=\"background-color:#e6e3ff\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p>Object Lock is a feature that allows users to enforce immutability on files stored in cloud storage, specifically in services like Backblaze B2 and AWS S3. Here are the key points about Object Lock:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Immutability<\/strong>: Once a file is locked, it cannot be modified or deleted until a specified retention period expires or a legal hold is lifted.<\/li>\n\n\n\n<li><strong>Retention Periods<\/strong>: Users can set a retention period when locking a file. During this time, the file remains immutable, meaning it cannot be changed or deleted.<\/li>\n\n\n\n<li><strong>Legal Hold<\/strong>: Users can place files under a legal hold, which prevents modifications or deletions indefinitely until the hold is removed.<\/li>\n\n\n\n<li><strong>Enabling Object Lock<\/strong>: Object Lock can be enabled for new buckets or existing ones, but it must be turned on at the bucket level before files are uploaded.<\/li>\n\n\n\n<li><strong>Integrations<\/strong>: Some applications, like Veeam Backup &amp; Replication, offer built-in support for Object Lock, making the process easier for users when they want to ensure file immutability during backups.<\/li>\n\n\n\n<li><strong>No Extra Cost<\/strong>: There is no additional fee for using Object Lock; however, standard storage charges for the locked files still apply.<\/li>\n\n\n\n<li><strong>Mistakes in Locking<\/strong>: If a user locks a file for longer than intended, they may need to contact support to resolve the situation, which could involve closing their storage account in some cases.<\/li>\n<\/ol>\n\n\n\n<p>Overall, Object Lock is a crucial feature for organizations that need to ensure data integrity and compliance with regulations concerning data retention.<\/p>\n<\/div><\/div>\n\n\n\n<p>The RAG app adds the user\u2019s question and the LLM\u2019s response to the message store (7), returns the answer to the user (8), and awaits the next question.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A quick tour of the sample app<\/h2>\n\n\n\n<p>The sample app is on GitHub at <a href=\"https:\/\/github.com\/backblaze-b2-samples\/ai-rag-app\">https:\/\/github.com\/backblaze-b2-samples\/ai-rag-app<\/a>. The app is open source, under the MIT license, so you can use it as a basis for your own experimentation without any restrictions. The app was originally written to demonstrate RAG with Backblaze B2 Cloud Storage, but it works with any S3 compatible object store.&nbsp;&nbsp;<\/p>\n\n\n\n<p>The README file covers configuration and deployment in some detail; in this blog post, I\u2019ll just give you a high-level overview. The sample app is written in Python using the <a href=\"https:\/\/www.djangoproject.com\/\">Django web framework<\/a>. API credentials and related settings are configured via environment variables, while the LLM and vector store are configured via Django\u2019s <code>settings.py<\/code> file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CHAT_MODEL: ModelSpec = {<br \/>    'name': 'OpenAI',<br \/>    'llm': {<br \/>        'cls': ChatOpenAI,<br \/>        'init_args': {<br \/>            'model': \"gpt-4o-mini\",<br \/>        }<br \/>    },<br \/>}<br \/><br \/># Change source_data_location and vector_store_location to match your environment<br \/># search_k is the number of results to return when searching the vector store<br \/>DOCUMENT_COLLECTION: CollectionSpec = {<br \/>    'name': 'Docs',<br \/>    'source_data_location': 's3:\/\/blze-ev-ai-rag-app\/pdfs',<br \/>    'vector_store_location': 's3:\/\/blze-ev-ai-rag-app\/vectordb\/docs\/openai',<br \/>    'search_k': 4,<br \/>    'embeddings': {<br \/>        'cls': OpenAIEmbeddings,<br \/>        'init_args': {<br \/>            'model': \"text-embedding-3-large\",<br \/>        },<br \/>    },<br \/>}<br \/><\/pre>\n\n\n\n<p>The sample app is configured to use OpenAI GPT-4o mini, but the README explains how to use different online LLMs such as DeepSeek V3 or Google Gemini 2.0 Flash, or even a local LLM such as Meta Llama 3.1 via the <a href=\"https:\/\/ollama.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ollama<\/a> framework. If you do run a local LLM, be sure to pick a model that fits your hardware. I tried running Meta\u2019s Llama 3.3, which has 70 billion parameters (70B), on my MacBook Pro with the M1 Pro CPU. It took nearly three hours to answer a single question! Llama 3.1 8B was a much better fit, answering questions in less than 30 seconds.<\/p>\n\n\n\n<p>Notice that the document collection is configured with the location of a vector store containing the Backblaze documentation as a sample dataset. The README file contains an application key with read-only access to the PDFs and vector store so you can try the application without having to load your own set of documents.<\/p>\n\n\n\n<p>If you want to use your own document collection, a pair of custom commands allow you to load them from a Backblaze B2 Bucket into the vector store and then query the vector store to test that it all worked.<\/p>\n\n\n\n<p>First, you need to load your data:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">% python manage.py load_vector_store<br \/>Deleting existing LanceDB vector store at s3:\/\/blze-ev-ai-rag-app\/vectordb\/docs<br \/>Creating LanceDB vector store at s3:\/\/blze-ev-ai-rag-app\/vectordb\/docs<br \/>Loading data from s3:\/\/blze-ev-ai-rag-app\/pdfs in pages of 1000 results<br \/>Successfully retrieved page 1 containing 618 result(s) from s3:\/\/blze-ev-ai-rag-app\/pdfs<br \/>Skipping pdfs\/.bzEmpty<br \/>Skipping pdfs\/cloud_storage\/.bzEmpty<br \/>Loading pdfs\/cloud_storage\/cloud-storage-about-backblaze-b2-cloud-storage.pdf<br \/>Loading pdfs\/cloud_storage\/cloud-storage-add-file-information-with-the-native-api.pdf<br \/>Loading pdfs\/cloud_storage\/cloud-storage-additional-resources.pdf<br \/>...<br \/>Loading pdfs\/v1_api\/s3-put-object.pdf<br \/>Loading pdfs\/v1_api\/s3-upload-part-copy.pdf<br \/>Loading pdfs\/v1_api\/s3-upload-part.pdf<br \/>Loaded batch of 614 document(s) from page<br \/>Split batch into 2758 chunks<br \/>[2025-02-28T01:26:11Z WARN  lance_table::io::commit] Using unsafe commit handler. Concurrent writes may result in data loss. Consider providing a commit handler that prevents conflicting writes.<br \/>Added chunks to vector store<br \/>Added 614 document(s) containing 2758 chunks to vector store; skipped 4 result(s).<br \/>Created LanceDB vector store at s3:\/\/blze-ev-ai-rag-app\/vectordb\/docs. \"vectorstore\" table contains 2758 rows<br \/><\/pre>\n\n\n\n<p>Now you can verify that the data is stored by querying the vector store. Notice how the raw results from the vector store include an S3 URI identifying the source document:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">% python manage.py search_vector_store 'Which B2 native APIs would I use to upload large files?' <br \/>2025-03-01 02:38:07,740 ai_rag_app.management.commands.search INFO     Opening vector store at s3:\/\/blze-ev-ai-rag-app\/vectordb\/docs\/openai<br \/>2025-03-01 02:38:07,740 ai_rag_app.utils.vectorstore DEBUG    Populating AWS environment variables from the b2 profile<br \/>Found 4 docs in 2.30 seconds<br \/>2025-03-01 02:38:11,074 ai_rag_app.management.commands.search INFO     <br \/>page_content='Parts of a large file can be uploaded and copied in parallel, which can significantly reduce the time it takes to upload terabytes of data. Each part can be  anywhere from 5 MB to 5 GB, and you can pick the size that is most convenient for your application. For best upload performance, Backblaze recommends that you use the recommendedPartSize parameter that is returned by the b2_authorize_account operation.  To upload larger files and data sets, you can use the command-line interface (CLI), the Native API, or an integration, such as Cyberduck.  Usage for Large Files  Generally, large files are treated the same as small files. The costs for the API calls are the same.  You are charged for storage for the parts that you uploaded or copied. Usage is counted from the time the part is stored. When you call the b2_finish_large_file' metadata={'source': 's3:\/\/blze-ev-ai-rag-app\/pdfs\/cloud_storage\/cloud-storage-large-files.pdf'}<br \/>...<br \/><\/pre>\n\n\n\n<p>The core of the sample application is <a href=\"https:\/\/github.com\/backblaze-b2-samples\/ai-rag-app\/blob\/main\/ai_rag_app\/rag.py\" target=\"_blank\" rel=\"noreferrer noopener\">the RAG class<\/a>. There are several methods that create the basic components of the RAG, but here we\u2019ll look at how the _create_chain() method brings together the system prompt, vector store, message history, and LLM.<\/p>\n\n\n\n<p>First, we define the system prompt, which includes a placeholder for the context\u2014those chunks of text that the RAG will retrieve from the vector store:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># These are the basic instructions for the LLM<br \/>system_prompt = (<br \/>    \"Use the following pieces of context and the message history to \"<br \/>    \"answer the question at the end. If you don't know the answer, \"<br \/>    \"just say that you don't know, don't try to make up an answer. \"<br \/>    \"\\n\\n\"<br \/>    \"Context: {context}\"<br \/>)<br \/><\/pre>\n\n\n\n<p>Then we create a prompt template that brings together the system prompt, message history, and the user\u2019s question:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># The prompt template brings together the system prompt, context, message history and the user's question<br \/>prompt_template = ChatPromptTemplate(<br \/>    [<br \/>        (\"system\", system_prompt),<br \/>        MessagesPlaceholder(variable_name=\"history\", optional=True, n_messages=10),<br \/>        (\"human\", \"{question}\"),<br \/>    ]<br \/>)<br \/><\/pre>\n\n\n\n<p>Now we use <a href=\"https:\/\/python.langchain.com\/docs\/concepts\/lcel\/\" target=\"_blank\" rel=\"noreferrer noopener\">LangChain Expression Language (LCEL)<\/a> to bring the various components together to form a chain. LCEL allows us to define a chain of components declaratively; that is, we provide a high-level representation of the chain we want, rather than specifying how the components should fit together.\u00a0<\/p>\n\n\n\n<p>Notice the <code>log_data()<\/code> helper method\u2014it simply logs its input and passes it on to the next component in the chain.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># Create the basic chain<br \/># When loglevel is set to DEBUG, log_input will log the results from the vector store<br \/>chain = (<br \/>    {<br \/>        \"context\": (<br \/>            itemgetter(\"question\")<br \/>            | retriever<br \/>            | log_data('Documents from vector store', pretty=True)<br \/>        ),<br \/>        \"question\": itemgetter(\"question\"),<br \/>        \"history\": itemgetter(\"history\"),<br \/>    }<br \/>    | prompt_template<br \/>    | model<br \/>    | log_data('Output from model', pretty=True)<br \/>)<br \/><\/pre>\n\n\n\n<p>Assigning a name to the chain allows us to add instrumentation when we invoke it:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># Give the chain a name so the handler can see it<br \/>named_chain: Runnable[Input, Output] = chain.with_config(run_name=\"my_chain\")<br \/><\/pre>\n\n\n\n<p>Now, we use LangChain\u2019s <code>RunnableWithMessageHistory<\/code> class to manage adding and retrieving messages from the message store:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># Add message history management<br \/>return RunnableWithMessageHistory(<br \/>    named_chain,<br \/>    lambda session_id: RAG._get_session_history(store, session_id),<br \/>    input_messages_key=\"question\",<br \/>    history_messages_key=\"history\",<br \/>)<br \/><\/pre>\n\n\n\n<p>Finally, the <code>log_chain()<\/code> function prints an ASCII representation of the chain to the debug log:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">log_chain(history_chain, logging.DEBUG, {\"configurable\": {'session_id': 'dummy'}})<\/pre>\n\n\n\n<p>This is the output:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69e51bdd132e3&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69e51bdd132e3\" class=\"aligncenter size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"662\" height=\"1296\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Output.png\" alt=\"An image of the RAG output from this chatbot. \" class=\"wp-image-112126\" srcset=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Output.png 662w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Output-153x300.png 153w, https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/Backblaze_AI-Chatbot-Output-523x1024.png 523w\" sizes=\"auto, (max-width: 662px) 100vw, 662px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n<\/div>\n\n\n<div style=\"height:10px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>The RAG class\u2019 <code>invoke()<\/code> function, in contrast, is very simple. Here is the key section of code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">response = self._chain.invoke(<br \/>    {\"question\": question},<br \/>    config={<br \/>        \"configurable\": {<br \/>            \"session_id\": session_key<br \/>        },<br \/>        \"callbacks\": [<br \/>            ChainElapsedTime(\"my_chain\")<br \/>        ]<br \/>    },<br \/>)<br \/><\/pre>\n\n\n\n<p>The input to the chain is a Python dictionary containing the question, while the <code>config<\/code> argument configures the chain with the Django session key and a callback that annotates the chain output with its execution time. Since the chain output contains <a href=\"https:\/\/www.markdownguide.org\/getting-started\/\" target=\"_blank\" rel=\"noreferrer noopener\">Markdown<\/a> formatting, the API endpoint that handles requests from the front end uses the open source <a href=\"https:\/\/github.com\/markdown-it\/markdown-it\" target=\"_blank\" rel=\"noreferrer noopener\">markdown-it library<\/a> to render the output to HTML for display.<\/p>\n\n\n\n<p>The remainder of the code is mostly concerned with rendering the web UI. One interesting facet is that the Django view, responsible for rendering the UI as the page loads, uses the RAG\u2019s message store to render the conversation, so if you reload the page, you don\u2019t lose your context.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Take this code and run it!<\/h2>\n\n\n\n<p>The <a href=\"https:\/\/github.com\/backblaze-b2-samples\/ai-rag-app\">sample AI RAG application<\/a> is open source under the MIT license, and I encourage you to use it as the basis for your own RAG exploration. The README file suggests a few ways you could extend it, and I also draw your attention to conclusion of the README if you are thinking of running the app in production:<\/p>\n\n\n\n<div class=\"wp-block-group has-background\" style=\"background-color:#e6e3ff\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p>[&#8230;] in order to get you started quickly, we streamlined the application in several ways. There are a few areas to attend to if you wish to run this app in a production setting:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The app does not use a database for user accounts, or any other data, so there is no authentication. All access is anonymous. If you wished to have users log in, you would need to restore Django&#8217;s <a href=\"https:\/\/docs.djangoproject.com\/en\/5.1\/ref\/middleware\/#module-django.contrib.auth.middleware\" target=\"_blank\" rel=\"noreferrer noopener\">AuthenticationMiddleware<\/a> class to the MIDDLEWARE configuration and <a href=\"https:\/\/docs.djangoproject.com\/en\/5.1\/ref\/databases\/\" target=\"_blank\" rel=\"noreferrer noopener\">configure a database<\/a>.<\/li>\n\n\n\n<li>Sessions are stored in memory. As explained above, <a href=\"https:\/\/github.com\/backblaze-b2-samples\/ai-rag-app#running-in-gunicorn\" target=\"_blank\" rel=\"noreferrer noopener\">you can use Gunicorn to scale the application to multiple threads<\/a>, but you would need to <a href=\"https:\/\/docs.djangoproject.com\/en\/5.1\/topics\/http\/sessions\/\" target=\"_blank\" rel=\"noreferrer noopener\">configure a Django session backend<\/a> to run the app in multiple processes or on multiple hosts.<\/li>\n\n\n\n<li>Similarly, conversation history is stored in memory, so you would need to use a persistent message history implementation, such as <a href=\"https:\/\/python.langchain.com\/api_reference\/redis\/chat_message_history\/langchain_redis.chat_message_history.RedisChatMessageHistory.html\" target=\"_blank\" rel=\"noreferrer noopener\">RedisChatMessageHistory<\/a> to run the app in multiple processes or on multiple hosts.<\/li>\n<\/ul>\n<\/div><\/div>\n\n\n\n<div style=\"height:10px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>Above all, have fun! AI is a rapidly evolving technology, with vendors and open source projects releasing new capabilities every day. I hope you find this app a useful way of jumping in.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Build your own conversational AI chatbot with this easy how-to from Backblaze Chief Technical Evangelist, Pat Patterson. <\/p>\n","protected":false},"author":174,"featured_media":112124,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[7,434,438,483],"tags":[489,468],"class_list":["post-112123","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cloud-storage","category-featured-1","category-featured-cloud-storage","category-tech-lab","tag-ai-ml","tag-b2cloud","entry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain\" \/>\n<meta property=\"og:description\" content=\"Build your own conversational AI chatbot with this easy how-to from Backblaze Chief Technical Evangelist, Pat Patterson.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/\" \/>\n<meta property=\"og:site_name\" content=\"Backblaze Blog | Cloud Storage &amp; Cloud Backup\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/backblaze\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-28T16:05:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png\" \/>\n\t<meta property=\"og:image:width\" content=\"6000\" \/>\n\t<meta property=\"og:image:height\" content=\"3417\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Pat Patterson\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@backblaze\" \/>\n<meta name=\"twitter:site\" content=\"@backblaze\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Pat Patterson\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/","og_locale":"en_US","og_type":"article","og_title":"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain","og_description":"Build your own conversational AI chatbot with this easy how-to from Backblaze Chief Technical Evangelist, Pat Patterson.","og_url":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/","og_site_name":"Backblaze Blog | Cloud Storage &amp; Cloud Backup","article_publisher":"https:\/\/www.facebook.com\/backblaze","article_published_time":"2025-05-28T16:05:00+00:00","og_image":[{"width":6000,"height":3417,"url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png","type":"image\/png"}],"author":"Pat Patterson","twitter_card":"summary_large_image","twitter_creator":"@backblaze","twitter_site":"@backblaze","twitter_misc":{"Written by":"Pat Patterson","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#article","isPartOf":{"@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/"},"author":{"name":"Pat Patterson","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/person\/a724a8aee97b6451107442747cd101a4"},"headline":"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain","datePublished":"2025-05-28T16:05:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/"},"wordCount":1977,"commentCount":0,"publisher":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#primaryimage"},"thumbnailUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png","keywords":["AI\/ML","B2Cloud"],"articleSection":["Cloud Storage","Featured","Featured-Cloud Storage","Tech Lab"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/","url":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/","name":"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain","isPartOf":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#primaryimage"},"image":{"@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#primaryimage"},"thumbnailUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png","datePublished":"2025-05-28T16:05:00+00:00","breadcrumb":{"@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#primaryimage","url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png","contentUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png","width":6000,"height":3417,"caption":"A decorative image showing a cloud with the Backblaze logo with code imagery."},{"@type":"BreadcrumbList","@id":"https:\/\/www.backblaze.com\/blog\/building-a-conversational-ai-chatbot-website-with-backblaze-b2-langchain\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Conversational AI Chatbot Website with Backblaze B2 + LangChain"}]},{"@type":"WebSite","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#website","url":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/","name":"Backblaze Cloud Solutions Blog","description":"Cloud Storage &amp; Cloud Backup","publisher":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#organization","name":"Backblaze","url":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/i0.wp.com\/www.backblaze.com\/blog\/wp-content\/uploads\/2017\/12\/backblaze_icon_transparent.png?fit=512%2C512&ssl=1","contentUrl":"https:\/\/i0.wp.com\/www.backblaze.com\/blog\/wp-content\/uploads\/2017\/12\/backblaze_icon_transparent.png?fit=512%2C512&ssl=1","width":512,"height":512,"caption":"Backblaze"},"image":{"@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/backblaze","https:\/\/x.com\/backblaze","https:\/\/www.youtube.com\/user\/Backblaze","https:\/\/en.wikipedia.org\/wiki\/Backblaze"]},{"@type":"Person","@id":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/#\/schema\/person\/a724a8aee97b6451107442747cd101a4","name":"Pat Patterson","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/01\/PatPatterson1920px-150x150.png","url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/01\/PatPatterson1920px-150x150.png","contentUrl":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2022\/01\/PatPatterson1920px-150x150.png","caption":"Pat Patterson"},"description":"Pat Patterson is the chief technical evangelist at Backblaze. Over his three decades in the industry, Pat has built software and communities at Sun Microsystems, Salesforce, StreamSets, and Citrix. In his role at Backblaze, he creates and delivers content tailored to the needs of the hands-on technical professional, acts as the \u201cvoice of the developer\u201d on the Product team, and actively participates in the wider technical community. Outside the office, Pat runs far, having completed ultramarathons up to the 50 mile distance. Catch up with Pat via Bluesky or LinkedIn.","url":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/author\/pat\/"}]}},"jetpack_featured_media_url":"https:\/\/backblazeprod.wpenginepowered.com\/wp-content\/uploads\/2025\/05\/bb-bh-Event-Notifications-How-to-Series_developer.png","_links":{"self":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/posts\/112123","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/users\/174"}],"replies":[{"embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/comments?post=112123"}],"version-history":[{"count":0,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/posts\/112123\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/media\/112124"}],"wp:attachment":[{"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/media?parent=112123"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/categories?post=112123"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/backblazeprod.wpenginepowered.com\/blog\/wp-json\/wp\/v2\/tags?post=112123"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}