LocalLens
アーキテクチャ

リクエストの流れ

1 つの質問を retrieval から LLM まで端から端まで追います。

質問の流れは LocalLens のホットパスです。チャットのラウンドトリップは すべて、固定の順序で 4 つのアクターを通過します。caller、ワークフロー クラス、QVAC gateway、そしてプロンプトビルダーです。

流れは 2 つの半分に分かれます。

  • Retrieval (RAG): 質問を GTE_LARGE_FP16 で埋め込み、workspace に対して ragSearch を実行し、top-K の chunks を返します。この側は 回答の言葉を 1 語も書きません。
  • Generation (LLM): retrieve した chunks と質問を QWEN3_1_7B_INST_Q4 (またはフォールバックの QWEN3_600M_INST_Q4) に completion() で渡します。これが実際に 回答を組み立てるチャットモデルで、トークンをストリームで返します。

ステップごとの流れ

1. Embed と search (RAG)

LocalLensApp.askBrain(id, question) (src/locallens.ts) は QvacGateway.search(workspace, question, 5) を呼びます。gateway は質問を GTE_LARGE_FP16 で embed し、brain の QVAC workspace に対して ragSearch を実行します。結果は SearchHit のリストです。

type SearchHit = {
  id: string;
  relativePath: string;
  chunkIndex: number;
  content: string;
  score?: number;
};

Top-K は 5 に固定されています。これはプロンプトサイズと回答品質の バランスで、小さな brain では実際よく機能します。変える場合は チャットモデルの context window (デフォルト 4096 トークン) に 注意してください。

2. Grounded prompt を組み立てる (継ぎ目)

src/rag.tsbuildGroundedHistory(question, hits) は 2 件のメッセージからなる ChatMessage[] を返します。これが RAG 側 (hits を生成した側) と LLM (これから hits を読む側) の継ぎ目です。

  • system メッセージ: ルールを与えます。excerpts の事実のみを使う、 ブラケットで引用する、ユーザーの言語で答える、隠れた chain-of-thought は出さない。
  • user メッセージ: 番号付きブロックとして excerpts を並べ、 そのあとに質問を続けます。

番号付き excerpts は、モデルが引用するときに [1] [2] として 返してくるものです。LLM は embeddings や検索スコアを一切見ません。 このパッケージ化されたプロンプトだけを見ます。

3. LLM の completion をストリームする

QvacGateway.answer(history)チャット LLM の仕事の場所です。 QVAC の completion()stream: true 付きで QWEN3_1_7B_INST_Q4 (デフォルトの 1.7B パラメータ、Q4 量子化の Qwen3 instruct モデル) に対して呼び出し、各 contentDelta イベントを AsyncGenerator<string> として yield します。askBrain はそれらの トークンを蓄積して最終文字列にします。

1.7B をロードできないマシンでは、gateway がモデルロード時に QWEN3_600M_INST_Q4 に透過的にフォールバックします ( QvacGateway.loadModels を参照)。streaming generator の形はどちらでも同じなので、上流は 変化に気づきません。

captureThinking: true は内部の reasoning トークンを可視出力から 除外し、kvCache: true は follow-up のために prefix のアテンション 状態を再利用させます。

実際に回答が書かれるのはこのステップです。ここを飛ばすと、 ランク付けされた chunks はあっても、文章としての回答はありません。

4. 引用を返す

プロンプトの構築に使った hits は、ChatAnswer.citations として そのまま caller に返ります。

type ChatAnswer = {
  answer: string;
  citations: { id; relativePath; chunkIndex; score? }[];
};

CLI がこれを出力し、UI が回答の下にこれを描画します。回答は LLM の出力、引用は RAG 側のエビデンスリスト。両方が一緒に届くことで、 読み手は主張をソースに照らして検証できます。

この流れに ない もの

  • 再 ingest は走りません。 質問のときにファイルシステムには 触れません。
  • store の書き換えは起きません。 JSON store は質問パスでは read-only です。
  • LLM への 2 回目の呼び出しはありません。 質問 1 件につき completion は 1 回。embedding model は 1 回 (search 用)、 chat LLM は 1 回 (回答用) です。

read path をこれだけ薄く保っているのが、follow-up が控えめな ハードウェアでも軽快に感じる理由です。モデル呼び出し 2 回、 ディスク書き込みゼロ、そして LLM が見るのは top-K chunks と質問だけ。

On this page