CareerChive

How was the docs chatbot implemented?

Docs Chat Build

Docs Chat Build

How was the docs chatbot implemented?

Docs Indexing

  • Indexed rows are stored without a public flag, and /docs/chat retrieves from those rows as written.
  • Sync reads a Git ref. User guide search and /llms.txt read the working tree when those routes are built.

src/lib/docs/compose.ts builds the text that later goes into docs_section. npm run sync-docs calls it as composeDocsCorpus({ forIndex: true, gitRef: branch }):

  • Reads the docs MDX files from a Git ref (BRANCH, default main).
  • Shared _snippets files are pasted into the page that imports them, so the index stores the full wording, not an import line.
  • A page with search: false is skipped. forIndex keeps the same pages as user guide search.
  • A page becomes one overview chunk plus one chunk per heading that has body text (the heading's href may include #anchor).

Chatbot Interface

Clicking the ask the documentation button (DocsChat) opens the DocsChatPanel. /docs/chat/route.ts:

  • Restores message history if the browser has a docs_chat_id cookie. (GET)
  • Looks up matching docs in docs_section, then asks Gemini to write a summary from those excerpts only on Send. (POST)
  • Clears the message history by dropping the current docs_chat_id cookie and the matching Supabase session when Clear Chat is clicked. (DELETE)
  • Gemini (gemini-embedding-001, 768 dimensions, RETRIEVAL_QUERY) turns the question into a vector so pages can match by meaning, not only by the same words. If embed quota or high demand is hit, keyword search still runs.
  • The same wording reuses docs_query_embedding (scripts/docs-chat.sql). The row ID is a hash of the question, so the question text is not stored. The server instance also keeps vectors it has already loaded, and a trigger keeps the newest 200 rows. Row-level security is on with no anon / authenticated policies. /docs/chat reads and writes those rows with the service_role client.
  • Postgres then runs two lookups — wording match (search_docs_sections, ranked on search_vector) and meaning match (match_docs_sections, cosine distance). Each lookup keeps up to 20 rows. Those ranked lists are combined (RRF) in /docs/chat so a page that does well on either can still surface.
  • At most three pages are kept. A page is dropped when its combined score is under 65% of the best hit. If wording search returned any pages, a page that matched only by meaning is dropped unless its score is at least 85% of the best hit.
  • Short words such as api and add are kept for matching (three characters or more).
  • If a heading on that page matches the question, that heading's excerpt is used instead of the page overview.

Chat History

  • docs_chat_session, docs_chat_message, and docs_chat_limits in Supabase, created by scripts/docs-chat.sql, store chat history per browser and chat IP quotas. That file also creates docs_section and docs_query_embedding.
  • The browser never talks to these tables — row-level security is on with no anon / authenticated policies. /docs/chat reads and writes them with the service_role client. Quotas go through consume_docs_chat_quota on that same client.
  • docs_chat_session is one conversation — cookie docs_chat_id (httpOnly, path /, five years) stores that row's ID so opening ask the documentation again restores the same thread.
  • Send upserts that session (a leftover cookie still captures) and writes the question immediately, then the reply after Gemini finishes.
  • Clear Chat deletes that cookie and this session row.

Chatbot Reference

Loading API reference…