# Rebuilding the Cerebras Knowledge Base: the web UI and a look back

> Source: <https://dev.to/faridgnank02/rebuilding-the-cerebras-knowledge-base-the-web-ui-and-a-look-back-4k2m>
> Published: 2026-08-16 12:20:45+00:00

[Post 6](//post-6-mcp.md) handed the retrieval tools to agents over MCP. This last post builds the other front end (a plain web page) for the humans who don't have an MCP client sitting in their editor. And since it is the end of the series, it is also where I add up the scoreboard.

The UI is deliberately small: one FastAPI app, two JSON endpoints, and a single static HTML file with no build step. The same no-frameworks rule that governed retrieval (no LangChain, no vector-store SDK) governs the front end too (no React, no bundler, no npm).

The whole back end is a thin wrapper over the pipeline built in posts 1–5:

``` php
def create_app(search_fn, ask_fn) -> FastAPI:
    @app.post("/api/search")   # LLM-free retrieval  (posts 1–4)
    @app.post("/api/ask")      # full pipeline       (post 5)
```

`create_app`

takes the two functions as arguments rather than building them: the same seam the tests use to inject fakes, and the same seam `main()`

uses to wire in the real `hybrid_search`

and `run_ask`

. The endpoints mirror the two things the series built:

`/api/search`

`/api/ask`

The front-end toggle makes that split literal:

```
<label><input type="radio" name="mode" value="search" checked> search (LLM-free)</label>
<label><input type="radio" name="mode" value="ask"> ask (full pipeline)</label>
```

You can see the architecture from the home page: one radio button for retrieval, one for retrieval plus reasoning.

Real responses from the running app against the live corpus. `search`

mode is instant and free:

```
// POST /api/search {"query": "how do I add middleware", "limit": 3}
{"results": [
  {"source_id": "issue_3027", "score": 0.0164,
   "snippet": "# How to add a header field to the request in a middleware?",
   "url": "https://github.com/fastapi/fastapi/issues/3027"},
  {"source_id": "issue_10180", "score": 0.0162,
   "snippet": "# Mounting sub-applications under `APIRouter`", "url": "…/10180"}
]}
```

`ask`

mode spends the LLM calls and hands back a written, cited answer:

```
// POST /api/ask {"question": "How do I add a custom middleware in FastAPI?"}
{
  "tools": ["search"],
  "answer": "You can add custom middleware in FastAPI in several ways.\n\n### 1. HTTP
             middleware with `@app.middleware(\"http\")` ... async def
             add_process_time_header(request, call_next): ...",
  "evidence": [{"n": 1, "source_id": "issue_5071",
                "url": "https://github.com/fastapi/fastapi/issues/5071",
                "snippet": "# Update Middleware Documentation ..."}]
}
```

The page renders the answer in one block and the evidence as a citation list under it: the `[n]`

markers in the prose line up with the numbered sources, so every claim is one click from the thread it came from.

`index.html`

is ~125 lines: inline CSS with a `prefers-color-scheme`

dark mode, a form, three result containers, and ~50 lines of vanilla `fetch`

that POST to the two endpoints and render the JSON. No framework, no state library, no build. It is served as a string straight from the package:

```
index_html = (resources.files("knowbase") / "static" / "index.html").read_text()
```

That is the entire deployment story: `kb-web`

starts uvicorn on `127.0.0.1:8000`

, and the one file it needs travels inside the package. For a knowledge base that a handful of people query, a single static page is not a compromise; it is the right size.

Seven posts, one rule: every post had to fix a failure the previous one demonstrated. Here is the whole arc on the fixed 31-question eval (MRR, the metric that tracked the story best):

| Post | Change | MRR† | What it taught |
|---|---|---|---|
| 1 | naive vector | 0.77* | the baseline is stronger than you expect |
| 2 | + hybrid (RRF) | 0.67 | fusion is confidence-blind; it regressed vs vector (0.77) |
| 3 | + distill & burst | 0.57 | corpus rebuild hurt precision (vector fell to 0.63 too) but lifted recall@10 to 0.94 |
| 4 | + rerank |
0.90 |
the reranker converts recall into precision (the win) |
| 5 | planner + synthesis | n/a | retrieval becomes cited answers; "misses" get answered |
| 6 | MCP server | n/a | the tools become an agent's, not just ours |
| 7 | web UI | n/a | …and a human's |

†The MRR column traces the hybrid pipeline (posts 2–4); post 1 is its vector-only predecessor, the thing hybrid replaced. *Posts 1–2 measured on the ~3,700-doc pre-distillation corpus; 3–4 on the 16,315-doc distilled/burst corpus. Same 31 questions throughout.

The shape of that table is the honest lesson of the series. The naive baseline was good. The two changes that looked like obvious wins (hybrid search, LLM distillation) each lost in isolation, and stayed in only because they were scaffolding: hybrid supplied a recall pool and distillation/bursting raised its ceiling to 0.94, and then one LLM reranker turned that pool into MRR 0.90. If I had shipped hybrid on faith in post 2 and never measured, I would have quietly made the system worse and called it progress.

The series ends honest about its edges:

`search_code`

is only as good as the pattern it is given`def X`

lands the definition. It should rank indexed code above docs.`jsonable_encoder`

and background-tasks code-location lookups), even though `ask`

answers both from issue evidence. Closing that gap means better code retrieval, not a better ranker.The system that started as one embeddings table and a cosine query ended as a planner routing across three retrievers, an LLM reranker, grounded synthesis with citations, an MCP server, and a web page; and the single most valuable component was the one I almost didn't measure carefully enough to keep. Build naive first, make the eval honest before you make the system clever, and let every addition prove it earned its place. That is the whole method; the FastAPI knowledge base was just where I ran it.

*Thanks for reading all seven. The retrieval numbers live in the Results Appendix; the web front end is knowbase.web plus one static index.html. *
