{"id":562,"date":"2026-08-17T09:00:00","date_gmt":"2026-08-17T00:00:00","guid":{"rendered":"https:\/\/www.theagenticprotocol.com\/?p=562"},"modified":"2026-08-16T23:21:19","modified_gmt":"2026-08-16T14:21:19","slug":"how-to-build-ai-chatbot-python","status":"publish","type":"post","link":"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/","title":{"rendered":"How to Build an AI Chatbot With Python: Complete 2026 Guide"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Building an AI chatbot with Python in 2026 takes less time than most tutorials suggest \u2014 and the gap between a working prototype and a production-ready system is three specific additions: persistent memory, a web API layer, and optionally a RAG retrieval pipeline for document-aware responses.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/www.theagenticprotocol.com\/wp-content\/uploads\/2026\/08\/grok-image-6c75ec13-17e6-477e-bc1b-ebc204454820-1024x576.jpg\" alt=\"how to build AI chatbot Python Claude 2026 complete guide\" class=\"wp-image-563\" srcset=\"https:\/\/www.theagenticprotocol.com\/wp-content\/uploads\/2026\/08\/grok-image-6c75ec13-17e6-477e-bc1b-ebc204454820-1024x576.jpg 1024w, https:\/\/www.theagenticprotocol.com\/wp-content\/uploads\/2026\/08\/grok-image-6c75ec13-17e6-477e-bc1b-ebc204454820-300x169.jpg 300w, https:\/\/www.theagenticprotocol.com\/wp-content\/uploads\/2026\/08\/grok-image-6c75ec13-17e6-477e-bc1b-ebc204454820-768x432.jpg 768w, https:\/\/www.theagenticprotocol.com\/wp-content\/uploads\/2026\/08\/grok-image-6c75ec13-17e6-477e-bc1b-ebc204454820-1536x864.jpg 1536w, https:\/\/www.theagenticprotocol.com\/wp-content\/uploads\/2026\/08\/grok-image-6c75ec13-17e6-477e-bc1b-ebc204454820.jpg 1792w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This guide builds all three, in order. You&#8217;ll start with a 30-line terminal chatbot using Claude Sonnet 5, add a FastAPI web server that exposes it as an API, and then add a ChromaDB retrieval layer so the chatbot can answer questions about your own documents. Each version is fully working code \u2014 not pseudocode, not snippets \u2014 that you can run immediately. If you haven&#8217;t set up the Claude API yet, start with the <a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/claude-api-python\/\">Claude API Python Tutorial<\/a> first. This guide picks up where that one ends.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_86 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Setup_Install_Everything_You_Need\" >Setup: Install Everything You Need<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Version_1_Terminal_Chatbot_With_Memory_10_Minutes\" >Version 1: Terminal Chatbot With Memory (10 Minutes)<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Version_2_FastAPI_Web_Chatbot_15_Minutes\" >Version 2: FastAPI Web Chatbot (15 Minutes)<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Run_and_test_the_API\" >Run and test the API<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Version_3_RAG_Document_Chatbot_Add_Your_Own_Knowledge\" >Version 3: RAG Document Chatbot (Add Your Own Knowledge)<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Production_Checklist_Before_You_Deploy\" >Production Checklist Before You Deploy<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#The_Builders_Takeaway\" >The Builder&#8217;s Takeaway<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/#Continue_in_This_Series\" >Continue in This Series<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Setup_Install_Everything_You_Need\"><\/span>Setup: Install Everything You Need<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install anthropic langchain-anthropic langchain-community \\\n            chromadb fastapi uvicorn python-dotenv pypdf<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># .env\nANTHROPIC_API_KEY=sk-ant-your-key-here<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Version_1_Terminal_Chatbot_With_Memory_10_Minutes\"><\/span>Version 1: Terminal Chatbot With Memory (10 Minutes)<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest working AI chatbot: a Python script that reads input, sends it to Claude with full conversation history, and prints the response. The critical addition over a single-shot API call is the message history loop \u2014 the chatbot remembers everything said in the current session.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nimport anthropic\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nclient = anthropic.Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\"))\n\ndef run_chatbot(\n    system_prompt: str = \"You are a helpful assistant. Be concise.\",\n    model: str = \"claude-sonnet-5\"\n) -&gt; None:\n    \"\"\"\n    A terminal chatbot with persistent conversation memory.\n    Type 'quit', 'exit', or press Ctrl+C to stop.\n    \"\"\"\n    history: list&#91;dict] = &#91;]\n    print(f\"Chatbot ready (model: {model}). Type 'quit' to exit.\\n\")\n\n    while True:\n        try:\n            user_input = input(\"You: \").strip()\n        except (KeyboardInterrupt, EOFError):\n            print(\"\\nGoodbye!\")\n            break\n\n        if user_input.lower() in (\"quit\", \"exit\", \"q\", \"\"):\n            print(\"Goodbye!\")\n            break\n\n        # Add user message to conversation history\n        history.append({\"role\": \"user\", \"content\": user_input})\n\n        try:\n            response = client.messages.create(\n                model=model,\n                max_tokens=1024,\n                system=system_prompt,\n                messages=history\n            )\n\n            assistant_message = response.content&#91;0].text\n\n            # Add Claude's response to history for the next turn\n            history.append({\"role\": \"assistant\", \"content\": assistant_message})\n\n            print(f\"\\nClaude: {assistant_message}\\n\")\n\n        except anthropic.APIError as e:\n            print(f\"API error: {e}\")\n            # Remove the failed user message from history\n            history.pop()\n\n\nif __name__ == \"__main__\":\n    run_chatbot(\n        system_prompt=\"\"\"You are a Python programming assistant.\nHelp developers write clean, production-ready Python code.\nWhen showing code, always include error handling.\"\"\"\n    )\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run this with <code>python chatbot.py<\/code>. The chatbot remembers the full conversation \u2014 ask a follow-up question and it knows what you were discussing three turns ago. The <code>system_prompt<\/code> parameter is where you specialize the chatbot for your use case: customer support, coding assistant, document Q&amp;A, or any other role.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Version_2_FastAPI_Web_Chatbot_15_Minutes\"><\/span>Version 2: FastAPI Web Chatbot (15 Minutes)<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The terminal chatbot works for local development. A production AI chatbot needs an HTTP API that a frontend, a mobile app, or another service can call. FastAPI is the 2026 standard for Python web APIs: async-native, automatic documentation, and type-safe request\/response models.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nimport uuid\nfrom contextlib import asynccontextmanager\nfrom dotenv import load_dotenv\n\nimport anthropic\nfrom fastapi import FastAPI, HTTPException\nfrom fastapi.responses import StreamingResponse\nfrom pydantic import BaseModel\n\nload_dotenv()\n\n# \u2500\u2500\u2500 Data models \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nclass Message(BaseModel):\n    role: str  # \"user\" or \"assistant\"\n    content: str\n\nclass ChatRequest(BaseModel):\n    message: str\n    session_id: str | None = None  # None = new session\n    stream: bool = False\n\nclass ChatResponse(BaseModel):\n    response: str\n    session_id: str\n    input_tokens: int\n    output_tokens: int\n\n# \u2500\u2500\u2500 In-memory session store (use Redis in production) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nsessions: dict&#91;str, list&#91;Message]] = {}\n\nSYSTEM_PROMPT = \"\"\"You are a helpful AI assistant.\nAnswer questions clearly and concisely.\nIf you don't know something, say so.\"\"\"\n\n# \u2500\u2500\u2500 App setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    print(\"Chatbot API starting...\")\n    yield\n    print(\"Chatbot API shutting down.\")\n\napp = FastAPI(\n    title=\"AI Chatbot API\",\n    description=\"Python chatbot powered by Claude Sonnet 5\",\n    version=\"1.0.0\",\n    lifespan=lifespan\n)\n\nclient = anthropic.Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\"))\n\n# \u2500\u2500\u2500 Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n@app.post(\"\/chat\", response_model=ChatResponse)\nasync def chat(request: ChatRequest) -&gt; ChatResponse:\n    \"\"\"\n    Send a message and get a response.\n    Include session_id to continue an existing conversation.\n    Omit session_id to start a new one.\n    \"\"\"\n    # Create new session or retrieve existing one\n    session_id = request.session_id or str(uuid.uuid4())\n    if session_id not in sessions:\n        sessions&#91;session_id] = &#91;]\n\n    history = sessions&#91;session_id]\n\n    # Add the new user message\n    history.append(Message(role=\"user\", content=request.message))\n\n    try:\n        # Build the messages payload for the API\n        messages = &#91;{\"role\": m.role, \"content\": m.content} for m in history]\n\n        response = client.messages.create(\n            model=\"claude-sonnet-5\",\n            max_tokens=1024,\n            system=SYSTEM_PROMPT,\n            messages=messages\n        )\n\n        assistant_text = response.content&#91;0].text\n\n        # Save assistant response to session history\n        history.append(Message(role=\"assistant\", content=assistant_text))\n\n        return ChatResponse(\n            response=assistant_text,\n            session_id=session_id,\n            input_tokens=response.usage.input_tokens,\n            output_tokens=response.usage.output_tokens\n        )\n\n    except anthropic.APIError as e:\n        history.pop()  # Remove failed user message\n        raise HTTPException(status_code=502, detail=f\"Claude API error: {e}\")\n\n\n@app.post(\"\/chat\/stream\")\nasync def chat_stream(request: ChatRequest):\n    \"\"\"\n    Stream the chatbot response token by token.\n    Useful for long responses \u2014 the user sees output immediately.\n    \"\"\"\n    session_id = request.session_id or str(uuid.uuid4())\n    if session_id not in sessions:\n        sessions&#91;session_id] = &#91;]\n\n    history = sessions&#91;session_id]\n    history.append(Message(role=\"user\", content=request.message))\n    messages = &#91;{\"role\": m.role, \"content\": m.content} for m in history]\n\n    async def generate():\n        full_response = \"\"\n        with client.messages.stream(\n            model=\"claude-sonnet-5\",\n            max_tokens=1024,\n            system=SYSTEM_PROMPT,\n            messages=messages\n        ) as stream:\n            for text in stream.text_stream:\n                full_response += text\n                yield text\n\n        # Save completed response to history after streaming finishes\n        history.append(Message(role=\"assistant\", content=full_response))\n\n    return StreamingResponse(generate(), media_type=\"text\/plain\")\n\n\n@app.get(\"\/sessions\/{session_id}\")\nasync def get_session(session_id: str) -&gt; dict:\n    \"\"\"Retrieve conversation history for a session.\"\"\"\n    if session_id not in sessions:\n        raise HTTPException(status_code=404, detail=\"Session not found\")\n    return {\n        \"session_id\": session_id,\n        \"message_count\": len(sessions&#91;session_id]),\n        \"messages\": &#91;m.model_dump() for m in sessions&#91;session_id]]\n    }\n\n\n@app.delete(\"\/sessions\/{session_id}\")\nasync def delete_session(session_id: str) -&gt; dict:\n    \"\"\"Clear a conversation session.\"\"\"\n    sessions.pop(session_id, None)\n    return {\"status\": \"deleted\", \"session_id\": session_id}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Run_and_test_the_API\"><\/span>Run and test the API<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># Start the server\nuvicorn chatbot_api:app --reload --port 8000\n\n# Test with curl\ncurl -X POST http:\/\/localhost:8000\/chat \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"message\": \"Hello! What can you help me with?\"}'\n\n# Continue the conversation (use session_id from previous response)\ncurl -X POST http:\/\/localhost:8000\/chat \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"message\": \"Tell me more\", \"session_id\": \"your-session-id-here\"}'\n\n# Interactive docs available at:\n# http:\/\/localhost:8000\/docs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The FastAPI server automatically generates interactive documentation at <code>\/docs<\/code> \u2014 paste the URL in your browser and you can test every endpoint without writing any frontend code. The <code>session_id<\/code> pattern is what makes the chatbot stateful across HTTP requests: each request is stateless at the protocol level, but the session history stored server-side makes the conversation feel continuous.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Version_3_RAG_Document_Chatbot_Add_Your_Own_Knowledge\"><\/span>Version 3: RAG Document Chatbot (Add Your Own Knowledge)<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The chatbot above answers questions from Claude&#8217;s training data. Add a RAG layer and it answers questions from <em>your<\/em> documents \u2014 product documentation, legal policies, technical manuals, knowledge bases. This is the same pipeline covered in yesterday&#8217;s <a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/rag-tutorial-python-2026\/\">RAG Tutorial<\/a>, integrated directly into the chatbot endpoint.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># rag_chatbot.py \u2014 add this to the FastAPI app above\n\nfrom langchain_community.document_loaders import PyPDFLoader, TextLoader\nfrom langchain.text_splitter import RecursiveCharacterTextSplitter\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_community.embeddings import HuggingFaceEmbeddings\n\n# \u2500\u2500\u2500 Build the knowledge base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndef build_knowledge_base(doc_paths: list&#91;str]) -&gt; Chroma:\n    \"\"\"\n    Index a list of documents into ChromaDB.\n    Call this once at startup; the index persists to disk.\n    \"\"\"\n    documents = &#91;]\n    for path in doc_paths:\n        if path.endswith(\".pdf\"):\n            loader = PyPDFLoader(path)\n        else:\n            loader = TextLoader(path)\n        documents.extend(loader.load())\n\n    splitter = RecursiveCharacterTextSplitter(\n        chunk_size=512, chunk_overlap=50\n    )\n    chunks = splitter.split_documents(documents)\n\n    embeddings = HuggingFaceEmbeddings(model_name=\"all-MiniLM-L6-v2\")\n    vectorstore = Chroma.from_documents(\n        documents=chunks,\n        embedding=embeddings,\n        persist_directory=\".\/chatbot_knowledge\"\n    )\n    print(f\"Indexed {len(chunks)} chunks from {len(doc_paths)} document(s)\")\n    return vectorstore\n\n\n# \u2500\u2500\u2500 RAG-enhanced chat endpoint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndef get_rag_context(vectorstore: Chroma, query: str, k: int = 3) -&gt; str:\n    \"\"\"Retrieve the most relevant document chunks for a query.\"\"\"\n    docs = vectorstore.similarity_search(query, k=k)\n    if not docs:\n        return \"\"\n    return \"\\n\\n---\\n\\n\".join(\n        f\"&#91;Source: {doc.metadata.get('source', 'unknown')}]\\n{doc.page_content}\"\n        for doc in docs\n    )\n\n\nRAG_SYSTEM_PROMPT = \"\"\"You are a helpful assistant with access to specific documents.\n\nWhen answering:\n1. Base your response on the provided context when relevant\n2. Cite the source document when using retrieved information\n3. If the answer isn't in the context, say so and use your general knowledge\n\nContext from documents:\n{context}\"\"\"\n\n\nasync def chat_with_rag(\n    user_message: str,\n    history: list&#91;Message],\n    vectorstore: Chroma\n) -&gt; str:\n    \"\"\"Chat with document context injected into the system prompt.\"\"\"\n    # Retrieve relevant context for this specific message\n    context = get_rag_context(vectorstore, user_message)\n\n    # Inject context into system prompt\n    system = RAG_SYSTEM_PROMPT.format(\n        context=context if context else \"No specific documents retrieved.\"\n    )\n\n    messages = &#91;{\"role\": m.role, \"content\": m.content} for m in history]\n\n    response = client.messages.create(\n        model=\"claude-sonnet-5\",\n        max_tokens=1024,\n        system=system,\n        messages=messages\n    )\n    return response.content&#91;0].text\n\n\n# \u2500\u2500\u2500 Initialize at startup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Add your documents here\nKNOWLEDGE_BASE_DOCS = &#91;\n    \"your_document.pdf\",\n    \"your_knowledge_base.txt\",\n]\n\n# Build once; comment out and use load_existing() after first run\nknowledge_base = build_knowledge_base(KNOWLEDGE_BASE_DOCS)\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Production_Checklist_Before_You_Deploy\"><\/span>Production Checklist Before You Deploy<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Replace in-memory sessions with Redis.<\/strong> The <code>sessions<\/code> dictionary in Version 2 resets every time the server restarts. Use <code>redis-py<\/code> with a TTL of 24 hours for production: session data persists across restarts and scales across multiple server instances.<\/li>\n\n\n\n<li><strong>Add rate limiting.<\/strong> Without rate limits, a single user can generate hundreds of dollars in API costs. Use <code>slowapi<\/code> (FastAPI-compatible) to limit each IP to 10 requests per minute: <code>@limiter.limit(\"10\/minute\")<\/code> on the chat endpoint.<\/li>\n\n\n\n<li><strong>Track costs per session.<\/strong> Log <code>input_tokens<\/code> and <code>output_tokens<\/code> for every API call. A 10-message conversation with 500 tokens per message uses approximately 5,000 total tokens \u2014 at Sonnet 5&#8217;s $2\/$10 pricing, that&#8217;s $0.007 per conversation. At 1,000 conversations per day: $7\/day. Know your cost per conversation before scaling.<\/li>\n\n\n\n<li><strong>Stream by default for responses longer than one sentence.<\/strong> Users tolerate waiting 2 seconds for a streaming response that starts immediately. They become frustrated waiting 5 seconds for a non-streaming response. Switch the production endpoint to <code>\/chat\/stream<\/code> for any response that might take more than 2 seconds.<\/li>\n\n\n\n<li><strong>Add the human review gate for consequential actions.<\/strong> If your chatbot can take actions \u2014 sending emails, updating records, submitting forms \u2014 add an explicit confirmation step before any action executes. The rogue AI agent incidents this week demonstrated exactly what happens when capable models are given external action tools without confirmation gates. Chatbots that only generate text are low-risk. Chatbots with tool access are not.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">For the complete Python chatbot ecosystem overview, see <a href=\"https:\/\/universopython.com\/en\/blog\/python-chatbots-ai-tutorial\" target=\"_blank\" rel=\"noopener\">UniversoPython&#8217;s 2026 Python chatbot tutorial survey<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"The_Builders_Takeaway\"><\/span>The Builder&#8217;s Takeaway<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An AI chatbot with Python in 2026 is a 30-line terminal script that grows into a production FastAPI service with streaming, session management, rate limiting, and optionally a RAG document layer \u2014 all on the same code foundation. Version 1 proves the concept in 10 minutes. Version 2 makes it deployable in 15 more. Version 3 makes it genuinely useful for business applications that need answers grounded in specific documents. The five production checklist items are the difference between a demo and a service. The cost tracking is the difference between a service and a sustainable one. Build Version 1 today, Version 2 this week, and Version 3 when you have a specific document corpus to serve.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Continue_in_This_Series\"><\/span>Continue in This Series<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/rag-tutorial-python-2026\/\">RAG Tutorial Python 2026<\/a> \u2014 the complete retrieval pipeline behind Version 3: chunking, embedding, ChromaDB, and quality improvements<\/li>\n\n\n\n<li><a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/claude-api-python\/\">Claude API Python Tutorial<\/a> \u2014 the foundational API patterns this chatbot builds on: system prompts, streaming, cost tracking<\/li>\n\n\n\n<li><a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/langchain-tutorial-2026\/\">LangChain Tutorial 2026<\/a> \u2014 the LCEL chain alternative to the raw SDK approach used here<\/li>\n\n\n\n<li><a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-agent-python\/\">How to Build an AI Agent With Python<\/a> \u2014 extend the chatbot into a full agent: add tools and autonomous decision-making<\/li>\n\n\n\n<li><a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-deploy-ai-agents\/\">How to Deploy AI Agents<\/a> \u2014 the production deployment checklist once your chatbot is ready to go live<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>This post is part of The Agentic Protocol&#8217;s Work series \u2014 the connective infrastructure layer beneath every autonomous pipeline. See also: <a href=\"https:\/\/www.theagenticprotocol.com\/index.php\/rag-tutorial-python-2026\/\">RAG Tutorial Python 2026<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Building an AI chatbot with Python in 2026 takes less time than most tutorials suggest \u2014 and the gap between a working prototype and a production-ready system is three specific additions: persistent memory, a web API layer, and optionally a RAG retrieval pipeline for document-aware responses. This guide builds all three, in order. You&#8217;ll start &#8230; <a title=\"How to Build an AI Chatbot With Python: Complete 2026 Guide\" class=\"read-more\" href=\"https:\/\/www.theagenticprotocol.com\/index.php\/how-to-build-ai-chatbot-python\/\" aria-label=\"Read more about How to Build an AI Chatbot With Python: Complete 2026 Guide\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":563,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13],"tags":[702,704,706,703,705],"class_list":["post-562","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-work-agentic-ai","tag-ai-chatbot-python","tag-build-ai-chatbot-python-2026","tag-chatbot-with-memory-python","tag-fastapi-chatbot-python","tag-python-chatbot-claude"],"_links":{"self":[{"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/posts\/562","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/comments?post=562"}],"version-history":[{"count":1,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/posts\/562\/revisions"}],"predecessor-version":[{"id":564,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/posts\/562\/revisions\/564"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/media\/563"}],"wp:attachment":[{"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/media?parent=562"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/categories?post=562"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.theagenticprotocol.com\/index.php\/wp-json\/wp\/v2\/tags?post=562"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}