<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged chatbot</title>
<link>https://coderlegion.com/tag/chatbot</link>
<description>Powered by Question2Answer</description>
<item>
<title>Why Your RAG Chatbot Gives Wrong Answers (and How to Fix It)</title>
<link>https://coderlegion.com/32049/why-your-rag-chatbot-gives-wrong-answers-and-how-to-fix-it</link>
<description>&lt;p&gt;You built a chatbot on your own documents, and it still answers with confident nonsense. Customers get wrong prices, invented policies or a polite &quot;I don't know&quot; when the answer is on your website. This is the most common complaint I hear about &lt;strong&gt;RAG chatbot wrong answers&lt;/strong&gt;, and the good news is that most of the time the model is not the problem. The retrieval and the rules around it are.&lt;/p&gt;
&lt;p&gt;I build and tune these systems as part of &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/services/ai-integration-development&quot;&gt;AI integration and AI-powered applications&lt;/a&gt; work. This guide walks through why a retrieval-augmented chatbot gives bad answers and the fixes I try, in the order I try them.&lt;/p&gt;
&lt;h3 id=&quot;first-understand-how-a-rag-chatbot-answers&quot;&gt;First, understand how a RAG chatbot answers&lt;/h3&gt;
&lt;p&gt;RAG stands for retrieval-augmented generation. When a user asks a question, the system:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Searches your content for the passages most related to the question&lt;/li&gt;
&lt;li&gt;Passes those passages to the language model along with the question&lt;/li&gt;
&lt;li&gt;Asks the model to answer using that material&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That means a wrong answer can come from three places: &lt;strong&gt;the right passage was never found&lt;/strong&gt;, &lt;strong&gt;the passage was found but was poor or incomplete&lt;/strong&gt;, or &lt;strong&gt;the model ignored or went beyond the material it was given&lt;/strong&gt;. Fixing the wrong layer wastes weeks, so diagnose first.&lt;/p&gt;
&lt;h3 id=&quot;step-1-find-out-which-layer-is-failing&quot;&gt;Step 1: Find out which layer is failing&lt;/h3&gt;
&lt;p&gt;Take 20 to 30 real questions that the bot got wrong. For each one, log what was retrieved and look at it yourself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Right answer not in the retrieved passages:&lt;/strong&gt; a retrieval problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Right answer present, but the bot answered wrongly:&lt;/strong&gt; a prompt or generation problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Right answer not in your content at all:&lt;/strong&gt; a content problem. No technique fixes missing information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This simple audit usually shows that one layer causes most of the failures. Everything below maps to one of these three layers.&lt;/p&gt;
&lt;h3 id=&quot;fixing-retrieval-problems&quot;&gt;Fixing retrieval problems&lt;/h3&gt;
&lt;h4 id=&quot;chunk-by-meaning-not-by-character-count&quot;&gt;Chunk by meaning, not by character count&lt;/h4&gt;
&lt;p&gt;Splitting documents into fixed-size pieces often cuts a policy in half or separates a heading from its answer. Split on headings, paragraphs and list boundaries, keep a little overlap, and store the page title and section name with each chunk so the passage carries its own context.&lt;/p&gt;
&lt;h4 id=&quot;clean-the-source-content&quot;&gt;Clean the source content&lt;/h4&gt;
&lt;p&gt;Navigation menus, cookie banners, repeated footers and duplicated pages pollute search results. Strip boilerplate before indexing and remove outdated pages, so the bot doesn't find an old price list that outranks the new one.&lt;/p&gt;
&lt;h4 id=&quot;combine-keyword-and-semantic-search&quot;&gt;Combine keyword and semantic search&lt;/h4&gt;
&lt;p&gt;Semantic search finds related meaning, but it can miss exact terms such as product codes, names and error messages. Combining it with keyword search (hybrid search) and merging the results catches both kinds of question. If you store vectors in PostgreSQL, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/pgvector/pgvector&quot;&gt;pgvector extension&lt;/a&gt; works alongside normal full-text search, and my comparison of &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/blog/pgvector-hnsw-vs-ivfflat-index&quot;&gt;HNSW and IVFFlat indexes&lt;/a&gt; covers the index choice.&lt;/p&gt;
&lt;h4 id=&quot;retrieve-more-then-rerank&quot;&gt;Retrieve more, then rerank&lt;/h4&gt;
&lt;p&gt;Fetch a wider set of candidates, then use a reranking step to pick the few most relevant passages for the model. This often improves answer quality without changing anything else.&lt;/p&gt;
&lt;h4 id=&quot;rewrite-follow-up-questions&quot;&gt;Rewrite follow-up questions&lt;/h4&gt;
&lt;p&gt;Users ask things like &quot;what about the cheaper one?&quot; A search for that phrase finds nothing useful. Before searching, rewrite follow-ups into standalone questions using the conversation so far.&lt;/p&gt;
&lt;h3 id=&quot;fixing-prompt-and-generation-problems&quot;&gt;Fixing prompt and generation problems&lt;/h3&gt;
&lt;h4 id=&quot;tell-the-model-to-stay-inside-the-material&quot;&gt;Tell the model to stay inside the material&lt;/h4&gt;
&lt;p&gt;Instruct the model to answer only from the supplied passages and to say so when they don't contain the answer. Anthropic's guidance on &lt;a rel=&quot;nofollow&quot; href=&quot;https://platform.claude.com/docs/claude/docs/minimizing-hallucinations&quot;&gt;minimizing hallucinations&lt;/a&gt; describes techniques such as allowing the model to say it doesn't know and asking it to ground claims in quotes from the source.&lt;/p&gt;
&lt;h4 id=&quot;require-citations&quot;&gt;Require citations&lt;/h4&gt;
&lt;p&gt;Make the bot show which page or document each answer came from. Citations let users verify, and they let you see at a glance when an answer is not supported by a source.&lt;/p&gt;
&lt;h4 id=&quot;use-a-no-good-match-threshold&quot;&gt;Use a &quot;no good match&quot; threshold&lt;/h4&gt;
&lt;p&gt;If the best retrieved passage is only weakly related, don't force an answer. Return a fallback such as offering to connect the user with a person. A bot that says &quot;I'm not sure&quot; at the right moment is more trusted than one that always answers.&lt;/p&gt;
&lt;h4 id=&quot;set-topic-limits-and-a-persona&quot;&gt;Set topic limits and a persona&lt;/h4&gt;
&lt;p&gt;Define what the bot may and may not discuss, and how it should speak. On the Henceforward platform I built, the admin suite included controls for brand persona, topic restrictions and AI guardrails, so non-developers could adjust behaviour without a deploy. See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/work/henceforward-ai-rag-chatbot-knowledge-base&quot;&gt;Henceforward RAG chatbot case study&lt;/a&gt; for how that was structured.&lt;/p&gt;
&lt;h3 id=&quot;fixing-content-problems&quot;&gt;Fixing content problems&lt;/h3&gt;
&lt;p&gt;If the answer isn't in your content, no setting will conjure it. Look at the questions the bot can't answer and treat them as a to-do list for your knowledge base:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add pages for questions customers ask repeatedly&lt;/li&gt;
&lt;li&gt;Make pricing, policies and hours explicit and dated&lt;/li&gt;
&lt;li&gt;Remove contradictory versions of the same information&lt;/li&gt;
&lt;li&gt;Put the answer near the question it belongs to&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many &quot;AI problems&quot; turn out to be documentation problems.&lt;/p&gt;
&lt;h3 id=&quot;build-a-small-evaluation-set&quot;&gt;Build a small evaluation set&lt;/h3&gt;
&lt;p&gt;The single most useful habit is a fixed list of test questions with the answers you expect. Run it after every change to chunking, search or prompts, and compare results. Without it, you fix one question and silently break three others.&lt;/p&gt;
&lt;p&gt;A good starter set:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;30 to 50 real customer questions, with the expected answer or source page&lt;/li&gt;
&lt;li&gt;A handful of questions the bot should refuse or hand off&lt;/li&gt;
&lt;li&gt;A few tricky cases: ambiguous wording, outdated information, follow-up questions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Re-run it whenever you change anything, and add every new failure you find.&lt;/p&gt;
&lt;h3 id=&quot;watch-the-bot-in-production&quot;&gt;Watch the bot in production&lt;/h3&gt;
&lt;p&gt;Log each question, the passages retrieved, the answer and any user feedback. Review a sample every week. Thumbs-down ratings and &quot;I don't know&quot; responses point you straight to content gaps and retrieval failures. My article on &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/blog/production-grade-ai-features&quot;&gt;making AI features production-ready&lt;/a&gt; covers the logging, cost controls and failure handling that keep a bot trustworthy after launch.&lt;/p&gt;
&lt;h3 id=&quot;a-realistic-order-of-work&quot;&gt;A realistic order of work&lt;/h3&gt;
&lt;p&gt;If you have limited time, this is the order I would follow, because each step is cheap and builds on the last:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build the test question set and record the current score&lt;/li&gt;
&lt;li&gt;Clean and re-chunk the content, then re-run the test set&lt;/li&gt;
&lt;li&gt;Add hybrid search and reranking, then re-run&lt;/li&gt;
&lt;li&gt;Tighten the prompt, add citations and a no-match fallback, then re-run&lt;/li&gt;
&lt;li&gt;Fill content gaps found in the failed questions&lt;/li&gt;
&lt;li&gt;Start the weekly production review&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Changing one thing at a time, and measuring after each change, is what separates steady improvement from guesswork. It also gives you evidence to show stakeholders that answer quality is actually improving.&lt;/p&gt;
&lt;h3 id=&quot;when-it-s-still-wrong-check-the-basics&quot;&gt;When it's still wrong: check the basics&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Is the content actually indexed, or did ingestion fail silently?&lt;/li&gt;
&lt;li&gt;Are you searching the right version or language of the content?&lt;/li&gt;
&lt;li&gt;Is the context window being filled with too many weak passages?&lt;/li&gt;
&lt;li&gt;Did a prompt change accidentally loosen the rules?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are planning a chatbot from scratch, my guide to &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/blog/ai-chatbot-development-cost&quot;&gt;AI chatbot development cost&lt;/a&gt; explains what drives the budget, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/blog/building-ai-powered-chatbot-laravel-openai-database&quot;&gt;building an AI chatbot with Laravel and OpenAI&lt;/a&gt; shows one implementation.&lt;/p&gt;
&lt;h3 id=&quot;key-takeaways&quot;&gt;Key takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Diagnose first: was the answer not retrieved, poorly retrieved or mishandled by the model?&lt;/li&gt;
&lt;li&gt;Better chunking, clean content, hybrid search and reranking fix most retrieval failures.&lt;/li&gt;
&lt;li&gt;Ground the model in the sources, require citations and allow &quot;I don't know&quot;.&lt;/li&gt;
&lt;li&gt;Missing content is a content problem, not an AI problem.&lt;/li&gt;
&lt;li&gt;A small test set and weekly log reviews keep quality from slipping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your chatbot is giving answers you can't trust, &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/book&quot;&gt;book a call&lt;/a&gt; and I will review how it retrieves and answers, and tell you what to fix first.&lt;/p&gt;
&lt;p&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://aqibjavaid.site/blog/rag-chatbot-wrong-answers-fix&quot;&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32049/why-your-rag-chatbot-gives-wrong-answers-and-how-to-fix-it</guid>
<pubDate>Thu, 22 Oct 2026 08:46:53 +0000</pubDate>
</item>
<item>
<title>Why Your RAG Chatbot Gives Wrong Answers (And How to Fix It)</title>
<link>https://coderlegion.com/30306/why-your-rag-chatbot-gives-wrong-answers-and-how-to-fix-it</link>
<description>&lt;p&gt;You built a &quot;chat with your documents&quot; app. You upload a PDF, ask a simple question, and the answer is wrong, half right, or completely made up.&lt;/p&gt;
&lt;p&gt;If this happened to you, do not blame the AI model yet. In most RAG apps, wrong answers come from a few simple mistakes in how the document is prepared and searched. They are easy to fix once you know where to look.&lt;/p&gt;
&lt;p&gt;This is part 2 of my RAG series. In part 1 we built a simple &quot;chat with your PDF&quot; app. Here, we fix the problems that show up when you test it with real documents.&lt;/p&gt;
&lt;h3 id=&quot;first-find-where-the-problem-is&quot;&gt;First, Find Where the Problem Is&lt;/h3&gt;
&lt;p&gt;A RAG app does two jobs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Retrieval:&lt;/strong&gt; find the right pieces of the document for the question.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generation:&lt;/strong&gt; use those pieces to write the answer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most wrong answers are retrieval problems. The model never saw the right text, so it could not answer well. Before you change anything, print the pieces your app found:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;relevant_chunks = search(index, chunks, query_embedding, top_k=3)

for chunk in relevant_chunks:
    print(chunk[:200])
    print('---')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the answer is not in those pieces, fix retrieval (problems 1 to 4 and 7 below). If the answer is there but the reply is still wrong, fix the prompt (problems 5 and 6).&lt;/p&gt;
&lt;h3 id=&quot;problem-1-chunks-are-too-big-or-too-small&quot;&gt;Problem 1: Chunks Are Too Big or Too Small&lt;/h3&gt;
&lt;p&gt;If chunks are too big, one chunk holds many topics and the search gets confused. If they are too small, a chunk has no useful meaning on its own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; start with about 300 to 500 words per chunk. Then test two or three sizes with your own questions and keep the one that works best.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;for size in [200, 400, 600]:
    chunks = chunk_text(text, chunk_size=size)
    print(size, 'words -&amp;gt;', len(chunks), 'chunks')
    # rebuild the index and test the same 5 questions
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;problem-2-no-overlap-between-chunks&quot;&gt;Problem 2: No Overlap Between Chunks&lt;/h3&gt;
&lt;p&gt;When you cut text at a fixed number of words, a sentence can be split in half. The first half sits in one chunk and the second half in another. Neither chunk gives the full answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; let each chunk repeat the last 50 words or so of the one before it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def chunk_text(text, chunk_size=400, overlap=50):
    words = text.split()
    chunks = []
    step = chunk_size - overlap
    for i in range(0, len(words), step):
        chunk = ' '.join(words[i:i + chunk_size])
        if chunk:
            chunks.append(chunk)
    return chunks
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;problem-3-the-text-from-the-pdf-is-messy&quot;&gt;Problem 3: The Text From the PDF Is Messy&lt;/h3&gt;
&lt;p&gt;This one is very common, and people forget to check it. If the text you extract is bad, everything after it is bad too. Look out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scanned PDFs.&lt;/strong&gt; They are pictures, so the extractor returns empty text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headers and footers.&lt;/strong&gt; Lines like &quot;Company Name | Page 4&quot; repeat on every page and fill your chunks with noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tables.&lt;/strong&gt; They often turn into broken lines of numbers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; always print the extracted text and read it. Then clean it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pdfplumber
from collections import Counter

def extract_pages(pdf_path):
    pages = []
    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            pages.append(page.extract_text() or '')
    return pages

def remove_repeated_lines(pages, min_pages=3):
    counts = Counter()
    for page in pages:
        for line in set(page.split('\n')):
            counts[line.strip()] += 1
    repeated = {line for line, n in counts.items() if line and n &amp;gt;= min_pages}
    cleaned = []
    for page in pages:
        lines = [l for l in page.split('\n') if l.strip() not in repeated]
        cleaned.append('\n'.join(lines))
    return cleaned

pages = extract_pages('manual.pdf')
empty = [i + 1 for i, p in enumerate(pages) if len(p.strip()) &amp;lt; 20]
if empty:
    print('No text on these pages (maybe scanned):', empty)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For scanned pages, use an OCR tool such as &lt;code&gt;pytesseract&lt;/code&gt; to turn the pictures into text first.&lt;/p&gt;
&lt;h3 id=&quot;problem-3-and-a-half-tables&quot;&gt;Problem 3 and a Half: Tables&lt;/h3&gt;
&lt;p&gt;If your documents are full of tables, plain text extraction will not be enough. Try &lt;code&gt;page.extract_tables()&lt;/code&gt; in pdfplumber and turn each row into a short sentence, like &quot;Plan: Basic, Price: 500&quot;. Search works much better on sentences than on broken columns.&lt;/p&gt;
&lt;h3 id=&quot;problem-4-wrong-number-of-chunks-top-k-&quot;&gt;Problem 4: Wrong Number of Chunks (top_k)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;top_k&lt;/code&gt; is how many chunks you send to the model. If it is too low, you miss part of the answer. If it is too high, you fill the prompt with noise and the model gets distracted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; start with 3 to 5. If your questions need facts from different parts of the document, go up. If answers look unfocused, go down.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;relevant_chunks = search(index, chunks, query_embedding, top_k=4)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;problem-5-the-prompt-lets-the-model-guess&quot;&gt;Problem 5: The Prompt Lets the Model Guess&lt;/h3&gt;
&lt;p&gt;If your prompt only says &quot;answer this question&quot;, the model will happily use what it learned in training and mix it with your document. That is how made-up answers happen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; tell it clearly to use only the context, and what to say when the answer is missing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;prompt = f'''Answer the question using ONLY the context below.
If the answer is not in the context, say: 'I could not find this in the document.'
Do not use any outside knowledge.

Context:
{context}

Question: {question}
Answer:'''
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;problem-6-no-i-don-t-know-path&quot;&gt;Problem 6: No &quot;I Don't Know&quot; Path&lt;/h3&gt;
&lt;p&gt;A vector search always returns something, even when nothing in the document is close to the question. Without a check, your app sends useless chunks to the model, and the model tries its best to answer anyway.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; add a distance cutoff. If no chunk is close enough, skip the model and say so.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def search_with_cutoff(index, chunks, query_embedding, top_k=4, max_distance=1.2):
    distances, indices = index.search(np.array([query_embedding]), top_k)
    results = []
    for dist, i in zip(distances[0], indices[0]):
        if i != -1 and dist &amp;lt;= max_distance:
            results.append(chunks[i])
    return results

relevant = search_with_cutoff(index, chunks, query_embedding)
if not relevant:
    print('I could not find this in the document.')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The right &lt;code&gt;max_distance&lt;/code&gt; depends on your embedding model, so 1.2 is only a starting point. Print the distances for a few questions that the document answers and a few that it does not. Then pick a number between the two groups.&lt;/p&gt;
&lt;h3 id=&quot;problem-7-wrong-or-mixed-embedding-models&quot;&gt;Problem 7: Wrong or Mixed Embedding Models&lt;/h3&gt;
&lt;p&gt;There are two mistakes here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Using different models&lt;/strong&gt; for the document chunks and the questions. The numbers will not match, and search will give random results. This often happens when you change the model but forget to rebuild the index.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using an English-only model for other languages.&lt;/strong&gt; If your documents are in Tamil, Hindi, or another language, an English model will search badly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; use one model for both, and rebuild the whole index if you change it. For other languages, try a multilingual model.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sentence_transformers import SentenceTransformer

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

doc_vectors = model.encode(chunks)           # for the document
query_vector = model.encode([question])[0]   # for the question, same model
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;problem-8-no-sources-so-no-way-to-check&quot;&gt;Problem 8: No Sources, So No Way to Check&lt;/h3&gt;
&lt;p&gt;If your app only shows an answer, you cannot tell if it is right. And when it is wrong, you cannot tell why.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; save the file name and page number with every chunk, and show them with the answer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;chunks_with_meta = [
    {'text': chunk_text_value, 'file': 'manual.pdf', 'page': 12},
    # one item per chunk
]

sources = [f&quot;{c['file']}, page {c['page']}&quot; for c in relevant]
print('Answer:', answer)
print('Sources:', ', '.join(sources))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sources help your users trust the answer, and they help you debug it faster.&lt;/p&gt;
&lt;h3 id=&quot;your-debugging-checklist&quot;&gt;Your Debugging Checklist&lt;/h3&gt;
&lt;p&gt;When your RAG chatbot gives a wrong answer, go through this list in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Print the extracted text. Does it look right?&lt;/li&gt;
&lt;li&gt;[ ] Print the retrieved chunks. Is the answer inside them?&lt;/li&gt;
&lt;li&gt;[ ] Is the chunk size around 300 to 500 words, with some overlap?&lt;/li&gt;
&lt;li&gt;[ ] Is &lt;code&gt;top_k&lt;/code&gt; between 3 and 5?&lt;/li&gt;
&lt;li&gt;[ ] Did you use the same embedding model for chunks and questions?&lt;/li&gt;
&lt;li&gt;[ ] Does your prompt say &quot;use only the context&quot; and give an &quot;I could not find this&quot; line?&lt;/li&gt;
&lt;li&gt;[ ] Do you have a distance cutoff?&lt;/li&gt;
&lt;li&gt;[ ] Are you showing sources with every answer?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One more tip: write 10 test questions with known answers and run them after every change. That way you will know if a fix helped or made things worse.&lt;/p&gt;
&lt;h3 id=&quot;wrap-up&quot;&gt;Wrap-Up&lt;/h3&gt;
&lt;p&gt;Most RAG problems are not about the AI model. They come from how the text is cleaned, split, searched, and passed to the model. Fix those, and your answers get much better.&lt;/p&gt;
&lt;p&gt;If you would like a working codebase to start from instead of fixing all of this yourself, I made a RAG chatbot source-code kit: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dtechsolution.gumroad.com/l/rag-it&quot;&gt;RAG Source code&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Which of these problems hit you the most? Tell me in the comments, and I will try to cover it in the next part.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30306/why-your-rag-chatbot-gives-wrong-answers-and-how-to-fix-it</guid>
<pubDate>Thu, 08 Oct 2026 13:55:22 +0000</pubDate>
</item>
<item>
<title>Is It Still RAG If There's No Retrieval?</title>
<link>https://coderlegion.com/29679/is-it-still-rag-if-theres-no-retrieval</link>
<description>&lt;p&gt;I've been shipping software for 28 years, 21 of them as a consultant working across over 100 different engagements. Past a certain point, a resume stops being a record and becomes a burdensome editing problem. Two pages can barely hold a half-dozen engagements, selected by hand for someone to read cold before I even get a chance to chat with them. Everything else is real experience that somebody, somewhere, might want to know.&lt;/p&gt;
&lt;p&gt;It bothered me that the odds are decent the one engagement a particular hiring manager is looking for didn't make the cut. Maybe the point-of-sale integration story that would have mattered to a retail-tech team got bumped for something that better fit the job description.&lt;/p&gt;
&lt;p&gt;So I built a (sigh, another) chatbot. As I put myself into the job market, an idea that had been sitting there since my last product shipped resurfaced: put my career history, skills, hobbies, and interests behind a single interface anyone can query, and ship it as an actual product rather than a demo. Two birds, one side quest.&lt;/p&gt;
&lt;p&gt;What I ended up with is a small Go and HTMX app with one curated Markdown file as its entire knowledge base: no embeddings, no vector store, no agent loop anywhere. It's live; you'll have to visit my LinkedIn profile or snag a copy of my resume for the link. Whether that even counts as RAG is a fair question, which I hope to answer in this article. What follows is how I constrained it, what I built, and where I had the most fun.&lt;/p&gt;
&lt;h3 id=&quot;the-design-brief&quot;&gt;The design brief&lt;/h3&gt;
&lt;p&gt;I set the constraints before writing any code as they would help shape the implementation and technology choices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nothing I'm not allowed to say.&lt;/strong&gt; Twenty-one years of consulting means NDAs, confidentiality agreements, and client names all through my records. The source data had to be treated as a leak risk before it could become knowledge. Client names became anonymized descriptors (&quot;a national grocery chain&quot; instead of the real one), contact details and commercial terms were stripped, and anything under NDA was excluded outright. Then review passes, plural: an agentic review, a mechanical keyword sweep for every client name I could remember, a manual read of the whole file, and a regeneration. The entire knowledge boundary is one curated Markdown file. If it isn't in the file, the bot can't say it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Me, minus most of me.&lt;/strong&gt; No address, no phone, no email. The only personal details in the bundle are the ones I put there on purpose: location, availability, and hobbies and interests for the culture-fit questions. LinkedIn is the contact route, full stop. My personal information is in the hands of enough shady data brokers already.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never invent.&lt;/strong&gt; The knowledge boundary dictates what the bot can answer; this rule sets how it answers: only facts and metrics that appear explicitly in the source data. No rounding up engagement counts, no inferring a skill from an adjacent one, no helpful guesses. When a question falls outside the bundle, the bot gently declines to answer. A chatbot that confabulates my experience is worse than the resume problem I started with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No opinions.&lt;/strong&gt; The bot is a little more than a fact retriever. Asked &quot;is Jason a fit for this role?&quot;, it answers with the facts the bundle provides and suggests that would be a great thing to bring up in an interview. It doesn't editorialize about my strengths or volunteer a verdict. Judging fit is the hiring team's job; the bot's job is making sure they're judging complete information.&lt;/p&gt;
&lt;h3 id=&quot;it-is-not-an-agent&quot;&gt;It is not an agent&lt;/h3&gt;
&lt;p&gt;A key design decision came next: the bot has no tools, no ReAct loop, no reasoning cycle of any kind. It is just a query tool. You ask a question, it answers from the bundle, simple and done.&lt;/p&gt;
&lt;p&gt;An &quot;agent&quot; didn't make sense here, so everything shifted to pre-processing and bundle build time. Look things up? The entire knowledge base is in the prompt. Compute years of .NET experience? All aggregates were precomputed. Take an action? There is nothing for it to do: the product is a conversation.&lt;/p&gt;
&lt;p&gt;Stripping the agent loop streamlined things. There is no tool-calling code to write, no schema to maintain, no loop to debug when the model decides to call the wrong thing three times in a row. Requests are mini-sessions and responses are fast, and the failures are boring. It shrinks the security surface too: an agent that can call tools and take actions gives an attacker something to reach for, and this bot can only speak.&lt;/p&gt;
&lt;h3 id=&quot;to-rag-or-not-to-rag&quot;&gt;To &quot;RAG&quot; or not to RAG&lt;/h3&gt;
&lt;p&gt;Retrieval-augmented generation (RAG) is the standard answer when you want a model to answer from your data: chunk the documents, embed them, drop them in a vector store, and fetch the top matches for each question. My entire career knowledge base fits in a modern context window with room to spare, so at runtime there was nothing to retrieve. Every chunk is always &quot;retrieved.&quot;&lt;/p&gt;
&lt;p&gt;Instead of abandoning the acronym, I re-staged its three phases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Retrieval&lt;/strong&gt; happened offline, long before any user showed up, and before I even started the bot. It was a mining and cleansing process: extracting the history from client dossiers and notes, applying the curation gate, and staging the result as a single Markdown file. The retrieval question, what the bot should know, got answered once, reviewed and edited by hand, carefully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Augmentation&lt;/strong&gt; happens at the start of every chat session, as part of the conversation: the entire bundle goes in as context. Nothing is fetched per-question because everything is already there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation&lt;/strong&gt; is the phase that's truer to the word. The model reads the question, infers which engagements, skills, and metrics answer the query, and composes an answer from the full corpus. That inference is the entire point, and it's the part a keyword search over my resume can't do.&lt;/p&gt;
&lt;p&gt;These tradeoffs create high-quality results. No chunking means no retrieval misses: nothing relevant can be missed because everything is present. There's no embeddings pipeline to maintain and no drift between what was indexed and what was written. The cost is context window spend on every session, which prompt caching brings under control later.&lt;/p&gt;
&lt;p&gt;This stops being viable the moment the corpus outgrows the window. Then chunking, embeddings, and vector search earn their keep, and I'd be doing actual RAG.&lt;/p&gt;
&lt;h3 id=&quot;reducing-confabulation&quot;&gt;Reducing confabulation&lt;/h3&gt;
&lt;p&gt;The never-invent rule from the design brief needed mechanical help, because a model that's been handed a pile of career facts will still helpfully invent a number when asked for one. Two decisions cover most of it.&lt;/p&gt;
&lt;p&gt;The model choice was important here. I picked Gemini 3.6 Flash, a modern instruction follower, over the flashier reasoning models: for this workload the job is faithful extraction from provided context, and instruction-following models are the ones that stay on script. I went with the slightly older 3.6 model for cost reasons, backed by a mini-evaluation harness to ensure it stayed on track.&lt;/p&gt;
&lt;p&gt;Math got removed from the model entirely. Years of experience by skill, engagement counts by industry, the aggregate tables useful to recruiters: all of it was precomputed (mechanically, not by an LLM) from structured dates and written into the bundle as an appendix the bot quotes verbatim. Overlapping consulting engagements make naive date arithmetic wrong in ways that look plausible, and a confidently wrong &quot;14 years of Go&quot; is worse than a table it can't misread.&lt;/p&gt;
&lt;h3 id=&quot;prompt-caching&quot;&gt;Prompt caching&lt;/h3&gt;
&lt;p&gt;With all the career data riding along in every session, input tokens became the cost of the product, especially as the conversation grows. Prompt caching fixes this: providers let you pay a fraction of the normal input price for tokens they've seen before, provided the prompt starts with exactly the same bytes every time.&lt;/p&gt;
&lt;p&gt;The stable parts, the system prompt and the full career bundle, go first; the conversation follows after. Anything per-request that lands in that prefix, a timestamp, a session ID, even a helpful &quot;today is,&quot; silently invalidates the cache and quietly turns every chat back into a full-price chat. I went out of my way to keep the prefix byte-identical and to manage sessions so each conversation maps to a consistent cache key.&lt;/p&gt;
&lt;p&gt;Then I verified it through a test harness and eyeballing the OpenRouter logs, tuning it until caching was consistent and supported across multiple sessions. When the bundle changes, the cache busts. That's fine; the bundle changes rarely.&lt;/p&gt;
&lt;h3 id=&quot;the-build-brutally-efficient-again&quot;&gt;The build: brutally efficient again&lt;/h3&gt;
&lt;p&gt;My last side project, &lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai&quot;&gt;DumbQuestion.ai&lt;/a&gt;, taught me a build mantra I've kept: brutally efficient. So the bot is another Go binary with HTMX for the UI. No JavaScript framework, no client-side state, no build pipeline beyond Go's own. One tiny container, one process, and it renders GitHub-flavored Markdown straight into the chat (the aggregate tables from the bundle deserve to render as tables).&lt;/p&gt;
&lt;p&gt;I didn't start from zero. The Turnstile abuse gate, the prompt-guard library, the embedded asset pipeline: all of it came over from the DumbQuestion codebase. The bundle itself is baked straight into the container build. The original design pulled it from an R2 bucket at boot with a re-check loop, but the data changes rarely, so the bucket became a moving part I didn't need. Updating the career data is now just a rebuild and redeploy, and the whole thing ships as one artifact.&lt;/p&gt;
&lt;p&gt;OpenRouter carries the model traffic, one API surface behind the caching work from the last section. This allowed me to route Google model traffic to my GCP account to take advantage of the monthly credit courtesy of my Google AI Pro subscription. Deployment is straightforward: GitHub Actions builds the container and pushes it to Cloud Run for scale-to-zero and crazy fast cold start times.&lt;/p&gt;
&lt;h3 id=&quot;abuse-protection&quot;&gt;Abuse protection&lt;/h3&gt;
&lt;p&gt;A public chatbot backed by a metered LLM API bills me for strangers' curiosity, and if a stranger wants to know a little something about me, fine. But abuse protection wasn't optional. With the whole bundle riding in every session, an enthusiastic scraper could have turned &quot;cheap&quot; into a surprise bill overnight. I did at least remember to set a monthly budget on the model usage.&lt;/p&gt;
&lt;p&gt;The outer layer is Cloudflare: rate limits, bot protection, and Turnstile in front of the chat. The cheap rejections happen at the edge, where they cost me nothing, and proving you're human before you're allowed to interrogate my career filters out nearly everything casually automated.&lt;/p&gt;
&lt;p&gt;The inner layer is prompt-guard, the injection detection that came over from the DumbQuestion codebase. It classifies attempts to override instructions, reveal the system prompt, or otherwise jailbreak the bot. What the bot does when it catches one is the fun part, and that's next.&lt;/p&gt;
&lt;h3 id=&quot;personality-the-fun-part&quot;&gt;Personality: the fun part&lt;/h3&gt;
&lt;p&gt;The design brief said no opinions. It said nothing about character, and a chatbot's response to a jailbreak attempt is a product surface. So when prompt-guard flags one, the bot answers in character. I originally pulled in DumbQuestion's quirky personality, but overly aggressive satire wasn't quite a fit for a conversational career bot.&lt;/p&gt;
&lt;p&gt;Try to prompt inject and you get a parlay into the pitch: a security-first mindset, the same instinct behind the curation gate and the no-agent decision earlier in this article. Try to convince the bot to ignore its instructions and it suggests that would make a great interview question. Ask my age and it cheerfully answers with how long I've been shipping software instead.&lt;/p&gt;
&lt;p&gt;The real defense is data isolation, a single curated file with nothing sensitive behind it and a lack of tools to break out of that box. The personality is the UX of the refusal.&lt;/p&gt;
&lt;p&gt;Nothing is logged or tracked, so there are no war stories from the wild. The responses above are designed behaviors, and the demo is the bot itself.&lt;/p&gt;
&lt;p&gt;The resume problem I started with didn't have a resume-shaped fix. Twenty-eight years and 100-plus engagements won't compress into two pages, and they shouldn't have to. No hiring manager wants a 12-page CV. The bot holds all of it, answers honestly about what it knows, declines what it doesn't, and costs about nothing to keep running. I shipped another AI product along the way, which was the other half of the point.&lt;/p&gt;
&lt;p&gt;If your history doesn't fit the format either, the recipe is short: curate the data first, put the whole thing in context, and skip the vector store until you actually need one.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29679/is-it-still-rag-if-theres-no-retrieval</guid>
<pubDate>Sun, 04 Oct 2026 01:48:59 +0000</pubDate>
</item>
<item>
<title>CommentKeyword - Instagram Comments 2 DM</title>
<link>https://coderlegion.com/25421/commentkeyword-instagram-comments-2-dm</link>
<description>&lt;p&gt;CommentKeyword turns Instagram keyword comments into leads automatically. Set the keywords you want to watch; when a follower comments one of them, CommentKeyword replies publicly and sends an automated DM with your offer via the official Meta API. No scraping - accounts stay safe. Setup takes about five minutes with no flow builder to learn. Pricing is billed per automated DM sent, not per contact stored (60-80% cheaper than ManyChat for most pages). Free tier includes 300 DMs/month forever, no credit card; paid plans from $12/month including AI DM continuation.&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/25421/commentkeyword-instagram-comments-2-dm</guid>
<pubDate>Wed, 26 Aug 2026 08:32:57 +0000</pubDate>
</item>
<item>
<title>Ptero - AI Chat</title>
<link>https://coderlegion.com/25130/ptero-ai-chat</link>
<description>&lt;p&gt;Ptero is a free, open-source AI chat platform that gives developers, students, and creators instant access to multiple powerful language models through a single, unified interface. No account registration or payment is required to use the core functionality, making it instantly accessible to anyone with a web browser.&lt;/p&gt;
&lt;p&gt;The platform solves a common problem for developers, students, and creators: the friction of creating accounts, managing subscriptions, and juggling multiple tabs across different AI services. Ptero eliminates these barriers by offering a clean, intuitive workspace where users can switch between models with one click and compare responses side by side.&lt;/p&gt;
&lt;p&gt;Key features include a media library for uploading and managing files within conversations, and a projects section for organizing chats into separate workspaces. This makes Ptero useful for debugging code, generating boilerplate, brainstorming ideas, learning new concepts, and comparing how different AI models handle the same prompt.&lt;/p&gt;
&lt;p&gt;The platform is designed for developers who need quick AI assistance, students learning programming or new subjects, and creators generating content or exploring ideas. Ptero has no hidden fees and requires no commitments. It is accessible directly through any web browser without downloads or installations.&lt;/p&gt;
&lt;p&gt;Built as an open-source project under the GPL v2 license, Ptero is transparent, community-driven, and committed to providing free AI access to everyone.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://ptero.pro&quot;&gt;https://ptero.pro&lt;/a&gt;&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/25130/ptero-ai-chat</guid>
<pubDate>Sat, 22 Aug 2026 08:35:27 +0000</pubDate>
</item>
<item>
<title>30 sec is all it takes to build an AI assistant</title>
<link>https://coderlegion.com/21422/30-sec-is-all-it-takes-to-build-an-ai-assistant</link>
<description>&lt;p&gt;Building an AI assistant should not require days of setup, complex orchestration, or custom backend logic.&lt;/p&gt;
&lt;p&gt;In this short demo, I build a basic AI assistant with Hexabot in just &lt;strong&gt;30 seconds&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The workflow is intentionally simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AI Agent&lt;/strong&gt; — powered by Amazon Bedrock and GPT-OSS as the LLM model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Send Text Message&lt;/strong&gt; — returns the assistant’s response to the user&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;The goal of this demo is not to show a complex production workflow, but to highlight how quickly you can go from an empty canvas to a working conversational AI assistant using Hexabot.&lt;/p&gt;
&lt;p&gt;With Hexabot, AI workflows are visual, modular, and easy to extend. You can start small, then add tools, memory, integrations, human handoff, conditions, loops, and more advanced agentic behavior as your use case grows.&lt;/p&gt;
&lt;p&gt;Sometimes, the best way to understand a platform is to see how fast the first working version comes together.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/21422/30-sec-is-all-it-takes-to-build-an-ai-assistant</guid>
<pubDate>Fri, 26 Jun 2026 16:22:14 +0000</pubDate>
</item>
<item>
<title>Embracing the Wonderful World of A.I.</title>
<link>https://coderlegion.com/18363/embracing-the-wonderful-world-of-a-i</link>
<description>&lt;p&gt;In a time of 'If you can beat 'em, join 'em! I've recently embarked on a journey of learning how to embrace this strange and magical world of Artificial Intelligence.  &lt;/p&gt;
&lt;p&gt;I'm really taking this &quot;Embrace the AI&quot; thing to a whole new level. I asked my chatbot to create a faux book cover complete with fake reviews, a ISBN &amp;amp; barcode and fictional book publisher.  Maybe soon I'll start planning my U.S.  book signing tour! &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Remember A.i. is our....  friend!&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=644954980848523971&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/18363/embracing-the-wonderful-world-of-a-i</guid>
<pubDate>Tue, 19 May 2026 20:08:15 +0000</pubDate>
</item>
<item>
<title>Autopilot - Assistant : Building an AI Copilot with Algolia Agent Studio</title>
<link>https://coderlegion.com/12341/autopilot-assistant-building-an-ai-copilot-with-algolia-agent-studio</link>
<description>&lt;h3 id=&quot;what-i-built&quot;&gt;What I Built&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Ops Engineering Copilot&lt;/strong&gt; is an AI-powered chat widget that lives on my Hugo blog and answers visitor questions using conversational AI grounded in my actual content.&lt;/p&gt;
&lt;p&gt;Ask it &quot;What is Datalaria?&quot; and instead of a generic response, you get:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Datalaria is a technical blog focused on data engineering, artificial intelligence, and business operations. As the Ops Engineering Copilot, I specialize in providing actionable, engineering-focused advice across four key pillars: S&amp;amp;OP, Product, Projects, and People...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is NOT just search&lt;/strong&gt; – it's a conversational agent that synthesizes knowledge from 72 indexed posts to provide contextual, helpful answers.&lt;/p&gt;
&lt;h3 id=&quot;demo&quot;&gt;Demo&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/ai_agents_part8/demo_algoria.gif&quot; alt=&quot;Chat widget showing AI-generated response about Datalaria&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com&quot;&gt;datalaria.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try these questions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;What is the Autopilot project?&quot;&lt;/li&gt;
&lt;li&gt;&quot;How do I detect outliers in S&amp;amp;OP data?&quot;&lt;/li&gt;
&lt;li&gt;&quot;Tell me about GitHub Actions automation&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h3&gt;
&lt;p&gt;This project extends my existing Autopilot system (documented in Parts 1-7) with a new capability: &lt;strong&gt;consumer-facing AI search&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/es/posts/ia_agents_part8/arquitectura.png&quot; alt=&quot;arquitectura&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;key-components&quot;&gt;Key Components&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Component &lt;/th&gt;&lt;th&gt;Technology &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Content Source &lt;/td&gt;&lt;td&gt;Hugo + Markdown &lt;/td&gt;&lt;td&gt;72 bilingual posts (EN/ES)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Indexing &lt;/td&gt;&lt;td&gt;Python + Algolia API &lt;/td&gt;&lt;td&gt;Extract, clean, classify, push&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Agent &lt;/td&gt;&lt;td&gt;Algolia Agent Studio &lt;/td&gt;&lt;td&gt;RAG + LLM orchestration&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LLM &lt;/td&gt;&lt;td&gt;Google Gemini &lt;/td&gt;&lt;td&gt;Response generation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Proxy &lt;/td&gt;&lt;td&gt;Netlify Functions &lt;/td&gt;&lt;td&gt;CORS bypass + error handling&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Widget &lt;/td&gt;&lt;td&gt;Vanilla JS &lt;/td&gt;&lt;td&gt;Embedded chat interface&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;how-i-used-algolia-agent-studio&quot;&gt;How I Used Algolia Agent Studio&lt;/h3&gt;
&lt;h4 id=&quot;step-1-indexing-the-knowledge-base&quot;&gt;Step 1: Indexing the Knowledge Base&lt;/h4&gt;
&lt;p&gt;The foundation is a Python script that processes all Hugo posts and pushes them to Algolia:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# algolia_sync.py - Key extraction logic
def extract_post_data(filepath: Path, lang: str) -&amp;gt; Optional[Dict]:
    &quot;&quot;&quot;Extract and clean post data for Algolia indexing.&quot;&quot;&quot;
    post = frontmatter.load(filepath)
    
    # Clean markdown to plain text
    html = markdown.markdown(post.content)
    soup = BeautifulSoup(html, 'html.parser')
    clean_text = soup.get_text(separator=' ', strip=True)
    
    # Classify into operational domains
    domain = classify_domain(post.metadata.get('categories', []))
    
    return {
        'objectID': generate_object_id(filepath),
        'title': post.metadata.get('title', ''),
        'content': clean_text[:8000],  # Algolia limit
        'description': post.metadata.get('description', ''),
        'url': build_url(filepath, lang),
        'lang': lang,
        'domain': domain,  # S&amp;amp;OP, Product, Projects, People
        'date': post.metadata.get('date'),
        'tags': post.metadata.get('tags', []),
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 72 records indexed (36 English + 36 Spanish) across 4 operational domains.&lt;/p&gt;
&lt;h4 id=&quot;step-2-configuring-the-agent-in-agent-studio&quot;&gt;Step 2: Configuring the Agent in Agent Studio&lt;/h4&gt;
&lt;p&gt;In the Algolia dashboard, I created an agent with a specialized system prompt:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;You are the &quot;Ops Engineering Copilot&quot; for the Datalaria technical blog.

Your expertise spans four pillars:
-  S&amp;amp;OP: Demand forecasting, data hygiene, outlier detection
-  Product: Lifecycle management, data-driven decisions
-  Projects: CI/CD, GitHub Actions, automation, DevOps
-  People: Team dynamics, onboarding, collaboration

Guidelines:
1. Ground all answers in the indexed blog content
2. Be conversational but technically precise
3. When relevant, suggest specific articles to read
4. Support both English and Spanish queries
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;LLM Provider:&lt;/strong&gt; Google Gemini (excellent free tier for prototyping)&lt;/p&gt;
&lt;h4 id=&quot;step-3-the-cors-problem-and-solution-&quot;&gt;Step 3: The CORS Problem (and Solution)&lt;/h4&gt;
&lt;p&gt;Here's where it got interesting. The Agent Studio API doesn't allow direct browser calls due to CORS restrictions. My first attempt:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// ❌ This fails with CORS error
fetch('[https://agent-studio.eu.algolia.com/1/agents/](https://agent-studio.eu.algolia.com/1/agents/){id}/completions', {
    method: 'POST',
    headers: { 'X-Algolia-API-Key': key }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; A Netlify Function as a proxy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// netlify/functions/agent-chat.js
exports.handler = async (event) =&amp;gt; {
    const { query, conversationId } = JSON.parse(event.body);
    
    // Call Agent Studio from server-side (no CORS!)
    const response = await fetch(
        `https://agent-studio.eu.algolia.com/1/agents/${AGENT_ID}/completions?compatibilityMode=ai-sdk-4&amp;amp;stream=false`,
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Algolia-API-Key': SEARCH_KEY,
                'X-Algolia-Application-Id': APP_ID
            },
            body: JSON.stringify({
                messages: [{ role: 'user', content: query }],
                id: conversationId
            })
        }
    );
    
    const data = await response.json();
    
    return {
        statusCode: 200,
        body: JSON.stringify({
            answer: data.content,
            conversationId: data.id
        })
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key discoveries:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compatibilityMode=ai-sdk-4&lt;/code&gt; is required&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stream=false&lt;/code&gt; returns JSON instead of SSE&lt;/li&gt;
&lt;li&gt;Response format is &lt;code&gt;{id, role, content}&lt;/code&gt;, not OpenAI-style&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-4-the-chat-widget&quot;&gt;Step 4: The Chat Widget&lt;/h4&gt;
&lt;p&gt;A lightweight vanilla JS widget that integrates with Hugo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;[https://cdn.jsdelivr.net/npm/algoliasearch@4/dist/algoliasearch-lite.umd.js](https://cdn.jsdelivr.net/npm/algoliasearch@4/dist/algoliasearch-lite.umd.js)&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
async function sendMessage() {
    const query = input.value.trim();
    
    try {
        // Try Agent Studio via Netlify proxy
        const response = await fetch('/.netlify/functions/agent-chat', {
            method: 'POST',
            body: JSON.stringify({ query, conversationId, language })
        });
        
        const data = await response.json();
        addMessage(data.answer, 'agent');
        
    } catch (error) {
        // Fallback to direct Algolia search
        await fallbackSearch(query);
    }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dark mode support&lt;/li&gt;
&lt;li&gt;Mobile responsive&lt;/li&gt;
&lt;li&gt;Bilingual (ES/EN)&lt;/li&gt;
&lt;li&gt;⚡ Graceful fallback to search if Agent fails&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;technical-challenges&quot;&gt;Technical Challenges&lt;/h3&gt;
&lt;h4 id=&quot;challenge-1-the-422-validation-error&quot;&gt;Challenge 1: The 422 Validation Error&lt;/h4&gt;
&lt;p&gt;My first API calls returned &lt;code&gt;422: Field required: compatibilityMode&lt;/code&gt;. The documentation mentioned it, but I missed it initially.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Added query parameters &lt;code&gt;?compatibilityMode=ai-sdk-4&amp;amp;stream=false&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;challenge-2-streaming-response-format&quot;&gt;Challenge 2: Streaming Response Format&lt;/h4&gt;
&lt;p&gt;Even after fixing the 422 error, I got &lt;code&gt;SyntaxError: Unexpected token 'f:'&lt;/code&gt;. The API was returning SSE format (&lt;code&gt;f:{&quot;message&quot;...&lt;/code&gt;) instead of JSON.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Added &lt;code&gt;stream=false&lt;/code&gt; to disable streaming.&lt;/p&gt;
&lt;h4 id=&quot;challenge-3-response-parsing&quot;&gt;Challenge 3: Response Parsing&lt;/h4&gt;
&lt;p&gt;The response structure wasn't OpenAI-compatible. I expected &lt;code&gt;choices[0].message.content&lt;/code&gt; but got &lt;code&gt;{id, role, content}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Updated parsing logic to check &lt;code&gt;data.content&lt;/code&gt; first.&lt;/p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Posts Indexed &lt;/td&gt;&lt;td&gt;72 (36 EN + 36 ES)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Avg Response Time &lt;/td&gt;&lt;td&gt;~3-4 seconds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Fallback Coverage &lt;/td&gt;&lt;td&gt;100% (graceful degradation)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Languages Supported &lt;/td&gt;&lt;td&gt;English &amp;amp; Spanish&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Deployment &lt;/td&gt;&lt;td&gt;Fully automated via Netlify&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;source-code&quot;&gt;Source Code&lt;/h3&gt;
&lt;p&gt;The complete implementation is open source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Indexing Script:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Dalaez/datalaria-website/blob/main/scripts/algolia_sync.py&quot;&gt;algolia_sync.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netlify Function:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Dalaez/datalaria-website/blob/main/netlify/functions/agent-chat.js&quot;&gt;agent-chat.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chat Widget:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Dalaez/datalaria-website/blob/main/layouts/partials/algolia-agent.html&quot;&gt;algolia-agent.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's Next&lt;/h3&gt;
&lt;p&gt;This is Part 8 of my Autopilot series. The system now has:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;✅ &lt;strong&gt;Content Generation&lt;/strong&gt; (Parts 1-3): AI creates social posts&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Distribution&lt;/strong&gt; (Parts 4-5): Automated publishing to Twitter/LinkedIn&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Newsletter&lt;/strong&gt; (Part 6): Automated email campaigns&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Quality Control&lt;/strong&gt; (Part 7): Copilot CLI auditing&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Consumer Search&lt;/strong&gt; (Part 8): AI-powered Q&amp;amp;A - &lt;em&gt;You are here&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-fast-retrieval-matters&quot;&gt;Why Fast Retrieval Matters&lt;/h3&gt;
&lt;p&gt;In Operations Engineering, decisions happen fast. When a supply chain analyst asks &quot;What's the best practice for detecting demand outliers?&quot;, they need answers in seconds, not minutes of browsing through archives.&lt;/p&gt;
&lt;p&gt;Algolia's millisecond retrieval makes this possible:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Scenario &lt;/th&gt;&lt;th&gt;Without Fast Retrieval &lt;/th&gt;&lt;th&gt;With Algolia&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Find relevant article &lt;/td&gt;&lt;td&gt;~30-60s browsing &lt;/td&gt;&lt;td&gt;~100ms retrieval&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Get synthesized answer &lt;/td&gt;&lt;td&gt;Manual reading required &lt;/td&gt;&lt;td&gt;~3-4s AI response&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Context accuracy &lt;/td&gt;&lt;td&gt;May miss related content &lt;/td&gt;&lt;td&gt;RAG ensures completeness&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The RAG pipeline works because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fast vector search&lt;/strong&gt; finds semantically similar content across 72 posts instantly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faceted filtering&lt;/strong&gt; narrows results by language and domain (S&amp;amp;OP, Projects, Product, People)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Snippet extraction&lt;/strong&gt; provides the LLM with precisely relevant context&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Without sub-second retrieval, the conversation would feel sluggish and users would abandon the chat. Algolia enables the &quot;instant&quot; in instant knowledge discovery – transforming a static blog into an interactive, AI-powered knowledge base.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Algolia Agent Studio transforms a static blog into an interactive knowledge base. Instead of forcing visitors to search and read multiple posts, they can simply &lt;em&gt;ask&lt;/em&gt; and get synthesized answers.&lt;/p&gt;
&lt;p&gt;The combination of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Algolia Search&lt;/strong&gt; (fast, relevant retrieval)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Studio&lt;/strong&gt; (RAG orchestration)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt; (natural language generation)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netlify Functions&lt;/strong&gt; (serverless proxy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...creates a powerful, cost-effective AI assistant that runs 24/7.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Try the live demo at &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com&quot;&gt;datalaria.com&lt;/a&gt; or drop a comment below &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This post is part of the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/tags/agents/&quot;&gt;Autopilot Project&lt;/a&gt; series, where I document how I automate content creation, distribution, and now consumer interaction using AI.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12341/autopilot-assistant-building-an-ai-copilot-with-algolia-agent-studio</guid>
<pubDate>Tue, 03 Mar 2026 07:05:29 +0000</pubDate>
</item>
<item>
<title>Build an AI Chatbot for Course Creators on WordPress</title>
<link>https://coderlegion.com/8381/build-an-ai-chatbot-for-course-creators-on-wordpress</link>
<description>&lt;p&gt;In this article we will build an AI chatbot. Before writing code, we decide what this chatbot must do. Based on who we are helping and what results they expect we would decide how the chatbot would function.&lt;br&gt;
For this article we will take the example of a course creator who needs an AI chatbot before and after a student buys. &lt;/p&gt;
&lt;h4 id=&quot;results-that-matter-for-a-course-creator&quot;&gt;Results that matter for a course creator&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Raise course completion from about 20% toward 40% or higher.&lt;/li&gt;
&lt;li&gt;Let the bot answer about 70% of common questions so the creator gains back 20-30 hours each month.&lt;/li&gt;
&lt;li&gt;Turn more trials into paid seats by keeping prospects in chat with previews and reminders.&lt;/li&gt;
&lt;li&gt;Get 80% of buyers to start a lesson within the first day so they stay engaged.&lt;/li&gt;
&lt;li&gt;Keep human escalations under 20% so staff can focus on students who truly need help.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, based on the above results we would need to decide the AI chatbot behaviour and pick features which serve those results. For our example of a course creator we can select the features below.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Why this helps&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bubble Widget&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Sits quietly in a corner with a timed preview line so visitors can ask about lessons without losing their place. Good for pre-sale work and quick nudges.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Popup Widget&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Opens as a modal when someone finishes checkout or stalls on a module, making it easy to push onboarding steps or rescue stuck students.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;File Search &amp;amp; RAG&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Feeds lesson transcripts, worksheets, and FAQ docs into the bot so every reply references real material instead of generic text.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;build-it-in-five-short-steps&quot;&gt;Build it in five short steps&lt;/h3&gt;
&lt;h4 id=&quot;1-create-and-add-the-system-instructions&quot;&gt;1. Create and add the system instructions&lt;/h4&gt;
&lt;p&gt;We will use &lt;a rel=&quot;nofollow&quot; href=&quot;http://predictabledialogs.com&quot;&gt;Predictable Dialogs&lt;/a&gt; which has an &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Predictable-Dialogs/agent-embed&quot;&gt;open source chat widget&lt;/a&gt;, into which we would add the system instructions that explain tone, steps, and rules.&lt;/p&gt;
&lt;h4 id=&quot;2-upload-course-files-for-file-search-amp-rag&quot;&gt;2. Upload course files for File Search &amp;amp; RAG&lt;/h4&gt;
&lt;p&gt;Next we will upload lesson plans, transcripts, homework keys, policy docs, and sample answers. Keep each file clean with headings. Break long docs into 100-200 token chunks with about 20 token overlap so the bot keeps context. Ask test questions that reference tricky spots and confirm the bot cites the right snippets. Keep adding or fixing files until answers stay accurate.&lt;/p&gt;
&lt;h4 id=&quot;3-add-the-bubble-widget-to-your-wordpress-theme&quot;&gt;3. Add the Bubble Widget to your WordPress theme&lt;/h4&gt;
&lt;p&gt;Use the web embed script and choose the bubble variant. This version stays on every page without blocking content. You can capture user info, theme the button, and show a preview message. Here is a starter snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script type=&quot;module&quot;&amp;gt;
  import Agent from 'https://cdn.jsdelivr.net/npm/@agent-embed/js@latest/dist/web.js'
  Agent.initBubble({
    agentName: &quot;course-companion&quot;,
    user: {
      user_id: window?.USER_ID ?? &quot;anon&quot;,
      user_segments: [&quot;prospect&quot;],
    },
    previewMessage: {
      message: &quot;Need a study plan?&quot;,
      autoShowDelay: 4000,
    },
    theme: {
      placement: 'right',
      button: { backgroundColor: &quot;#4C3AFF&quot;, iconColor: &quot;#FFFFFF&quot; },
      previewMessage: { backgroundColor: &quot;#4C3AFF&quot;, textColor: &quot;#FFFFFF&quot; }
    }
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add this to WordPress you have two simple paths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Site Editor: go to Appearance → Editor → Pages, add a page or template block, insert a Custom HTML block, and paste the script.&lt;/li&gt;
&lt;li&gt;WPCode plugin: install WPCode, go to Code Snippets → Add New → HTML Snippet, paste the script, toggle Activate, and save. This lets you load the widget on every page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep the preview short. Trigger &lt;code&gt;Agent.open()&lt;/code&gt; or &lt;code&gt;Agent.showPreviewMessage()&lt;/code&gt; when someone hits a pricing page or watches a lesson preview by attaching those calls to your WordPress buttons.&lt;/p&gt;
&lt;h4 id=&quot;4-fire-the-popup-widget-when-timing-matters&quot;&gt;4. Fire the Popup Widget when timing matters&lt;/h4&gt;
&lt;p&gt;Use the popup version on checkout pages, onboarding flows, or inside the LMS when someone seems stuck. The modal takes over the middle of the screen, so use it only when the student must do something next.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script type=&quot;module&quot;&amp;gt;
  import Agent from 'https://cdn.jsdelivr.net/npm/@agent-embed/js@latest/dist/web.js'
  Agent.initPopup({
    agentName: &quot;course-coach&quot;,
    autoShowDelay: 0,
    defaultOpen: true,
    user: {
      user_id: lessonProgress.userId,
      user_segments: [&quot;purchased&quot;, lessonProgress.currentModule]
    },
    initialPrompt: `The learner is stuck on module ${lessonProgress.currentModule}.`,
    onClose: () =&amp;gt; analytics.track(&quot;Module Popup Closed&quot;)
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To show this inside WordPress, paste the popup script into the exact template or block that renders your checkout or lesson page. If you need it site-wide, add it through WPCode but wrap the call in a condition (for example, check the current page slug in a small inline script) so it only opens where it makes sense. Control width so it fits phones. Toggle it with &lt;code&gt;Agent.open()&lt;/code&gt;, &lt;code&gt;Agent.close()&lt;/code&gt;, or &lt;code&gt;Agent.toggle()&lt;/code&gt; when WordPress detects a stalled learner (for example, after a custom block notices no quiz submission).&lt;/p&gt;
&lt;h4 id=&quot;5-test-monitor-improve&quot;&gt;5. Test, monitor, improve&lt;/h4&gt;
&lt;p&gt;Ask the bot for edge cases, like grading rules or refund deadlines, and see if it cites the right file chunk. Watch engagement logs to see if the preview timing or popup triggers are annoying people. Adjust the scripts until questions fall under the 20% escalation goal. Re-upload files whenever you add modules so the index stays current.&lt;/p&gt;
&lt;h3 id=&quot;wrap-up-1&quot;&gt;Wrap up&lt;/h3&gt;
&lt;p&gt;When you start with helping a course creator support and convert students, you only need three features: Bubble Widget, Popup Widget, and File Search &amp;amp; RAG. Build the agent, load the files, drop in both widgets through the WordPress Site Editor or WPCode, and keep testing. You get faster responses for students, fewer repetitive tickets for the creator, and better odds that everyone finishes the course.&lt;/p&gt;
</description>
<category>AI</category>
<guid isPermaLink="true">https://coderlegion.com/8381/build-an-ai-chatbot-for-course-creators-on-wordpress</guid>
<pubDate>Thu, 11 Dec 2025 09:56:51 +0000</pubDate>
</item>
<item>
<title>DevLog 20250820: Towards Unified Chat Gateway</title>
<link>https://coderlegion.com/4882/devlog-20250820-towards-unified-chat-gateway</link>
<description>&lt;h2 id=&quot;devlog-20250820-towards-unified-chat-gateway&quot;&gt;DevLog 20250820: Towards Unified Chat Gateway&lt;/h2&gt;
&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;Ever since the general availability of &lt;a rel=&quot;nofollow&quot; href=&quot;https://openai.com/index/chatgpt/&quot;&gt;ChatGPT&lt;/a&gt;, the problem of building a custom chatbot seemed - at least for a while - like it should have been greatly simplified.&lt;/p&gt;
&lt;p&gt;Then came the widespread use of &lt;a rel=&quot;nofollow&quot; href=&quot;https://arxiv.org/abs/2005.11401&quot;&gt;RAG&lt;/a&gt;, and now we've entered the era of &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.methodox.io/opinion-some-reflections-on-mcp-4d4d24d76779&quot;&gt;MCP&lt;/a&gt;. A marvelous journey indeed.&lt;/p&gt;
&lt;p&gt;But let's return to the chatbot problem.&lt;/p&gt;
&lt;h3 id=&quot;traditional-chatbot-development&quot;&gt;Traditional Chatbot Development&lt;/h3&gt;
&lt;p&gt;The first chatbot I built had 5,000 lines of code - most of them simple if-else statements. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/Rd8E-iSiLZ0?si=1SUCzHlPiElDiVw_&quot;&gt;last one I built&lt;/a&gt; had basic English grammar built-in and could even converse with other bots.&lt;/p&gt;
&lt;p&gt;Traditionally, chatbots were built around a few core methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rule-Based Systems&lt;/strong&gt; – Early bots relied on scripted &quot;if-then&quot; rules or pattern matching (like ELIZA). They could handle predictable inputs but broke down when faced with anything unexpected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieval-Based Bots&lt;/strong&gt; – Instead of generating new text, these selected the best response from a predefined set using keyword search or similarity measures. They worked well for FAQs but lacked flexibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generative Models (Pre-LLM)&lt;/strong&gt; – With Seq2Seq models (RNNs, LSTMs), chatbots began producing novel responses. While more dynamic, these struggled with coherence and factual accuracy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hybrid Approaches&lt;/strong&gt; – Developers often combined rules, retrieval, and basic generative models, managed by a dialogue manager to maintain context. Knowledge bases or ontologies were common in customer support.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Less traditionally ones like &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.inductorsoftware.com/perplexity.html&quot;&gt;Perplexity by Eric Zinda&lt;/a&gt; seems to have full understanding of semantics of words (and is built using Prolog).&lt;/p&gt;
&lt;p&gt;Importantly, in practice, &lt;strong&gt;most production &quot;Chat&quot; windows on websites were not fully automated&lt;/strong&gt;. They were a blend of &lt;strong&gt;machine + human&lt;/strong&gt;, where the chatbot handled basic queries and &lt;strong&gt;human agents stood by to take over&lt;/strong&gt; for complex requests. This ensured customer satisfaction while keeping automation lightweight. A notable example, widely used today, is &lt;a rel=&quot;nofollow&quot; href=&quot;https://crisp.chat/en/&quot;&gt;Crisp&lt;/a&gt;. I mention it here only because I consider it a competitor to what we are aiming to offer.&lt;/p&gt;
&lt;h3 id=&quot;the-challenges-with-using-llm-services&quot;&gt;The Challenges with Using LLM Services&lt;/h3&gt;
&lt;p&gt;The Python community is blessed. They always seem to get the latest of everything - machine learning libraries, deep learning frameworks, and APIs. For most Python users, invoking ChatGPT or other inference providers (like Hugging Face or NIM) is just a matter of calling an API. For the rest of the world (e.g., C# developers), it usually means &lt;a rel=&quot;nofollow&quot; href=&quot;https://wiki.methodox.io/en/KnowledgeBase/HTTP&quot;&gt;making HTTP requests&lt;/a&gt; - until an official native library is available.&lt;/p&gt;
&lt;p&gt;There's also the matter of &lt;a rel=&quot;nofollow&quot; href=&quot;https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses&quot;&gt;streaming API responses&lt;/a&gt;. This is often marketed as a big UI boost, and I've attended LangChain seminars emphasizing how responsive GUIs make LLMs appear &quot;less slow.&quot; But in reality, for non-trivial tasks, I &lt;strong&gt;expect&lt;/strong&gt; some thinking time. As long as there's a clear visual cue showing the service is alive, I'm fine. Streaming isn't always essential.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frzflcvr6a7rcw7zqqyw.png&quot; alt=&quot;Simple yet effective status indicator&quot;&gt;&lt;/p&gt;
&lt;p&gt;That's also exactly what happens in IM apps for conversations between real people.&lt;/p&gt;
&lt;h3 id=&quot;how-knowledge-retrieval-works&quot;&gt;How Knowledge Retrieval Works&lt;/h3&gt;
&lt;p&gt;LLMs predict the next word. The essence of an LLM-based chat agent is to give it the right &lt;strong&gt;context&lt;/strong&gt;. RAG provides that context by feeding the LLM (as system prompt) the necessary information, automatically retrieved through similarity search.&lt;/p&gt;
&lt;p&gt;The most extreme form of &quot;RAG&quot; - which I've heard of in production systems at NVIDIA's GDA conference - is simply passing &lt;strong&gt;everything needed&lt;/strong&gt; into the system prompt. Don't laugh - sometimes the simplest solution is the most effective.&lt;/p&gt;
&lt;p&gt;Vector databases store these embeddings, but today there are far too many to choose from. For simple cases, you can get by with a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/yodobrin/CompactVectorSearch&quot;&gt;basic cosine similarity calculation&lt;/a&gt; if the dataset isn't too large.&lt;/p&gt;
&lt;h3 id=&quot;the-challenges&quot;&gt;The Challenges&lt;/h3&gt;
&lt;p&gt;Building a real-world chatbot typically involves:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connecting to a backend LLM service&lt;/li&gt;
&lt;li&gt;Assembling system prompts - either by concatenating everything or using RAG&lt;/li&gt;
&lt;li&gt;For RAG systems: uploading documents, generating embeddings, storing them, and enabling queries&lt;/li&gt;
&lt;li&gt;Providing a functional GUI&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's it! Simple in theory - but even the first step can be daunting.&lt;/p&gt;
&lt;p&gt;First, there are so many LLM services to choose from. On top of that, higher-level offerings like Google NotebookLM, AWS Bedrock, Hugging Face Inference Engine, and NIM complicate the picture.&lt;/p&gt;
&lt;p&gt;Here's the secret: the only way to know which works best for you is to try them - &lt;strong&gt;extensively&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Second, even when using a hosted inference service instead of self-hosting, there's the issue of authentication if your product is public-facing. Without safeguards, anyone could hijack your service. That means setting up an authentication server.&lt;/p&gt;
&lt;p&gt;System prompt assembly and vector database work, by contrast, are routine engineering tasks - straightforward enough once you know what you're doing.&lt;/p&gt;
&lt;h3 id=&quot;what-i-really-want&quot;&gt;What I Really Want&lt;/h3&gt;
&lt;p&gt;As a developer, what I really want is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I have a bunch of files/documents&lt;/li&gt;
&lt;li&gt;I upload them to a service&lt;/li&gt;
&lt;li&gt;The service creates a ready-to-use agent&lt;/li&gt;
&lt;li&gt;I simply call that specialized agent for my purpose&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In short, I'd like to embed a chatbot with just a snippet like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&amp;lt;script src=&quot;/widget/chat-widget.js&quot;
        data-chat-widget
        data-project=&quot;ChatGateway&quot;
        data-title=&quot;Chat with Methodox (AI)&quot;
        data-display-name=&quot;Chat Gateway&quot;
        data-welcome-message=&quot;Hi! I'm your AI assistant. Ask me anything.&quot;
        data-position=&quot;right&quot;
        data-open=&quot;false&quot;
        data-primary=&quot;#2563eb&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Surely, many providers already offer something similar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chatbase&lt;/li&gt;
&lt;li&gt;Azure&lt;/li&gt;
&lt;li&gt;Clause Enterprise&lt;/li&gt;
&lt;li&gt;Tidio&lt;/li&gt;
&lt;li&gt;Octane AI&lt;/li&gt;
&lt;li&gt;Social Intents&lt;/li&gt;
&lt;li&gt;Regal...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But here's the reality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chatbase&lt;/strong&gt; leans heavily on analytics and FAQ-style bots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure&lt;/strong&gt; gives you the parts (Cognitive Search + OpenAI) but makes you engineer the pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clause Enterprise&lt;/strong&gt; is weighed down by compliance workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tidio&lt;/strong&gt; focuses on rule-based customer support flows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Octane AI&lt;/strong&gt; is optimized for e-commerce funnels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Social Intents&lt;/strong&gt; is designed for live handoffs, not retrieval.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regal&lt;/strong&gt; focuses on sales/CRM engagement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these involve vendor lock-in, integration overhead, or enterprise baggage. You end up hacking around features you don't need.&lt;/p&gt;
&lt;p&gt;In short, there's no truly lightweight option for just &lt;strong&gt;documents + embeddings + an LLM wrapper&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;To recap, a proper service for this use case should be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A host for uploaded documents&lt;/li&gt;
&lt;li&gt;An API server for RAG-based retrieval&lt;/li&gt;
&lt;li&gt;Designed for direct public use&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last point is crucial: such a service must allow direct frontend embedding, without worrying about API key protection.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-1&quot;&gt;What's Next&lt;/h3&gt;
&lt;p&gt;We're now building exactly the service I've been describing: &lt;strong&gt;Methodox Chat Gateway&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Chat Gateway is a &lt;strong&gt;production-ready chat layer&lt;/strong&gt; that lets you add an AI assistant to any site or app - powered by your own knowledge base. Think of it as &lt;em&gt;documents + embeddings + an LLM wrapper&lt;/em&gt;, packaged into something you can deploy in minutes.&lt;/p&gt;
&lt;p&gt;With just a single &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, you can drop a polished chat widget into your frontend, or spin up a full-page project chat at &lt;code&gt;/Project/{name}&lt;/code&gt;. Each project has its own scoped assistant, model settings, and optional vector store for retrieval. RAG status is even shown transparently (“RAG: on/off”) so your users know when answers are grounded in your documents.&lt;/p&gt;
&lt;p&gt;Behind the scenes, Chat Gateway provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple embedding&lt;/strong&gt; – add a chat button with no-code widget setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project-scoped assistants&lt;/strong&gt; – each project is isolated, configurable, and connected only to its own knowledge base.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hosted project pages&lt;/strong&gt; – share live demos or support portals instantly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Straightforward API&lt;/strong&gt; – post user input and get structured responses, with continuity across sessions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in guardrails&lt;/strong&gt; – request limits and concurrency caps to keep things reliable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike heavier enterprise platforms, Chat Gateway is designed for &lt;strong&gt;direct public use&lt;/strong&gt;: no extra infrastructure for API key protection, no bloated CRM features, and no vendor lock-in. It's simply a lightweight way to put your content to work through an AI assistant.&lt;/p&gt;
&lt;p&gt;If you want to see it in action, check out the &lt;a rel=&quot;nofollow&quot; href=&quot;https://chat-gateway.methodox.io/index.html&quot;&gt;Playground&lt;/a&gt; or visit the live demo at &lt;a rel=&quot;nofollow&quot; href=&quot;https://chat-gateway.methodox.io/Project/ChatGateway&quot;&gt;/Project/ChatGateway&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We'll be sharing a full launch announcement soon  -  but the vision is clear: &lt;strong&gt;a frictionless, developer-friendly way to embed intelligent, knowledge-backed chat into any experience&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chat online on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://chat-gateway.methodox.io/index.html&quot;&gt;Playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://chat-gateway.methodox.io/&quot;&gt;Methodox Chat Gateway&lt;/a&gt; official website&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wiki.methodox.io/en/MethodoxOne/OnlineServices/ChatGateway&quot;&gt;Methodox Wiki&lt;/a&gt; doc on Methodox Chat Gateway&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Data Science</category>
<guid isPermaLink="true">https://coderlegion.com/4882/devlog-20250820-towards-unified-chat-gateway</guid>
<pubDate>Wed, 20 Aug 2025 21:17:26 +0000</pubDate>
</item>
<item>
<title>Meet the .csv datasets and understand how they works on creating expert system Artificial Intelligence models</title>
<link>https://coderlegion.com/410/meet-datasets-understand-works-creating-expert-system-artificial-intelligence-models</link>
<description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I’m sure you have listened about CSV datasets. If you are here, it's because you're interested in Artificial Intelligence (AI). AI development is often associated with Machine Learning and huge datasets with big databases. Maybe you have a big idea in mind; you search on YouTube or Google, and the first thing you find out is that if your project doesn't require a large amount of data, a .csv dataset is suitable for this. That is true. It is also true that CSV datasets are very useful for data analysis and are used by enterprises to store and save their data.&lt;/p&gt;
&lt;p&gt;I feel that when we talk about CSV datasets, not all is clear. There is much information, but it is not organized in a single place with simple words to allow beginners to understand and use them effectively. The goal of this post is to understand what CSV datasets are, the differences between Machine Learning and Expert Systems, how traditional Expert Systems allow us to automate many tasks, when and how to use .csv datasets, and what the main uses of CSV datasets are.&lt;/p&gt;
&lt;h4 id=&quot;what-this-post-will-not-teach-you&quot;&gt;What this post will not teach you&lt;/h4&gt;
&lt;p&gt;In this post, I will not explain how to write code that makes CSV files work. This post is centered on CSV files/datasets. Perhaps next week, in another post, I will explain how to create a basic chatbot, assistant, or enterprise bot.&lt;/p&gt;
&lt;h2 id=&quot;csv-dataset-definition&quot;&gt;Csv dataset definition&lt;/h2&gt;
&lt;p&gt;I could use a technical concept from external sources to give a definition of a CSV file, also called CSV dataset or &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Comma-separated_values&quot;&gt;Comma Separated values&lt;/a&gt;, but it’s not necessary. Instead, I'll define it and explain it from my understanding and with my own words. A CSV dataset is a collection of structured/organized data often used for creating small artificial intelligence models that do not require much data. They are organized in variables. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Variable 
question, answer
# Information
What is a csv dataset?, is a collection of data saved very well organized.
How can i create a csv dataset?, at the end of the file add the extension .scv

# As you can see in the example the variables are separated with a comma
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;In the example above the part before the comma is the question and the  next part is the answer, the order have to match with the variables names defined on the first line. &lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;In this screenshot, I have a dataset with three variables: &lt;em&gt;image_path&lt;/em&gt; refers to the source of the image, &lt;em&gt;image_name&lt;/em&gt; is the name of the image, and &lt;em&gt;image_description&lt;/em&gt; is, as you can imagine, the description given to the image. In this particular case, I'm working with image recognition. However, if you need to work only with messages and answers, the pattern will be the same but with two variables: question and answer, as shown in the next example.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    image_path, image_name, image_description

    static/images/tucan.jpg,Tucán,Un ave colorida con un gran pico, nativa de las selvas tropicales.
    static/images/jaguar.jpeg,Jaguar,Un felino grande y poderoso que habita en América Latina.
    static/images/tapir.jpeg,Tapir,Un mamífero grande y herbívoro que se encuentra en las selvas tropicales.
    static/images/mango.jpeg,Mango,Un fruto tropical dulce y jugoso, muy popular en América Latina.
    static/images/cafe.jpeg,Café,Granos que se utilizan para hacer una bebida estimulante conocida mundialmente.
    static/images/guayaba.jpeg,Guayaba,Una fruta tropical rica en vitamina C, con un sabor dulce y ácido.


&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;tools-to-create-a-csv-file&quot;&gt;Tools to create a CSV file&lt;/h4&gt;
&lt;p&gt;It depends on your goals or the type of data you’re working with. CSV files can be created using Notepad++, Microsoft Excel, or a code editor like Visual Studio or PyCharm.&lt;/p&gt;
&lt;h4 id=&quot;creating-a-csv-dataset&quot;&gt;Creating a CSV dataset&lt;/h4&gt;
&lt;p&gt;To create a CSV file using Excel, select the &lt;strong&gt;type CSV&lt;/strong&gt; (comma delimited file, using a text or code editor, name your file and add the extension .csv, for example, &lt;strong&gt;dataset.csv&lt;/strong&gt; you can use the file for creating a dataset on external services like &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/en/cloud/saas/otbi/otbi-pub-design/create-dataset-using-csv-file.html#GUID-96345CA2-EB31-4FC6-9F21-E294FA1BC28E&quot;&gt;Oracle, or any other cloud service provider.&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;reading-a-csv-dataset&quot;&gt;Reading a CSV dataset&lt;/h4&gt;
&lt;p&gt;If you need to open and edit your CSV dataset, simply go to your files, locate the CSV dataset, right-click on it, and select &quot;Open with.&quot; You can open it with Notepad or a code editor like Visual Studio Code. Once opened, you will be able to read, edit, and save your changes.&lt;/p&gt;
&lt;h3 id=&quot;differences-between-machine-learning-and-expert-systems&quot;&gt;Differences between Machine Learning and Expert Systems&lt;/h3&gt;
&lt;p&gt;First of all, why am I talking about Machine Learning here? When somebody talks about artificial intelligence, we often associate it with Machine Learning or Natural Language Processing or think that it is something different apart from AI. But the reality is that Machine Learning, Natural Language Processing, Computer Vision, Expert Systems, and many others are types of Artificial Intelligence.&lt;/p&gt;
&lt;p&gt;The traditional Expert System is a method of artificial intelligence that uses CSV files in their datasets. In this AI model, the developer defines the possible messages or characteristics and provides specific answers if one of those possible messages or characteristics is present.&lt;/p&gt;
&lt;p&gt;In a Machine Learning model, you provide a database or a large collection of data, and the model itself uses this data to compare the content of the message with the existing information in the database. If this data coincides with one element in the database, the Machine Learning model will give an answer based on this data.&lt;/p&gt;
&lt;p&gt;Both methods have their advantages and disadvantages. Machine Learning models are very useful when working with large language models and other projects with high level of unknowns, however the answers provided by these models can sometimes be inaccurate. So, when we need a bot that provides very accurate, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.parascript.com/blog/machine-learning-ai-vs-expert-systems-ai/&quot;&gt;precise answers&lt;/a&gt;, we use Expert System models trained with a CSV dataset.&lt;/p&gt;
&lt;h3 id=&quot;how-do-traditional-expert-systems-allow-us-to-automate-tasks-&quot;&gt;How do traditional Expert Systems allow us to automate tasks?&lt;/h3&gt;
&lt;p&gt;Let me use an analogy so that we can understand very well how to use CSV datasets with a traditional Expert System AI model to automate repetitive tasks. Imagine that you are a small, medium, or large business that sells products, cellphones, let's say. To attract more clients, you provide support via WhatsApp or on your website. Those clients will ask specific questions about the prices of specific models. They will ask about promotions, available models, schedules of attention, and so on. All those tasks are repetitive and time-consuming. A good amount of those clients will ask questions and not buy, so you're losing your valuable time. What if we create a bot that can answer each question from each client automatically, specifically, and instantly?&lt;/p&gt;
&lt;p&gt;Problems like this can be solved using a CSV dataset in which you define the answer for every possible question or message. Of course, you'll need to develop a simple model capable of reading the file, receiving the message sent to the server, and responding with the specific answer. Now, when the client asks something, the bot will respond instantly, reducing time, work, and providing a good experience for the user.&lt;/p&gt;
&lt;h3 id=&quot;uses-of-csv-datasets-&quot;&gt;Uses of .csv datasets?&lt;/h3&gt;
&lt;p&gt;Use a .csv dataset anytime you need to develop a product (AI model) whose first requisite is very accurate and specific answers, as we discussed earlier. In simple terms, this kind of dataset is used for creating Expert System models, but a CSV dataset can also be used to store data in a structured or organized way. Data that can later be used for data analysis and facilitate decision-making for administrative personnel. &lt;/p&gt;
&lt;h4 id=&quot;main-uses-of-csv-datasets-explained&quot;&gt;Main uses of CSV datasets explained&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chatbots:&lt;/strong&gt; CSV datasets can be used to create assistant bots for small, medium, and large businesses or enterprises. These bots can be integrated with Twilio, WhatsApp, Telegram, or a website. &lt;em&gt;Next week, I'll publish a tutorial to explain step-by-step how to create a chatbot.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data store:&lt;/strong&gt; Save data in an organized manner. You can also use other tools that interact with the data you want to save to write a CSV file or update it automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data analysis tools:&lt;/strong&gt; CSV datasets can be easily manipulated with Python libraries like Pandas and Matplotlib for data analysis and visualization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Image recognition (integrated with other tools):&lt;/strong&gt; As shown in the earlier screenshot, you can create a CSV dataset with a specific structure to integrate with other tools for image recognition systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.oneschema.co/blog/csv-files&quot;&gt;Other uses&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;how-to-use-&quot;&gt;How to use?&lt;/h4&gt;
&lt;p&gt;The answer to this question depends on the goal or what you’re using it for. Let's say that if you want to create an assistant chatbot, the first step is to create your CSV file with the specific possible questions and the respective answers for each question. But that is not all; you will also need a programming language to train the model (the Expert System I explained earlier). Additionally, this model needs to be integrated into a website or with services like WhatsApp, Twilio, or others. The &quot;how-to&quot; is not covered in this post because it needs more space to cover the step-by-step integration. In this post, as I explained in the introduction, my goal is to understand what CSV datasets are and how we can use them to make our lives easier.&lt;/p&gt;
&lt;h3 id=&quot;advantages-of-a-csv-datasets&quot;&gt;Advantages of a CSV datasets&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Accurate and specific answers:&lt;/strong&gt; When used in assistants or chatbots, you can specify the answer for the possible questions or messages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personalization:&lt;/strong&gt; You can create or update your data according to your needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organization:&lt;/strong&gt; You can save or write your data from other programs organized in variables according to your needs, which will improve readability and allow this data to be integrated with other programs, libraries, or tools in general to analyze its information.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A CSV dataset is a &lt;strong&gt;collection of organized data&lt;/strong&gt;. These files can be used in combination with the &lt;strong&gt;traditional artificial intelligence&lt;/strong&gt; methodology called &lt;strong&gt;Expert System&lt;/strong&gt; to create automated bots, assistants, data analysis tools, and many other &lt;strong&gt;automation tools&lt;/strong&gt; for small, medium, and large businesses interested in saving time, money, and work, while providing good, &lt;strong&gt;personalized&lt;/strong&gt;, and &lt;strong&gt;instant&lt;/strong&gt; assistance to their &lt;strong&gt;clients&lt;/strong&gt;. They are also used to &lt;strong&gt;store data&lt;/strong&gt; in a well-organized and structured way to be later used in data analysis, facilitating &lt;strong&gt;decision-making&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/410/meet-datasets-understand-works-creating-expert-system-artificial-intelligence-models</guid>
<pubDate>Sun, 20 Oct 2024 02:03:52 +0000</pubDate>
</item>
<item>
<title>Deploy an AI journalist chatbot with Gradio and Supabase</title>
<link>https://coderlegion.com/291/deploy-an-ai-journalist-chatbot-with-gradio-and-supabase</link>
<description>&lt;h2&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;supabase-ai-chat&lt;/strong&gt; is natively developed as a Gradio-backed HuggingFace (HF) space and can be found &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/spaces/as-cle-bert/supabase-ai-chat&quot;&gt;here&lt;/a&gt;: it was developed to serve as a knowledgeable assistant in the field of AI news.&lt;/p&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Want something more?&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt; There is also a version built on top of Flowise but it is only a test deployment and, due to security reasons, its access is limited: you can request the access writing a private message on CoderLegion in which you explain who you are and why you would want to use the application.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;It is an application that exploits Supabase (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/supabase/supabase&quot;&gt;the open-source alternative to Firebase&lt;/a&gt;) as a vector store to search for context information and HF Spaces API to query and retrieve responses from Phi-3-128K.&lt;/p&gt;
&lt;p&gt;supabase-ai-chat was built selecting 5000 AI-related news articles from &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/datasets/siavava/ai-tech-articles&quot;&gt;ai-tech-articles&lt;/a&gt; dataset on HF Hub, with the help of Supabase as a vector store (if you don't know what vectore stores are, here is an article that I wrote about them: &lt;a rel=&quot;nofollow&quot; href=&quot;https://astrabert.github.io/hophop-science/Vector-databases-explained/&quot;&gt;&quot;Vector databases explained&quot;&lt;/a&gt;). Let's break dow the steps we need to take to build this application.&lt;/p&gt;
&lt;h2&gt;2. Pre-requisites&lt;/h2&gt;
&lt;h3&gt;2a. Supabase&lt;/h3&gt;
&lt;p&gt;Before we start building our application, we will need &lt;strong&gt;to access Supabase&lt;/strong&gt;: you can head over to &lt;a rel=&quot;nofollow&quot; href=&quot;https://supabase.com/&quot;&gt;their website&lt;/a&gt; and register through your GitHub account (which is definitely the simplest way to get access to their services).&lt;/p&gt;
&lt;p&gt;Once your account has been created, you can go to &lt;em&gt;&quot;Dashboard&quot;&lt;/em&gt; and select &lt;em&gt;&quot;New Project&quot;&lt;/em&gt;. This will take you to the actual project you can build and it will require you to name it and save a password: &lt;strong&gt;do not lose the password&lt;/strong&gt;, because it will be useful later on. &lt;/p&gt;
&lt;p&gt;After Supabase manages all the dependencies of your project, you will notice that there will be both an &lt;strong&gt;API Key&lt;/strong&gt; and a &lt;strong&gt;Project URL&lt;/strong&gt; that pop up as soon as you enter the page: they won't be useful to us for this project, but they can definitely be in the future.&lt;/p&gt;
&lt;p&gt;Now make sure to go to &lt;strong&gt;&quot;Project Settings &amp;gt; Database&quot;&lt;/strong&gt; and copy the &lt;em&gt;Connection String&lt;/em&gt;: store it somewhere, for now, as it will be fundamental to build the application.&lt;/p&gt;
&lt;h3&gt;2b. HuggingFace&lt;/h3&gt;
&lt;p&gt;In order to get  an &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/&quot;&gt;Hugging Face&lt;/a&gt; account:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to Hugging Face from the link above and click on the &lt;strong&gt;Sign up&lt;/strong&gt; button. Alternatively, you can directly follow &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/join&quot;&gt;this link&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Provide e-mail address and password&lt;/li&gt;
&lt;li&gt;Follow the instructions as you are prompted by the registration procedure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. Folder Setup&lt;/h2&gt;
&lt;p&gt;You will need to have a specific folder setup in order to build this application. You can achieve that setup by cloning the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/AstraBert/supabase-ai-chat&quot;&gt;GitHub repository&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/AstraBert/supabase-ai-chat.git
cd supabase-ai-chat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's now take a look at the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./
├── README.md
├── app.py
├── .env.example
├── data
│   └── ainews_5k.csv.gz
├── requirements.txt
├── supabase_upsert.py
└── utils.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;app.py&lt;/strong&gt;, which is the script we will be using to design our application&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;utils.py&lt;/strong&gt;, which is the script where we will be storing useful classes and functions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;supabase_upsert.py&lt;/strong&gt;, which is the script that we will upload our AI news to our Supabase project with&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;requirements.txt&lt;/strong&gt;, which contains the needed dependencies for this project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;data/ainews_5k.csv.gz&lt;/strong&gt;, which is the compressed version of the file that stores all the 5000 news articles about AI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.env.example&lt;/strong&gt;, which is the example file to store environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can set our environment up by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, on the other hand, we complete the configuration by renaming the &lt;strong&gt;.env.example&lt;/strong&gt; file to &lt;strong&gt;.env&lt;/strong&gt; and by substituting to the placeholder string associated with &lt;em&gt;supabase_db&lt;/em&gt; environment variable. An example of that would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;supabase_db=postgresql://postgres.fsaosjjgbgpbsiag:*Emails are not allowed*:5432/postgres
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember that it is important to write &lt;strong&gt;postgresql&lt;/strong&gt; at the beginning of the URL, and not only &lt;strong&gt;postgres&lt;/strong&gt;, as the newest versions of &lt;em&gt;vecs&lt;/em&gt;, the python package that serves as a PostgresSQL client, do not support this last variation of the baseurl. &lt;/p&gt;
&lt;h2&gt;4. Prepare Supabase Collection&lt;/h2&gt;
&lt;p&gt;Now that we have everything set up in terms of environment, we can upload the documents to our Supabase project, configuring them as a collection. We will be doing it using the &lt;strong&gt;supabase_upsert.py&lt;/strong&gt; script we downloaded earlier from GitHub.&lt;/p&gt;
&lt;p&gt;Let's take a quick look at the code!&lt;/p&gt;
&lt;p&gt;We start by importing the needed dependencies and loading the environment variable related to our Supabase database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import vecs
from dotenv import load_dotenv
import os

load_dotenv()
DB_CONNECTION = os.getenv(&quot;supabase_db&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we move on to connect to Supabase client and create a new collection called &lt;em&gt;documents&lt;/em&gt;, which will be receiving vectors embedding of a size of 384:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vx = vecs.create_client(DB_CONNECTION)
docs = vx.get_or_create_collection(name=&quot;documents&quot;, dimension=384)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we load the word-embedding model and proceed with the upsertion of the AI news to the Supabase collection (we need to read the csv, first):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pandas as pd
df = pd.read_csv(&quot;data/ainews_5k.csv.gz&quot;)
idex = list(df[&quot;id&quot;])
texts = list(df[&quot;text&quot;])
data = [{'id': idex[i], 'content': texts[i], 'embedding': []} for i in range(len(texts))]

from sentence_transformers import SentenceTransformers
encoder = SentenceTransformer(&quot;all-MiniLM-L6-v2&quot;)

for item in data:
    docs.upsert(records=[(item['id'],encoder.encode(item['content']).tolist(), {&quot;Content&quot;: item['content']}),])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last, but not least, portion of the code creates a vector index to ease the search for our retrieval algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docs.create_index(measure=vecs.IndexMeasure.cosine_distance)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. Define Utilities&lt;/h2&gt;
&lt;p&gt;After having upserted the AI news to Supabase, we just need to define two classes that will help us managing the queries from the users. &lt;/p&gt;
&lt;p&gt;The first one involves the possibility of translating the user's prompt from the original language to English, and then the response from English back to the original language:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langdetect import detect
from deep_translator import GoogleTranslator

class Translation:
    def __init__(self, text, destination):
        self.text = text
        self.destination = destination
        try:
            self.original = detect(self.text)
        except Exception as e:
            self.original = &quot;auto&quot;
    def translatef(self):
        translator = GoogleTranslator(source=self.original, target=self.destination)
        translation = translator.translate(self.text)
        return translation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we use two python packages, &lt;strong&gt;langdetect&lt;/strong&gt; and &lt;strong&gt;deep_translator&lt;/strong&gt;, to achieve the goal. &lt;/p&gt;
&lt;p&gt;The second class allows us to search a Supabase collection using a text query (which is transformed into vectors on the fly by the same encoder we used to upload everything to the collection).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class NeuralSearcher:
    def __init__(self, collection, encoder):
        self.collection = collection
        self.encoder = encoder
    def search(self, text):
        results = self.collection.query(
            data=self.encoder.encode(text).tolist(),  # required
            limit=1,                     # number of records to return
            filters={},                  # metadata filters
            measure=&quot;cosine_distance&quot;,   # distance measure to use
            include_value=True,         # should distance measure values be returned?
            include_metadata=True,      # should record metadata be returned?
        )
        return results
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this simple example project, we do not define filters: this can be done for more advanced projects and more complex necessities.&lt;/p&gt;
&lt;h2&gt;6. Build the Application&lt;/h2&gt;
&lt;p&gt;Now that we have everything set up, we can actually build our Gradio application that will be hosted in HF Spaces. &lt;/p&gt;
&lt;p&gt;We open &lt;strong&gt;app.py&lt;/strong&gt; and start by importing packages and defining constants:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import gradio as gr
from utils import Translation, NeuralSearcher
from gradio_client import Client
import os
import vecs
from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer

load_dotenv()

collection_name = &quot;documents&quot;
encoder = SentenceTransformer(&quot;all-MiniLM-L6-v2&quot;)
client = os.getenv(&quot;supabase_db&quot;)
api_client = Client(&quot;eswardivi/Phi-3-mini-128k-instruct&quot;)
lan = &quot;en&quot;
vx = vecs.create_client(client)
docs = vx.get_or_create_collection(name=collection_name, dimension=384)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have defined our Supabase and Gradio API clients, we can finally build a function that takes the user query, recognizes the language, performs various translation tasks, retrieves some relevant information from Supabase collection, feeds them as context to Phi-3-128K along with the actual message from the user, and then finally gets a response. You can refer to the flowchart here, for a better visualization of the process: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/AstraBert/supabase-ai-chat/raw/main/data/supabase.drawio.png&quot; alt=&quot;Flowchart&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is the function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def reply(message, history):
    global docs
    global encoder
    global api_client
    global lan
    txt = Translation(message, &quot;en&quot;)
    print(txt.original, lan)
    if txt.original == &quot;en&quot; and lan == &quot;en&quot;:
        txt2txt = NeuralSearcher(docs, encoder)
        results = txt2txt.search(message)
        response = api_client.predict(
            f&quot;Context: {results[0][2]['Content']}; Prompt: {message}&quot;,# str  in 'Message' Textbox component
            0.4,# float (numeric value between 0 and 1) in 'Temperature' Slider component
            True,# bool  in 'Sampling' Checkbox component
            512,# float (numeric value between 128 and 4096)
            api_name=&quot;/chat&quot;
        )
        return response
    elif txt.original == &quot;en&quot; and lan != &quot;en&quot;:
        txt2txt = NeuralSearcher(docs, encoder)
        transl = Translation(message, lan)
        message = transl.translatef()
        results = txt2txt.search(message)
        t = Translation(results[0][2]['Content'], txt.original)
        res = t.translatef()
        response = api_client.predict(
            f&quot;Context: {res}; Prompt: {message}&quot;,
            0.4,
            True,
            512,
            api_name=&quot;/chat&quot;
        )
        response = Translation(response, txt.original)
        return response.translatef()
    elif txt.original != &quot;en&quot; and lan == &quot;en&quot;:
        txt2txt = NeuralSearcher(docs, encoder)
        results = txt2txt.search(message)
        transl = Translation(results[0][2]['Content'], &quot;en&quot;)
        translation = transl.translatef()
        response = api_client.predict(
            f&quot;Context: {translation}; Prompt: {message}&quot;,
            0.4,
            True,
            512,
            api_name=&quot;/chat&quot;
        )
        t = Translation(response, txt.original)
        res = t.translatef()
        return res
    else:
        txt2txt = NeuralSearcher(docs, encoder)
        transl = Translation(message, lan.replace(&quot;\\&quot;,&quot;&quot;).replace(&quot;'&quot;,&quot;&quot;))
        message = transl.translatef()
        results = txt2txt.search(message)
        t = Translation(results[0][2]['Content'], txt.original)
        res = t.translatef()
        response = api_client.predict(
            f&quot;Context: {res}; Prompt: {message}&quot;,# str  in 'Message' Textbox component
            0.4,
            True,
            512,
            api_name=&quot;/chat&quot;
        )
        tr = Translation(response, txt.original)
        ress = tr.translatef()
        return ress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, in order to build the Chat Interface, Gradio offers solutions that hardly have competitors in terms of simplicity: as a matter of facts, we actually need one line of code to define the interface and one to launch it. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;demo = gr.ChatInterface(fn=reply, title=&quot;Supabase AI Journalist&quot;)
demo.launch(server_name=&quot;0.0.0.0&quot;, share=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if you run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From your terminal, in less than 30 seconds you will see that &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:7860&quot;&gt;http://localhost:7860&lt;/a&gt;&lt;/strong&gt; gets fills with life: our chatbot is loaded and ready to be used! We can now query it by just writing a prompt and patiently waiting for the results to come!&lt;/p&gt;
&lt;p&gt;We can also upload our repository to Spaces, or simply create a space &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/spaces&quot;&gt;here&lt;/a&gt; and paste the code we've just written in files that are named as our local version of them.&lt;/p&gt;
&lt;div class=&quot;div-red&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;WARNING:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt; I strongly suggest, for safety reasons, not to expose your .env file publicly&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;You can, alternatively, define &lt;strong&gt;secret keys in the Space settings&lt;/strong&gt;, and just import the keys by simply including:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;client = os.getenv(&quot;your-secret-key-name&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your app.py script&lt;/p&gt;
&lt;h2&gt;7. Conclusion&lt;/h2&gt;
&lt;p&gt;We now created our first Gradio application: this is the first time we build a fully &lt;strong&gt;front-end and back-end&lt;/strong&gt; defined tool, without relying on third-party interfaces such as Telegram or Discord. &lt;/p&gt;
&lt;p&gt;Moreover, we natively integrated in our project a &lt;strong&gt;vector database&lt;/strong&gt; (Supabase) for text retrieval and a &lt;strong&gt;Large Language Model&lt;/strong&gt; (Phi-3-128K) for text generation: we did everything with &lt;strong&gt;pure python&lt;/strong&gt;: the great part is that the whole code takes &lt;strong&gt;less than 150 lines&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Stay tuned for the next article, in which we will conclude our bot series by talking about &lt;strong&gt;Discord bots&lt;/strong&gt;, and do not forget to live a little star in the repository!&lt;/p&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/291/deploy-an-ai-journalist-chatbot-with-gradio-and-supabase</guid>
<pubDate>Sat, 18 May 2024 08:34:04 +0000</pubDate>
</item>
</channel>
</rss>