<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: HoverBot</title>
    <description>The latest articles on DEV Community by HoverBot (hoverbot).</description>
    <link>https://dev.to/hoverbot</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14652%2Febe4d7ec-d533-45ec-ba8c-935b3fa625c5.png</url>
      <title>DEV Community: HoverBot</title>
      <link>https://dev.to/hoverbot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hoverbot"/>
    <language>en</language>
    <item>
      <title>System One Models for Chatbot Decisions: Testing Jev for PII, Guardrails and Product Selection</title>
      <dc:creator>Vitaly Goncharenko</dc:creator>
      <pubDate>Fri, 25 Sep 2026 02:57:20 +0000</pubDate>
      <link>https://dev.to/hoverbot/system-one-models-for-chatbot-decisions-testing-jev-for-pii-guardrails-and-product-selection-2k5e</link>
      <guid>https://dev.to/hoverbot/system-one-models-for-chatbot-decisions-testing-jev-for-pii-guardrails-and-product-selection-2k5e</guid>
      <description>&lt;p&gt;A chatbot needs language generation, but much of the work around that generation is classification. Does this message contain personal information? Is a draft reply safe to send? Which retrieved product best matches the request? Should the conversation go to support?&lt;/p&gt;

&lt;p&gt;Those questions do not require another paragraph of generated text. They require a value that software can inspect and use. This is the problem TypeSafe AI positions Jev to solve. TypeSafe calls Jev its first public System One model and describes the interface as unstructured state in, typed probabilistic decisions out. Its &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener noreferrer"&gt;launch announcement&lt;/a&gt; says Jev gives up string generation in favor of structured outputs.&lt;/p&gt;

&lt;p&gt;HoverBot is evaluating that pattern. We made seven synthetic test calls on September 25, 2026. The messages were invented and contained no customer data. Every request returned HTTP 200 with the expected answer, taking 0.67 to 0.82 seconds end to end from Singapore, including network time. This was a smoke test of the API shape. It was not an accuracy benchmark, a latency benchmark or evidence of production performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most chatbot decisions are classification, not writing
&lt;/h2&gt;

&lt;p&gt;The visible reply is only one part of a product aware chat pipeline. Before generation, software may inspect the message for sensitive data and select an intent route. After retrieval, it may rank a shortlist. After generation, it may test the draft against safety and commercial rules. At any point, it may decide that a human should take over.&lt;/p&gt;

&lt;p&gt;An LLM can perform these tasks, but the application usually has to request JSON, validate the schema and decide how to handle malformed or ambiguous output. TypeSafe says Jev is built around predefined answer types instead. Its public &lt;a href="https://api.typesafe.ai/docs" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; documents a POST request to &lt;code&gt;https://api.typesafe.ai/v1/systemone&lt;/code&gt; with &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;questions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Jev returns
&lt;/h2&gt;

&lt;p&gt;The TypeSafe API documents three answer families: &lt;code&gt;noul&lt;/code&gt; for a probability, &lt;code&gt;choice&lt;/code&gt; for one option from named criteria, and &lt;code&gt;score&lt;/code&gt; for a position on a defined scale. The unusual word &lt;code&gt;noul&lt;/code&gt; appears in the direct TypeSafe API. Vercel uses the more familiar name &lt;code&gt;boolean&lt;/code&gt; in its gateway interface.&lt;/p&gt;

&lt;p&gt;Here is the reduced shape of our positive PII test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jev-latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi, my order hasnt arrived. I am Anna Lim, phone +65 9123 4567, card ending 4242."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"has_pii"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does this message contain personal data such as a name, phone number, email, address or payment details?"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response identified model version &lt;code&gt;jev-1.13.0&lt;/code&gt; and returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"has_pii"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That value is not a policy by itself. Application code must convert it into an action through a threshold and an uncertainty path.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Check for PII before the LLM
&lt;/h2&gt;

&lt;p&gt;A PII check belongs early in the pipeline, before a message is copied into prompts, logs or downstream tools. In our synthetic positive example, Jev returned &lt;code&gt;0.99&lt;/code&gt;. For the invented message, “Do you have the linen shirt in size M, and is it machine washable?”, it returned &lt;code&gt;0.02&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simple policy might redact or block messages at or above &lt;code&gt;0.90&lt;/code&gt;, pass messages below &lt;code&gt;0.20&lt;/code&gt;, and send the middle band to a stricter detector or human review. Those numbers are illustrations, not recommended universal thresholds. Missing PII can have a much higher cost than pausing an ordinary product question, so the threshold should reflect the data flow and legal context.&lt;/p&gt;

&lt;p&gt;Detection is only one layer. The pipeline still needs minimization, retention controls and clear boundaries around what reaches external models. Our guides to &lt;a href="https://www.hoverbot.ai/blog/protecting-pii-ai-chatbots" rel="noopener noreferrer"&gt;protecting PII&lt;/a&gt; and &lt;a href="https://www.hoverbot.ai/blog/data-privacy-ai-chatbots-2026-update" rel="noopener noreferrer"&gt;chatbot data privacy&lt;/a&gt; cover that wider system.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Apply guardrails to draft replies
&lt;/h2&gt;

&lt;p&gt;Guardrails can inspect an LLM draft before the customer sees it. Our synthetic risky draft prescribed “800mg of ibuprofen three times a day” while selling a posture cushion. One call asked two independent questions: whether the reply gave medical advice and whether it recommended a competitor.&lt;/p&gt;

&lt;p&gt;Jev returned &lt;code&gt;0.99&lt;/code&gt; for medical advice and &lt;code&gt;0.03&lt;/code&gt; for a competitor recommendation. A clean draft about shipping in two to four days and a 30 day return window returned &lt;code&gt;0.01&lt;/code&gt; for both checks.&lt;/p&gt;

&lt;p&gt;The useful pattern is specific checks rather than one vague “Is this safe?” question. Each check can have a different consequence. A high medical advice probability could block the draft and request a rewrite. A competitor mention might trigger review or an approved comparison flow. The check evaluates the provided text. It does not prove that shipping or return statements are factually correct, so those claims still need grounding in trusted business data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Pick from a retrieved catalog shortlist
&lt;/h2&gt;

&lt;p&gt;A product decision model should not search an entire catalog from memory. Retrieval should first produce a current shortlist with relevant attributes. The decision step can then select among known identifiers.&lt;/p&gt;

&lt;p&gt;Our synthetic shopper wanted road running shoes for a half marathon, mentioned mild overpronation and had a budget near 150 dollars. The four candidates included a trail shoe, a stability road shoe, a neutral carbon racer and a walking shoe. Jev chose &lt;code&gt;sku_road_stability&lt;/code&gt;, the 140 dollar road shoe with stability support, and returned a probability map over all four identifiers.&lt;/p&gt;

&lt;p&gt;This keeps the output inside the retrieved set. It does not validate inventory, price or product specifications. Those fields must come from the catalog source, and the final reply should cite or reflect that source. See our approach to &lt;a href="https://www.hoverbot.ai/blog/knowledge-management-ai-chatbots" rel="noopener noreferrer"&gt;knowledge management for AI chatbots&lt;/a&gt; and &lt;a href="https://www.hoverbot.ai/blog/progress-streaming-catalog-chat" rel="noopener noreferrer"&gt;catalog chat progress streaming&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Route intent and estimate buying intent
&lt;/h2&gt;

&lt;p&gt;One request can answer several related questions. For the invented message, “do you ship to Singapore and how much would 3 of the blue ones cost?”, we asked for an intent choice and a buying intent probability.&lt;/p&gt;

&lt;p&gt;The intent options were product question, order status, complaint and other. Jev selected &lt;code&gt;product_question&lt;/code&gt; and returned &lt;code&gt;0.89&lt;/code&gt; for buying intent. The router could use the choice to fetch shipping and pricing information. The buying signal could alter which actions are offered, but it should not be treated as proof that a purchase will happen.&lt;/p&gt;

&lt;p&gt;The option definitions matter. Overlapping labels create ambiguous supervision and unstable routing. Include a fallback, log the probability distribution and review examples where the top choices are close.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Score human handoff urgency
&lt;/h2&gt;

&lt;p&gt;Escalation is often better represented as a scale than a yes or no flag. Our test defined three levels: no handoff needed, offer a human as an option, and hand off to a human now.&lt;/p&gt;

&lt;p&gt;The synthetic customer said this was the third request, the refund had not arrived, the bot kept repeating the same FAQ link, and a person was wanted immediately. Jev returned score &lt;code&gt;2.0&lt;/code&gt;, mapped to immediate handoff, with probabilities for every level.&lt;/p&gt;

&lt;p&gt;Real routing should combine that score with operational facts such as agent availability, account status and support hours. A model score can prioritize a queue, but it should not silently deny access to a person when policy promises human support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set thresholds around the cost of mistakes
&lt;/h2&gt;

&lt;p&gt;A probability becomes useful only when connected to a policy. Start by naming the two mistakes for each question. For PII, they are letting sensitive data through and interrupting a safe message. For guardrails, they are sending a harmful draft and blocking an acceptable reply.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose an automatic action threshold using labelled examples from the real traffic domain.&lt;/li&gt;
&lt;li&gt;Create a middle band for uncertainty. Route borderline cases to a human, a deterministic rule or an LLM with more context.&lt;/li&gt;
&lt;li&gt;Measure calibration. Among cases scored near 0.80, the positive rate should be examined rather than assumed.&lt;/li&gt;
&lt;li&gt;Set thresholds independently for each use case. PII, buying intent and handoff urgency carry different costs.&lt;/li&gt;
&lt;li&gt;Monitor drift when products, policies, markets or customer language change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TypeSafe says Jev returns probabilities so software can account for uncertainty. That is a product claim, not a substitute for measuring calibration on the population where the chatbot will operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits, and where an LLM is still needed
&lt;/h2&gt;

&lt;p&gt;Jev does not write replies. TypeSafe explicitly presents it as a decision model rather than a text generator. The company also describes inputs as unstructured data, with its published examples centered on text and structured program state. Treat each API call as self contained: send the relevant conversation state again, because the request shape does not provide chatbot memory.&lt;/p&gt;

&lt;p&gt;An LLM is still useful for composing a clear response, asking a tactful follow up question, summarizing a long exchange or explaining a recommendation. Jev can decide which route or candidate to use, while ordinary code constrains actions and an LLM handles language.&lt;/p&gt;

&lt;p&gt;Availability also matters. TypeSafe described Jev as early access in its September 15, 2026 announcement. Separately, &lt;a href="https://vercel.com/changelog/ai-gateway-now-supports-typesafe-clients-and-http-api-for-jev" rel="noopener noreferrer"&gt;Vercel says&lt;/a&gt; its AI Gateway added Jev support on September 21, 2026 through a TypeSafe client, HTTP API and AI SDK. Check current access and terms before planning a rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluate on labelled data before switching
&lt;/h2&gt;

&lt;p&gt;Seven obvious synthetic cases can confirm that requests serialize, responses parse and answer types fit the pipeline. They cannot show performance on typos, indirect language, conflicting evidence, multilingual conversations or adversarial input.&lt;/p&gt;

&lt;p&gt;Build a representative dataset from properly governed, labelled examples. Freeze question instructions and candidate definitions. Split threshold selection from final evaluation. For each use case, report a confusion matrix, precision, recall, review rate and calibration by probability band. For choices, inspect both top choice accuracy and near ties. For scores, measure costly under escalation separately from harmless adjacent errors.&lt;/p&gt;

&lt;p&gt;Then run the candidate beside the existing decision path without allowing it to affect customers. Compare disagreements, investigate failure clusters and test degraded behavior for timeouts or unavailable service. Switch only when the measured tradeoff meets the policy for that specific decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short practical rule
&lt;/h2&gt;

&lt;p&gt;Use a decision model when the acceptable outputs can be named before the call. Use ordinary code to enforce policy, retrieve authoritative data and execute actions. Use an LLM when the chatbot needs to write, explain or continue an open ended conversation. When the probability is borderline, escalate instead of pretending uncertainty is certainty.&lt;/p&gt;

&lt;p&gt;Want to see how decision checks fit into a product-aware chatbot? &lt;a href="https://www.hoverbot.ai/request-demo" rel="noopener noreferrer"&gt;Request a demo&lt;/a&gt; and we will walk through guardrails and PII handling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.hoverbot.ai/request-demo" rel="noopener noreferrer"&gt;Request a demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Showing the Work: Progress Streaming for Catalog-Backed Chat</title>
      <dc:creator>Vitaly Goncharenko</dc:creator>
      <pubDate>Fri, 18 Sep 2026 02:41:30 +0000</pubDate>
      <link>https://dev.to/hoverbot/showing-the-work-progress-streaming-for-catalog-backed-chat-2lc</link>
      <guid>https://dev.to/hoverbot/showing-the-work-progress-streaming-for-catalog-backed-chat-2lc</guid>
      <description>&lt;p&gt;When a catalog-backed chatbot takes more than a second to answer, silence reads as failure. The fix is not making retrieval faster. It is streaming which stage is running so the user knows the system is working.&lt;/p&gt;

&lt;p&gt;This post describes a design we are committing to for HoverBot catalog skills: six named progress stages, a capability-gated SSE transport that degrades to a plain JSON call, an absolute wall-clock deadline, &lt;code&gt;AbortSignal&lt;/code&gt; cancellation, and five explicit stream outcomes. The design is in &lt;a href="https://github.com/HoverBotAI/hoverbot/pull/3" rel="noopener noreferrer"&gt;open PR #3&lt;/a&gt; and &lt;a href="https://github.com/HoverBotAI/hoverbot/pull/6" rel="noopener noreferrer"&gt;open PR #6&lt;/a&gt;, and is not live in production yet. Alexander Khomenko authored the implementation in commit &lt;code&gt;a6d8ac3c&lt;/code&gt; across hoverbot-api, hoverbot-config-ui, and hoverbot-widget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Planned behaviour, not shipped. &lt;a href="https://github.com/HoverBotAI/hoverbot/pull/3" rel="noopener noreferrer"&gt;PR #3&lt;/a&gt; and &lt;a href="https://github.com/HoverBotAI/hoverbot/pull/6" rel="noopener noreferrer"&gt;PR #6&lt;/a&gt; in the hoverbot repo are still open. Treat everything below as the contract we intend to merge, not what you will see on hoverbot.ai today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Silence Fails Before Search Does
&lt;/h2&gt;

&lt;p&gt;Catalog retrieval is a multi-step pipeline. The orchestrator interprets the user's message, resolves product references from prior turns, calls a search backend, validates returned SKUs, ranks candidates, and only then assembles a response. Each step can add hundreds of milliseconds to several seconds depending on catalog size, query complexity, and backend load.&lt;/p&gt;

&lt;p&gt;Users do not experience that as a pipeline. They experience a chat bubble with a typing indicator that never changes. &lt;a href="https://www.nngroup.com/articles/response-times-3-important-limits/" rel="noopener noreferrer"&gt;Nielsen Norman Group's response-time research&lt;/a&gt; identifies one second as the threshold where flow breaks and ten seconds as the point where attention is lost. A catalog query that finishes in four seconds is fast enough to be correct and slow enough to feel broken if the UI says nothing.&lt;/p&gt;

&lt;p&gt;The instinct is to optimize latency. That is worth doing, but it does not solve the perception problem. Even a well-tuned retrieval can spike when a user asks a comparison question across three product lines with constraint filters. You cannot guarantee sub-second answers for every catalog turn. You can guarantee the user sees which stage is running.&lt;/p&gt;

&lt;p&gt;This connects to the broader knowledge-retrieval picture in &lt;a href="https://www.hoverbot.ai/blog/knowledge-management-ai-chatbots" rel="noopener noreferrer"&gt;knowledge management for AI chatbots&lt;/a&gt;: retrieval quality depends on what you fetch, but retrieval UX depends on whether the user waits with context or waits in the dark.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Six Stages
&lt;/h2&gt;

&lt;p&gt;Progress updates are typed against a fixed stage list. No free-form status strings from the backend; the adapter and widget agree on six values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CATALOG_PROGRESS_STAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interpreting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resolving_reference&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retrieving&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validating&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ranking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;building_response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CatalogProgressUpdate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CatalogProgressStage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;elapsedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage maps to a user-facing message in the chat controller:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;interpreting:&lt;/strong&gt; Understanding your request. The orchestrator parses intent, constraints, and search mode before touching the catalog.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;resolving_reference:&lt;/strong&gt; Resolving the products you mentioned. Handles ordinals ("the second one"), pronouns, and context handoff from prior turns.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;retrieving:&lt;/strong&gt; Searching the catalog. The HTTP adapter calls the search backend. This is usually the longest stage.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;validating:&lt;/strong&gt; Checking product information. Confirms returned SKUs exist, are in scope for the tenant, and match the query constraints.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;ranking:&lt;/strong&gt; Ranking the best matches. Reorders candidates by relevance, availability, or business rules before presentation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;building_response:&lt;/strong&gt; Preparing the results. Assembles the final message, product cards, or clarification prompt the user will see.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The optional &lt;code&gt;detail&lt;/code&gt; field carries adapter-specific context (for example, a category name) without expanding the stage vocabulary. The &lt;code&gt;elapsedMs&lt;/code&gt; field is wall-clock time since the search started, useful for logging and for deciding when to show a "still working" fallback message.&lt;/p&gt;

&lt;p&gt;Not every query runs every stage. A first-turn category browse may skip &lt;code&gt;resolving_reference&lt;/code&gt;. A cache hit might flash through &lt;code&gt;retrieving&lt;/code&gt; in under 50ms. That is fine. The stages describe what is happening when it happens, not a mandatory sequence with equal duration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capability-Gated Transport
&lt;/h2&gt;

&lt;p&gt;Progress streaming is opt-in at three levels. Adapter config exposes &lt;code&gt;progressStreaming?: 'auto' | 'off'&lt;/code&gt;. The default in config-ui is &lt;code&gt;'off'&lt;/code&gt;. Tenants turn it on explicitly.&lt;/p&gt;

&lt;p&gt;When set to &lt;code&gt;'auto'&lt;/code&gt;, the HTTP search adapter checks four conditions before opening an SSE stream:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Config says &lt;code&gt;progressStreaming: 'auto'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The caller sets &lt;code&gt;supportsProgressStreaming: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The caller provides an &lt;code&gt;onProgress&lt;/code&gt; callback.&lt;/li&gt;
&lt;li&gt; The catalog backend's &lt;code&gt;/health&lt;/code&gt; endpoint advertises &lt;code&gt;capabilities.progressStreaming&lt;/code&gt; with &lt;code&gt;version: 1&lt;/code&gt;, &lt;code&gt;transport: 'sse'&lt;/code&gt;, and a &lt;code&gt;stages&lt;/code&gt; array.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any check fails, the adapter logs the transport selection and falls back to a standard POST that returns JSON when complete. No error, no broken widget, no second integration path for non-streaming clients.&lt;/p&gt;

&lt;p&gt;The capability probe is cached for 60 seconds per base URL so every catalog turn does not pay an extra health round-trip. The transport itself uses &lt;a href="https://html.spec.whatwg.org/multipage/server-sent-events.html" rel="noopener noreferrer"&gt;Server-Sent Events&lt;/a&gt; as defined in the WHATWG HTML specification: a long-lived HTTP response where the server pushes &lt;code&gt;event:&lt;/code&gt; and &lt;code&gt;data:&lt;/code&gt; frames. SSE fits progress updates because they are server-to-client, unidirectional, and small. The chat API already uses SSE for answer streaming on compatible clients; catalog progress rides the same pattern.&lt;/p&gt;

&lt;p&gt;Why degrade instead of requiring SSE everywhere? Embedded widgets run on third-party sites with varied network stacks, corporate proxies, and older mobile WebViews. Some cannot hold an SSE connection reliably. Forcing SSE would break those clients or require maintaining two widget builds. Capability gating lets streaming clients get stage updates and everyone else get the same final answer through JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deadline and Cancellation
&lt;/h2&gt;

&lt;p&gt;Streaming progress does not remove the need for timeouts. It makes timeouts legible.&lt;/p&gt;

&lt;p&gt;Adapter config includes &lt;code&gt;timeouts.absoluteMs&lt;/code&gt;: a wall-clock deadline for the entire catalog operation, progress stream included. Separate connect and read timeouts still apply per HTTP hop, but the absolute deadline caps total user-visible wait regardless of how many stage transitions occur.&lt;/p&gt;

&lt;p&gt;Cancellation flows through the standard &lt;a href="https://dom.spec.whatwg.org/#interface-abortcontroller" rel="noopener noreferrer"&gt;AbortController / AbortSignal&lt;/a&gt; interface. The widget creates an &lt;code&gt;AbortController&lt;/code&gt; per request, passes its signal to the chat API, and wires the typing indicator's cancel action to &lt;code&gt;abort()&lt;/code&gt;. When the signal fires, the adapter stops reading the SSE stream and throws a transport error with outcome &lt;code&gt;cancelled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last part matters. A stream can end five ways, and conflating them loses debuggability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CatalogStreamOutcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;closed_without_result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;malformed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;cancelled:&lt;/strong&gt; User or client aborted via AbortSignal.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;timeout:&lt;/strong&gt; Absolute or read deadline exceeded.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;error:&lt;/strong&gt; Network failure or non-2xx response mid-stream.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;malformed:&lt;/strong&gt; SSE frame parsed but stage name not in the allowed list.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;closed_without_result:&lt;/strong&gt; Stream ended cleanly but no search result arrived.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These outcomes are not user-facing copy. They are the classification layer for logs, metrics, and deciding whether to retry. A &lt;code&gt;cancelled&lt;/code&gt; outcome after the user closes the widget should not increment the same error counter as a &lt;code&gt;malformed&lt;/code&gt; frame from a misconfigured backend. Transport failures throw &lt;code&gt;CatalogSearchTransportError&lt;/code&gt; with the outcome attached so callers cannot accidentally treat a dead stream as an empty result set.&lt;/p&gt;

&lt;p&gt;HTTP semantics for long-lived responses are governed by &lt;a href="https://datatracker.ietf.org/doc/html/rfc9110" rel="noopener noreferrer"&gt;RFC 9110&lt;/a&gt;. The practical implication for us: the client must handle connection drops, the server must not assume the client read every event, and both sides need a defined terminal state. Explicit outcomes are that terminal state for catalog progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Widget Does With It
&lt;/h2&gt;

&lt;p&gt;The widget does not render a progress bar. It updates the typing indicator text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;onProgress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateTypingIndicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API maps each stage to a short sentence ("Searching the catalog…", "Ranking the best matches…") before the event reaches the widget. The widget only displays the string. It does not know about stage enums or elapsed milliseconds.&lt;/p&gt;

&lt;p&gt;That is deliberate. A progress bar implies measurable completion. Catalog retrieval does not have a stable denominator. Is retrieving 40% of the work? It depends on the query. A bar that jumps from 30% to 90% in one frame is worse than a label that says what is happening now. &lt;a href="https://www.nngroup.com/articles/progress-indicators/" rel="noopener noreferrer"&gt;Nielsen Norman Group's guidance on progress indicators&lt;/a&gt; distinguishes determinate bars (known duration) from indeterminate indicators (unknown duration). Catalog search is indeterminate. Stage names are the honest representation.&lt;/p&gt;

&lt;p&gt;The widget also keeps a fallback timer. If no progress event arrives within 17 seconds, the typing indicator switches to "This search is taking a little longer. I'm still working on it…" That covers backends that support streaming but emit sparse updates, and clients where capability gating fell back to JSON mid-flight.&lt;/p&gt;

&lt;p&gt;For customer-facing deployments, this sits alongside the automation patterns in &lt;a href="https://www.hoverbot.ai/blog/customer-service-automation-2026" rel="noopener noreferrer"&gt;customer service automation in 2026&lt;/a&gt;: automate the lookup, but keep the human-visible loop honest when the lookup takes time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Gave Up
&lt;/h2&gt;

&lt;p&gt;Progress streaming adds complexity across 20 files. The adapter now maintains two transport paths (SSE and JSON), a capability cache, SSE frame parsing, and outcome classification. Every new catalog backend must advertise progress capabilities in its health endpoint or streaming silently degrades. That is the intended behaviour, but it means backend teams have a contract to implement.&lt;/p&gt;

&lt;p&gt;Fast stages look silly. When validating finishes in 12ms, the user may see "Checking product information…" flash for a single frame. We considered suppressing stages below a minimum display time and rejected it. Artificial delays lie about system speed. A flash is honest; a forced 500ms pause is theater.&lt;/p&gt;

&lt;p&gt;Default is off. Tenants must enable &lt;code&gt;progressStreaming: 'auto'&lt;/code&gt; in catalog skill config. We did not ship it as the default because not every catalog backend supports SSE progress yet, and we would rather have tenants opt in once their backend is ready than have streaming fail open on every turn.&lt;/p&gt;

&lt;p&gt;Observability gets harder before it gets easier. Five outcome types means five buckets in dashboards instead of one "search failed" counter. The payoff is that on-call can distinguish user cancels from backend timeouts without reading stack traces.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Ships Next
&lt;/h2&gt;

&lt;p&gt;When PR #3 and PR #6 merge, catalog skills with &lt;code&gt;progressStreaming: 'auto'&lt;/code&gt;, a streaming-capable widget, and a backend that advertises SSE progress will show stage updates during retrieval. Everything else continues to work as a plain JSON search with a static typing indicator.&lt;/p&gt;

&lt;p&gt;The design does not make catalog search faster. It makes the wait interpretable. For a chat interface backed by a live product catalog, that is the difference between "broken" and "working on it."&lt;/p&gt;

&lt;p&gt;Want to see catalog-backed chat with progress streaming once it ships? &lt;a href="https://www.hoverbot.ai/request-demo" rel="noopener noreferrer"&gt;Request a demo&lt;/a&gt; and we will walk through the catalog skill configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.hoverbot.ai/request-demo" rel="noopener noreferrer"&gt;Request a demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Knowledge Management for AI Chatbots: Structure, Maintain, Improve</title>
      <dc:creator>Vitaly Goncharenko</dc:creator>
      <pubDate>Wed, 16 Sep 2026 15:13:03 +0000</pubDate>
      <link>https://dev.to/hoverbot/knowledge-management-for-ai-chatbots-structure-maintain-improve-j26</link>
      <guid>https://dev.to/hoverbot/knowledge-management-for-ai-chatbots-structure-maintain-improve-j26</guid>
      <description>&lt;p&gt;A retrieval-augmented chatbot can still give a bad answer when retrieval finds the wrong passage, an outdated passage, or nothing useful. Before changing the model, inspect the evidence it received. That separates a retrieval failure from a generation failure and points to a fix you can test.&lt;/p&gt;

&lt;p&gt;Knowledge management for AI chatbots is the discipline of organizing, maintaining, and improving the content your assistant retrieves from. This guide covers how to structure content for retrieval-augmented generation (RAG), how to keep it fresh, and how to use analytics to find and close gaps systematically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Knowledge Is the Real Bottleneck
&lt;/h2&gt;

&lt;p&gt;In a RAG system, every answer flows through the same pipeline: the user's question is used to retrieve relevant chunks of your content, and the model composes an answer grounded in those chunks. If retrieval surfaces the wrong chunk, an outdated chunk, or no chunk at all, the answer suffers no matter how capable the model is.&lt;/p&gt;

&lt;p&gt;Knowledge structure is one part of that pipeline teams can change directly. You can restructure source content, change chunking and metadata, then test whether the intended passages appear for representative questions. For the broader architecture context, see &lt;a href="https://www.hoverbot.ai/blog/multilingual-rag-practical-architecture" rel="noopener noreferrer"&gt;multilingual RAG architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure Content for Retrieval, Not Just Reading
&lt;/h2&gt;

&lt;p&gt;Content written for humans browsing a help center is often poorly suited for retrieval. A few principles make a large difference:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - **One topic per section.** Self-contained sections retrieve cleanly; sprawling articles that cover five topics retrieve ambiguously.

  - **Front-load the answer.** State the answer near the top of each section so a retrieved chunk carries the substance.

  - **Use explicit headings.** Headings that mirror how customers phrase questions improve matching.

  - **Avoid pronoun chains across sections.** A chunk should make sense on its own, without the paragraph before it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Chunking Strategy: The Quiet Lever
&lt;/h2&gt;

&lt;p&gt;Chunking decides what unit of content gets embedded and retrieved. Chunks that are too large dilute relevance and bury the answer; chunks that are too small lose the context needed to answer well. The sweet spot is usually a coherent section: large enough to stand alone, small enough to be specific.&lt;/p&gt;

&lt;p&gt;Prefer structure-aware chunking that respects headings and natural boundaries over naive fixed-length splitting. Overlapping a little context between adjacent chunks helps preserve meaning at the edges. Then score retrieval at the chunk level so you can see which chunks actually answer questions and which never get used.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  **Practical tip:** If a single article answers many different questions, test focused sections or entries against the original. The useful unit is the one that retrieves the complete answer for your evaluation questions without bringing unrelated material with it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Metadata Tagging for Precision and Freshness
&lt;/h2&gt;

&lt;p&gt;Metadata turns a flat pile of content into something you can filter and govern. Tag chunks with attributes like product area, audience, language, region, and last-reviewed date. This enables more precise retrieval, lets you scope answers to the right context, and makes freshness auditable.&lt;/p&gt;

&lt;p&gt;A last-reviewed date in particular is the backbone of maintenance: it tells you and the system which content is aging and may need a human check before it keeps answering customers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Maintain Freshness Without a Full-Time Librarian
&lt;/h2&gt;

&lt;p&gt;Knowledge decays. Policies change, products ship, and yesterday's correct answer becomes today's complaint. The fix is a lightweight recurring process rather than a heroic annual cleanup:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - Flag content past its review date for a quick human check

  - Tie knowledge updates to product and policy release cycles

  - Retire or merge chunks that never get retrieved

  - Promote answers that resolve well into canonical, well-structured entries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Use Analytics to Find Gaps Systematically
&lt;/h2&gt;

&lt;p&gt;An unanswered or low-confidence question is a useful diagnostic signal. Cluster those conversations, inspect the retrieved passages, and separate missing content from weak retrieval or an answer-generation problem. When the source material is missing, write a focused entry and add the original question to the retrieval test set. We covered the loop in depth in &lt;a href="https://www.hoverbot.ai/blog/close-the-loop-analytics-that-teach-your-chatbot-to-fix-itself" rel="noopener noreferrer"&gt;close the loop&lt;/a&gt;, and the analytics surface is described in the &lt;a href="https://www.hoverbot.ai/resources/feature-deep-dives/analytics-close-loop-optimization" rel="noopener noreferrer"&gt;analytics deep dive&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Weekly Maintenance Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Review misses.&lt;/strong&gt; Look at clustered unresolved and low-confidence conversations from the week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Triage.&lt;/strong&gt; Decide which gaps are worth fixing now based on volume and impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Write or restructure.&lt;/strong&gt; Add or reshape content as self-contained, well-headed chunks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Tag.&lt;/strong&gt; Apply metadata and a fresh review date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Verify.&lt;/strong&gt; Confirm the new content actually gets retrieved for the target questions.&lt;/p&gt;

&lt;p&gt;Keep the review small enough to repeat. The important result is not time spent or documents edited. It is whether the changed content is retrieved for the target questions and supports a correct answer in the same evaluation set.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where HoverBot Fits
&lt;/h2&gt;

&lt;p&gt;HoverBot ingests and chunks source content for retrieval, attributes answers to sources, and surfaces unresolved conversations for review. That gives a team evidence to inspect when an answer fails instead of treating the model as a black box. The knowledge-base tooling is detailed in the &lt;a href="https://www.hoverbot.ai/resources/feature-deep-dives/knowledge-base-management" rel="noopener noreferrer"&gt;knowledge base management deep dive&lt;/a&gt;, with the wider system in the &lt;a href="https://www.hoverbot.ai/resources/technical-overview" rel="noopener noreferrer"&gt;technical overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Want to inspect how grounded retrieval behaves on your own content? &lt;a href="https://www.hoverbot.ai/request-demo" rel="noopener noreferrer"&gt;Request a demo&lt;/a&gt; and test HoverBot against questions from your knowledge base.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [Request a demo](https://www.hoverbot.ai/request-demo)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Bot's Job Starts Where a Good Email Ends</title>
      <dc:creator>Vitaly Goncharenko</dc:creator>
      <pubDate>Tue, 15 Sep 2026 14:55:40 +0000</pubDate>
      <link>https://dev.to/hoverbot/the-bots-job-starts-where-a-good-email-ends-2mm6</link>
      <guid>https://dev.to/hoverbot/the-bots-job-starts-where-a-good-email-ends-2mm6</guid>
      <description>&lt;p&gt;The hardest ecommerce chatbot decision is not which model to use. It is deciding which questions the bot is allowed to answer.&lt;br&gt;
Start with the questions customers already ask: order status, delivery windows, stock, returns, product fit, and exceptions. Some have a current answer in a system your team trusts. Some can be prevented with a clearer email or product page. Some need a person. Treating all three groups as one automation project creates avoidable risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map the answerable surface
&lt;/h2&gt;

&lt;p&gt;Call that the &lt;strong&gt;answerable surface&lt;/strong&gt;: the questions for which a correct answer already exists in a source the chatbot can reliably reach. That might include order state, delivery window, stock, or policy. Outside that surface the bot is guessing. A fluent guess is often worse than no answer because the customer cannot see the uncertainty until it costs them time or money.&lt;br&gt;
Before automating a question, write down the source that should answer it and the condition that makes the answer current. A policy page may be enough for a general return window. It is not enough for a specific order state. If the source is missing, stale, or unreachable, the correct behavior is a visible boundary and a useful next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the cheaper-surface test
&lt;/h2&gt;

&lt;p&gt;A question belongs in chat only if it is answerable &lt;strong&gt;and&lt;/strong&gt; a cheaper surface does not solve it first. Put a tracking link in the shipment notice. Explain the return window on the product page. Send a plain delay update when the expected date changes. Chat should handle the remaining questions that still benefit from a conversation.&lt;br&gt;
The same rule explains two common failures. A bot pointed only at generic FAQ text guesses when a question needs current operational data. A bot that tries to pass as human hides the boundary instead of managing it. One is an integration problem; the other is an honesty problem.&lt;br&gt;
A demo cannot prove that production sources are connected, current, or complete. It can help you inspect the interaction: whether the answer is specific, whether the boundary is visible, and whether the next step makes sense. Verify production claims separately against the intended data and workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-step scoping exercise
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;List the ten questions your team receives most often.&lt;/li&gt;
&lt;li&gt;Remove the questions a better email, product page, or status notice can prevent.&lt;/li&gt;
&lt;li&gt;For each remaining question, identify the exact source that holds the correct answer.&lt;/li&gt;
&lt;li&gt;Automate only the questions whose source is reliable and reachable.&lt;/li&gt;
&lt;li&gt;Define a visible next step for everything outside that boundary.
## Run a three-prompt review
For each workflow, prepare three prompts before launch:&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answerable:&lt;/strong&gt; the approved source contains one specific expected answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing:&lt;/strong&gt; the source does not contain the requested detail, so the bot should say so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human:&lt;/strong&gt; the request needs judgment or action, so the bot should route it clearly.
Record the expected answer, source, boundary, and next step before testing. A response that merely sounds fluent does not pass.
Where HoverBot fits
For a design-partner pilot, bring one bounded support or lead-capture workflow. The useful starting point is an accessible source, one expected answer, one boundary case, and a human next step. Applying starts a fit review; it does not create an account or guarantee an invitation.
Fix preventable questions first. Then map what remains. That is the chatbot's actual job.
&lt;a href="https://demo4.hoverbot.ai/?utm_source=hoverbot_blog&amp;amp;utm_medium=owned_content&amp;amp;utm_campaign=answerable_surface&amp;amp;utm_content=inline_demo" rel="noopener noreferrer"&gt;Explore the catalogue demonstration&lt;/a&gt;
&lt;a href="https://www.hoverbot.ai/request-demo" rel="noopener noreferrer"&gt;Apply for a design-partner pilot&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>rag</category>
      <category>chatbots</category>
    </item>
    <item>
      <title>Four chatbot widget patterns for websites and apps: from bubble to super app</title>
      <dc:creator>Vitaly Goncharenko</dc:creator>
      <pubDate>Mon, 14 Sep 2026 12:14:26 +0000</pubDate>
      <link>https://dev.to/hoverbot/four-chatbot-widget-patterns-for-websites-and-apps-from-bubble-to-super-app-4dde</link>
      <guid>https://dev.to/hoverbot/four-chatbot-widget-patterns-for-websites-and-apps-from-bubble-to-super-app-4dde</guid>
      <description>&lt;p&gt;Websites tend to embed chat in four patterns: a simple bubble, an inbox with history, a task-driven support bot, and a multi-tab hub.&lt;/p&gt;

&lt;p&gt;This guide explains where each pattern fits, what it does well, and the least you need to configure to run it reliably.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Acronyms: personally identifiable information (PII); service-level agreement (SLA).)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00o765a20cglq07gwt5c.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00o765a20cglq07gwt5c.jpeg" alt="Simple chat bubble widget showing a support conversation interface" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Simple chat bubble widget interface&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Simple chat bubble
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is:
&lt;/h3&gt;

&lt;p&gt;The simple chat bubble is a lightweight, single-thread assistant that opens when you click, answers your question, and then closes. It has no inbox or account linking and only limited memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Marketing pages and documentation landers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lead capture ("ask a question → leave email")&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Narrow, high-confidence FAQs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strengths:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Fast to ship; minimal UI surface&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Low maintenance and risk&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clear focus on the current question&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trade-offs:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No persistent history by default&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limited multi-step tasks without tools/actions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Harder to measure long-term outcomes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Minimum configuration:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Greeting and scope:&lt;/strong&gt; short, explicit welcome&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Knowledge base:&lt;/strong&gt; 5–20 curated docs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guardrails:&lt;/strong&gt; refusal policy, safe fallbacks, and topic blocks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intent capture:&lt;/strong&gt; 3–5 quick-reply buttons&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lead handoff:&lt;/strong&gt; email form on low confidence or on request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Analytics:&lt;/strong&gt; impressions, opens, first response time, resolved vs escalated&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02rig7q4v55ll05fvp74.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02rig7q4v55ll05fvp74.jpeg" alt="Messages list widget showing conversation history with multiple threads" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: Messages list with history widget interface&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Messages list with history
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is:
&lt;/h3&gt;

&lt;p&gt;An inbox-style widget. Users can open past threads, resume conversations, and see bot or agent follow-ups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SaaS apps and customer portals&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Education and healthcare portals where continuity matters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sales cycles that run over days or weeks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strengths:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Conversation memory within and across sessions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Asynchronous support ("reply when ready")&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clear UX for escalations and status updates&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trade-offs:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Identity and storage decisions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More compliance exposure (PII, retention, export)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Operational overhead: SLAs, routing, backlog management&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2i4iclt3fg4dqeaxk11d.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2i4iclt3fg4dqeaxk11d.jpeg" alt="Product-support chatbot widget showing task-oriented interface" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: Product-support chatbot widget interface&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Product-support chatbot
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is:
&lt;/h3&gt;

&lt;p&gt;A support-focused widget built for tasks: look up orders, reset passwords, file tickets, schedule, check refund eligibility. Think "chat + tools."&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Measurable deflection and faster resolution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clear ROI when tools are reliable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Structured analytics (top tasks, failure points)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trade-offs:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Integration work (CRM, ticketing, order and billing APIs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Precision demands: weak tools create loops and churn&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ongoing maintenance of intents, prompts, and policies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jl340r5p8fjt764b7hh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jl340r5p8fjt764b7hh.jpeg" alt="Multi-tab widget showing super-app interface" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: Multi-tab widget (super-app) interface&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Multi-tab widget (the "super-app")
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it is:
&lt;/h3&gt;

&lt;p&gt;A docked panel that bundles modules such as Home, Messages, Help, and News/Announcements; sometimes Tasks or Shortcuts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Multi-feature products and communities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enterprise portals and intranets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sites that need to surface announcements, docs, chat, and actions in one place&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Picking the right pattern
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Choose&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Marketing site; FAQs and leads&lt;/td&gt;
&lt;td&gt;Simple bubble&lt;/td&gt;
&lt;td&gt;Fast, low risk, clear CTA to sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logged-in product; conversations span days&lt;/td&gt;
&lt;td&gt;Messages with history&lt;/td&gt;
&lt;td&gt;Continuity and async service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurable deflection on top 10 tasks&lt;/td&gt;
&lt;td&gt;Product-support chatbot&lt;/td&gt;
&lt;td&gt;Tooling and policies drive ROI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple help surfaces: chat, docs, news&lt;/td&gt;
&lt;td&gt;Multi-tab widget&lt;/td&gt;
&lt;td&gt;One hub; consistent entry point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heavily regulated flows (health/finance)&lt;/td&gt;
&lt;td&gt;Messages with history or Product-support&lt;/td&gt;
&lt;td&gt;Auditability, retention, policy enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; Start with the simplest widget that delivers the outcome. Move up a level only when continuity, task depth, or surface area require it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Pick the smallest widget that solves the user's job today. Add depth, such as history, tools, or multiple tabs, only when your product and users clearly need it. The gains come from clear scope, reliable tools, and disciplined measurement, not from the fanciest UI.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>ux</category>
    </item>
    <item>
      <title>Multilingual RAG Architecture That Works in Production</title>
      <dc:creator>Vitaly Goncharenko</dc:creator>
      <pubDate>Sun, 06 Sep 2026 15:49:12 +0000</pubDate>
      <link>https://dev.to/hoverbot/multilingual-rag-architecture-that-works-in-production-1pl9</link>
      <guid>https://dev.to/hoverbot/multilingual-rag-architecture-that-works-in-production-1pl9</guid>
      <description>&lt;p&gt;A battle-tested architecture for multilingual RAG: translate at the edges, reason in one base language, and protect entities throughout. This is not theory. We run this in production across 14 languages with 50M+ queries processed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why most multilingual RAG systems fail
&lt;/h2&gt;

&lt;p&gt;Teams building multilingual RAG make the same mistakes repeatedly. They run separate vector indices per language and wonder why retrieval quality varies wildly. They trust multilingual embedding models to handle languages they have never tested. They let translation services silently mangle product names and order IDs.&lt;/p&gt;

&lt;p&gt;The failure modes are predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Embedding drift:&lt;/strong&gt; Multilingual models cluster similar concepts in different regions of the vector space depending on language. A query in Japanese may not retrieve the same documents as its English equivalent. We measured 34% retrieval disagreement between EN and JA queries for the same underlying content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation corruption:&lt;/strong&gt; "Order #SKU-2847" becomes something else entirely, or gets interpreted as natural language and garbled. In one client deployment, 12% of SKU references were corrupted before we implemented entity protection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking failures:&lt;/strong&gt; Sentence splitters designed for English break CJK text mid-phrase, destroying semantic coherence. A chunk that ends mid-sentence retrieves poorly and generates worse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groundedness collapse:&lt;/strong&gt; When retrieval is weak, models hallucinate. When answers are translated back, hallucinations get laundered into plausible-sounding text. Users cannot tell the difference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our position:&lt;/strong&gt; The only reliable architecture keeps retrieval and reasoning in one base language. Translate at the edges. Protect entities throughout. Everything else is hope dressed as engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in one diagram
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5hnx375a80qn43ixc31i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5hnx375a80qn43ixc31i.png" alt="Multilingual RAG architecture showing translation at edges with base language retrieval and reasoning in the middle" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Translate at edges, reason in base language, protect entities throughout&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The flow has five stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingest:&lt;/strong&gt; Content arrives in any language. Detect, normalize, protect entities, translate to base language, chunk, embed, index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query translation:&lt;/strong&gt; User query arrives. Detect language, protect entities, translate to base language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval:&lt;/strong&gt; Search the base-language index. Rerank with cross-encoder. Keep top-k.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation:&lt;/strong&gt; Generate answer in base language with citations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back translation:&lt;/strong&gt; Translate answer to user language, restore protected entities, localize formats.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why one base language beats the alternative
&lt;/h2&gt;

&lt;p&gt;The intuitive approach is to build per-language indices. Query in Japanese, search Japanese index, generate in Japanese. This sounds elegant until you operate it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Per-Language Indices&lt;/th&gt;
&lt;th&gt;Single Base Language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Index count&lt;/td&gt;
&lt;td&gt;N indices (one per language)&lt;/td&gt;
&lt;td&gt;1 index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding model tuning&lt;/td&gt;
&lt;td&gt;Tune N models or accept variance&lt;/td&gt;
&lt;td&gt;Tune 1 model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache efficiency&lt;/td&gt;
&lt;td&gt;Fragmented across languages&lt;/td&gt;
&lt;td&gt;Unified cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content gaps&lt;/td&gt;
&lt;td&gt;Some languages have less content&lt;/td&gt;
&lt;td&gt;All content available to all users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;Check N code paths&lt;/td&gt;
&lt;td&gt;Check 1 code path + translation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality consistency&lt;/td&gt;
&lt;td&gt;Varies by language&lt;/td&gt;
&lt;td&gt;Consistent (translation quality permitting)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The single base language approach adds translation latency (typically 50-150ms per direction). But it gives you one vector store to tune, one reranking stack to secure, and one grounded generation policy to validate. When something breaks at 3am, you want one path to debug, not fourteen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedding model selection: the data you need
&lt;/h2&gt;

&lt;p&gt;Multilingual embedding models vary dramatically in quality across languages. "Supports 100+ languages" means nothing without benchmarks on your actual languages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;EN Recall@10&lt;/th&gt;
&lt;th&gt;JA Recall@10&lt;/th&gt;
&lt;th&gt;ZH Recall@10&lt;/th&gt;
&lt;th&gt;Cross-lingual Agreement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;text-embedding-3-large&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;81%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;multilingual-e5-large&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bge-m3&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;86%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single-language + translation&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;93%*&lt;/td&gt;
&lt;td&gt;92%*&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Via translation to English before embedding. Benchmarks from our internal eval set (customer support domain, 10K queries).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; Translation to a base language before embedding often outperforms native multilingual embeddings, especially for less common languages. The translation step adds latency but improves cross-language consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entity protection: the difference between working and broken
&lt;/h2&gt;

&lt;p&gt;Translation services will mangle anything that looks like natural language. Protect entities before any external call:&lt;/p&gt;

&lt;h3&gt;
  
  
  Entity types to protect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SKUs and order IDs:&lt;/strong&gt; Pattern-based detection for alphanumeric codes (e.g., /[A-Z]{2,4}-\d{4,}/)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand names:&lt;/strong&gt; Glossary-based exact match with case-insensitive variants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code blocks:&lt;/strong&gt; Preserve exactly as written, including whitespace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URLs and emails:&lt;/strong&gt; Standard pattern matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product model numbers:&lt;/strong&gt; Often alphanumeric, easily corrupted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measurement values:&lt;/strong&gt; "5.2mm" can become "5.2 millimeters" or worse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Replace detected entities with placeholders like [[ENTITY_0]], [[BRAND_1]], etc. Store the mapping. After translation, restore the originals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before translation
Input: "Where can I find the SKU-4829 brake kit for BMW M3?"
Protected: "Where can I find the [[SKU_0]] brake kit for [[BRAND_0]] [[MODEL_0]]?"
Map: { SKU_0: "SKU-4829", BRAND_0: "BMW", MODEL_0: "M3" }

// After translation (Japanese)
Translated: "[[BRAND_0]] [[MODEL_0]]の[[SKU_0]]ブレーキキットはどこで入手できますか？"

// After restoration
Final: "BMW M3のSKU-4829ブレーキキットはどこで入手できますか？"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Glossary management:&lt;/strong&gt; Version your glossaries. When "refund policy" gets translated inconsistently across documents, you lose term consistency in your knowledge base. Pin glossary versions at ingest time and log which version was used. When you update the glossary, re-translate affected content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Script-aware chunking: where most implementations break
&lt;/h2&gt;

&lt;p&gt;Chunking is where multilingual RAG systems quietly fail. Standard sentence splitters assume whitespace-delimited words. CJK scripts do not work that way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Script Type&lt;/th&gt;
&lt;th&gt;Languages&lt;/th&gt;
&lt;th&gt;Chunking Approach&lt;/th&gt;
&lt;th&gt;Libraries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latin/Cyrillic&lt;/td&gt;
&lt;td&gt;EN, ES, FR, DE, RU&lt;/td&gt;
&lt;td&gt;Sentence splitting on punctuation&lt;/td&gt;
&lt;td&gt;spaCy, NLTK, standard splitters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJK (Chinese)&lt;/td&gt;
&lt;td&gt;ZH&lt;/td&gt;
&lt;td&gt;Character-based with jieba segmentation&lt;/td&gt;
&lt;td&gt;jieba, pkuseg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJK (Japanese)&lt;/td&gt;
&lt;td&gt;JA&lt;/td&gt;
&lt;td&gt;Morphological analysis&lt;/td&gt;
&lt;td&gt;MeCab, SudachiPy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJK (Korean)&lt;/td&gt;
&lt;td&gt;KO&lt;/td&gt;
&lt;td&gt;Morphological analysis&lt;/td&gt;
&lt;td&gt;KoNLPy, Mecab-ko&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thai&lt;/td&gt;
&lt;td&gt;TH&lt;/td&gt;
&lt;td&gt;No spaces between words; requires segmentation&lt;/td&gt;
&lt;td&gt;PyThaiNLP, ICU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arabic/Hebrew&lt;/td&gt;
&lt;td&gt;AR, HE&lt;/td&gt;
&lt;td&gt;RTL-aware sentence splitting&lt;/td&gt;
&lt;td&gt;CAMeL Tools, spaCy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Structure preservation:&lt;/strong&gt; Keep tables and lists intact. A table row split from its header is useless. Carry breadcrumbs (Title › Section › Subsection) into each chunk for context. This matters especially for technical documentation where hierarchy provides meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval with confidence-based widening
&lt;/h2&gt;

&lt;p&gt;When translation confidence is low, widen retrieval to compensate for potential query drift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High confidence (&amp;gt;0.85):&lt;/strong&gt; Standard retrieval with base k (typically k=5). Trust the translation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium confidence (0.70-0.85):&lt;/strong&gt; Double the retrieval candidates (k=10), then rerank to original k. Compensates for translation uncertainty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low confidence (&amp;lt;0.70):&lt;/strong&gt; Also search the original query text before reranking everything together. Useful for queries with many entities or domain-specific terms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A cross-encoder reranker usually pays for itself. It allows you to retrieve more candidates cheaply with bi-encoder similarity, then use the more expensive cross-encoder to select the best passages. Result: fewer and better passages in the final context, which reduces tokens, noise, and hallucination risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grounded generation: keeping the model honest
&lt;/h2&gt;

&lt;p&gt;Keep the generator on a short leash. It should see only the reranked passages and rules that enforce citation-first behavior.&lt;/p&gt;

&lt;p&gt;System prompt guidance that works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are answering questions based on the provided context passages.
Rules:
1. Only use information from the context passages
2. Cite passage numbers for every factual claim: [1], [2], etc.
3. If the context does not contain the answer, say so explicitly
4. Do not invent information, product names, or specifications
5. Preserve all bracketed tokens exactly as written
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Produce a base language draft with citations. Translate that draft back to the user language, restore protected entities, and localize numbers, currencies, and dates. English in the middle keeps reasoning stable as models evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation metrics that actually matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Retrieval metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recall@k by language:&lt;/strong&gt; Does retrieval work equally well across all supported languages? Target: within 5% of English baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language retrieval agreement:&lt;/strong&gt; Does the same question in different languages retrieve the same passages? Target: &amp;gt;85% agreement on top-3 passages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity preservation rate:&lt;/strong&gt; What percentage of protected entities survive the round-trip? Target: 100%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Generation metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Groundedness score:&lt;/strong&gt; Can every claim be traced to a source passage? Automated checks catch 80% of issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language answer agreement:&lt;/strong&gt; Do answers to equivalent questions agree factually across languages? Sample and human-review weekly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glossary consistency:&lt;/strong&gt; Are key terms translated consistently? Spot-check high-frequency terms monthly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operational metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency by language:&lt;/strong&gt; Translation adds 50-150ms per direction. Track p50/p95 per language to catch regressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token cost by language:&lt;/strong&gt; CJK languages often tokenize inefficiently (2-3x more tokens for same content). Monitor cost per query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human handoff rate by language:&lt;/strong&gt; Are certain languages causing more escalations? May indicate retrieval or translation quality issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common pitfalls and how to avoid them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Running multiple indices per language.&lt;/strong&gt; Multiplies complexity without improving quality. One base language index is easier to tune, cache, and debug. We tried per-language indices early on and reverted within 3 months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting multilingual embeddings blindly.&lt;/strong&gt; Test retrieval quality per language before going live. Embedding models have uneven performance across languages. Build a parallel eval set with queries in each supported language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letting translation corrupt entities.&lt;/strong&gt; Always protect SKUs, order IDs, brand names, and code before any translation call. This is non-negotiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking CJK text with English tools.&lt;/strong&gt; Standard sentence splitters break on whitespace. CJK needs specialized segmentation. Use the right library for each script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping back-translation quality checks.&lt;/strong&gt; The answer looks right in English does not mean it looks right in Arabic. Verify entity preservation and format localization. Sample and review regularly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring low-confidence cases.&lt;/strong&gt; When translation confidence is low, widen retrieval. Consider asking for clarification instead of guessing. A "could you rephrase?" is better than a wrong answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;☐ Choose base language (usually English for tooling maturity)&lt;/li&gt;
&lt;li&gt;☐ Build glossary with do-not-translate terms and term mappings&lt;/li&gt;
&lt;li&gt;☐ Implement entity protection before any translation call&lt;/li&gt;
&lt;li&gt;☐ Deploy script-aware chunking for CJK and Thai&lt;/li&gt;
&lt;li&gt;☐ Set up translation confidence thresholds and fallback widening&lt;/li&gt;
&lt;li&gt;☐ Add cross-encoder reranking to reduce context size&lt;/li&gt;
&lt;li&gt;☐ Implement groundedness verification in generation&lt;/li&gt;
&lt;li&gt;☐ Build cross-language evaluation suite with parallel queries&lt;/li&gt;
&lt;li&gt;☐ Log all translation decisions for audit and debugging&lt;/li&gt;
&lt;li&gt;☐ Monitor latency and cost per language&lt;/li&gt;
&lt;li&gt;☐ Set up alerts for cross-language retrieval disagreement spikes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The opinionated take
&lt;/h2&gt;

&lt;p&gt;Most teams over-engineer multilingual RAG. They build complex language-detection cascades, deploy multiple indices, and try to tune embeddings per language. This creates operational nightmares and fragile systems.&lt;/p&gt;

&lt;p&gt;The simpler architecture works better: one index, one base language, translation at the edges. Yes, you add translation latency. But you get a single system to tune, test, and debug.&lt;/p&gt;

&lt;p&gt;Three principles we have learned operating this at scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Translation quality beats embedding quality for cross-language consistency.&lt;/strong&gt; A good translation service plus a monolingual English embedding model often outperforms a mediocre multilingual embedding model. Test both approaches on your actual data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity protection is not optional.&lt;/strong&gt; Every time we have seen a multilingual RAG system fail in production, entity corruption was in the top three causes. Protect entities before translation, restore after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the eval suite first.&lt;/strong&gt; You cannot improve what you do not measure. Create parallel queries in all supported languages before you launch. Run cross-language agreement checks weekly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sophistication belongs in entity protection, glossary management, and evaluation. Not in retrieval architecture. Get those right, and the rest follows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>machinelearning</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
