02. Agentic AI Architecture¶
Browser RAG is not a multi-agent tool-using system in production. It is a RAG orchestration loop with optional LLM query rewriting, hybrid retrieval, and streamed local generation. Tool-calling plumbing exists but is disabled on the product path.
Orchestration model¶
graph TD
classDef default fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef highlight fill:#065f46,stroke:#34d399,stroke-width:2px,color:#f0fdf4
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:2px,color:#fffbeb
Q["User query + history"]:::highlight
RW["rewriteQueryForRetrieval<br/>maxTokens 128"]
RQ["Standalone retrieval query"]
HY["retrieveChunks<br/>embed + vector + keyword + RRF"]
CTX["Build Source N context"]
GEN["streamLLMWithToolLoop<br/>thinking on, tools off"]:::highlight
OUT["text / thinking / citations / debug"]
EMPTY["No relevant information..."]:::warning
Q --> RW --> RQ --> HY
HY -->|hits| CTX --> GEN --> OUT
HY -->|empty| EMPTY
linkStyle default stroke:#64748b,stroke-width:2px
Entry point: generateRAGAnswer in src/rag/orchestrator.ts.
Model provider boundaries¶
| Layer | Provider | Evidence |
|---|---|---|
| Embeddings | Transformers.js in embedding.worker |
src/rag/embedding-runtime.ts, src/workers/embedding.worker.ts |
| LLM — Transformers.js | @browser-ai/transformers-js / HF ONNX |
use-qwen35, catalog transformers-js |
| LLM — WebLLM | @mlc-ai/web-llm / @browser-ai/web-llm |
use-webllm |
| LLM — Gemma kernel | Bundled gemma-4-e2b.js |
use-gemma4 |
| LLM — LFM kernel | Bundled lfm2_5.js |
use-lfm2 |
Unified API: loadLLMVariant / streamLLMWithToolLoop / LLMEngineAdapter in src/llm/llm-runtime.ts.
Defaults (src/llm/llm-models.ts): iOS → WebLLM qwen-0.5b; else Transformers.js qwen35-0.8b.
Prompts¶
| Stage | Prompt role |
|---|---|
| Rewrite | System: produce a single standalone search query; user: history + latest message |
| Answer | System: answer only from context; cite [1]/[2]; include Context Excerpts |
| Tools | Prompt augmenters exist but return empty strings when tools disabled |
History for rewrite/answer is capped at MAX_HISTORY_TURNS = 8.
Tools and tool execution¶
Confirmed: RAG calls streamLLMWithToolLoop with toolsEnabled: false.
Confirmed stubs in llm-runtime.ts:
MAX_TOOL_ROUNDS = 0,MAX_TOOL_CALLS_PER_ROUND = 0executeToolCalls→[]buildToolPromptPrefix/buildToolPromptSection→''
Parsers and engine-features.ts still describe calculator/time-style capabilities — Inferred leftover from a broader Browser AI toolkit, not active in this app’s ask path.
Representative request sequence¶
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1e293b",
"primaryTextColor": "#f8fafc",
"primaryBorderColor": "#38bdf8",
"lineColor": "#64748b",
"actorBackground": "#1e293b",
"actorBorder": "#38bdf8",
"actorTextColor": "#f8fafc",
"noteBorderColor": "#fbbf24",
"noteBkgColor": "#78350f",
"noteTextColor": "#fffbeb"
}
}}%%
sequenceDiagram
participant Orch as Orchestrator
participant LLM as Engine Adapter
participant Ret as Retrieval
participant UI as Chat UI
Orch->>LLM: Rewrite stream (thinking/tools off)
LLM-->>Orch: rewritten query text
Orch-->>UI: retrieval_query
Orch->>Ret: retrieveChunks
Ret-->>Orch: results + RetrievalDebugInfo
Orch-->>UI: debug, citations
Orch->>LLM: Answer stream (thinking on, tools off)
loop tokens
LLM-->>Orch: text_delta / thinking_delta
Orch-->>UI: forward events
end
Orch-->>UI: done
Memory, state, resumability¶
| Concern | Behavior |
|---|---|
| In-chat memory | React state for messages; last 8 turns passed into orchestrator |
| Persistence | Completed Q/A written to query_history (chat route) |
| Checkpoints | None for mid-stream generation |
| Abort | abortSignal checked during rewrite/stream |
| Embedding lock | Project stores embedding_model_id; retrieval filters by it |
Run lifecycle (document indexing)¶
stateDiagram-v2
[*] --> pending: insert document
pending --> processing: indexDocument start
processing --> completed: chunks + embeddings committed
processing --> failed: extract/embed/DB error
failed --> processing: retry from stored file
completed --> [*]
Statuses live on documents.status (pending|processing|completed|failed). Schema also defines index_jobs, but no application writes were found.
Streaming and debug events¶
RAGAnswerChunk types: text_delta, thinking_delta, citations, retrieval_query, debug, done, error.
RagDebugInfo includes user vs rewritten query, history turn count, and full RetrievalDebugInfo (semantic/keyword/fused hits + stage timings). UI: src/components/chat/retrieval-debug-panel.tsx.
Human-in-the-loop¶
No approval gates for tool use (tools off). User controls: model selection, project settings, abort, document filter, retry failed indexes, backup/restore.
Failure handling¶
| Failure | Handling |
|---|---|
| Rewrite fails | Fall back to original query |
| No citations | Fixed “No relevant information…” text; no LLM call |
| Generation error | Yield { type: 'error', error } |
| Index failure | documents.status = failed + error_message; retry from IDB file bytes |
Interview Q&A¶
Q: Is this an agent?
A: Product path is a deterministic RAG pipeline with one optional LLM rewrite step, not a tool-calling agent loop. Tool infrastructure is stubbed.
Q: How do you keep multi-turn retrieval accurate?
A: rewriteQueryForRetrieval resolves pronouns into a standalone search query before hybrid search, while the answer turn still sees full recent history.
Q: Why hybrid + RRF?
A: Vector search misses exact tokens; keyword OR + ts_rank recovers lexical hits; RRF merges ranked lists without score calibration (k=60).