sqlite_graphrag/backend_choice.rs
1//! LLM / embedding backend CLI choices (Wave C1).
2
3/// LLM backend for embedding. Accepts `openrouter` (OpenRouter REST) or
4/// `none` (skips embedding; useful for tests).
5#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
6pub enum LlmBackendChoice {
7 /// Open router variant.
8 OpenRouter,
9 /// None variant.
10 None,
11}
12
13/// v1.0.93: embedding backend selector. Separate from `--llm-backend` which
14/// controls enrichment (entity extraction, body enrichment).
15/// `auto` uses OpenRouter when a client is initialised.
16/// `openrouter` requires API key (exit 78 if absent).
17#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
18pub enum EmbeddingBackendChoice {
19 /// Auto variant.
20 Auto,
21 /// Openrouter variant.
22 Openrouter,
23}
24
25/// The `llm` + `embedding` pair, named once.
26///
27/// GAP-SG-265: these two selectors are resolved together in `main`, travel
28/// together down every write path, and are consumed together by
29/// `embed_passage_with_embedding_choice`. Passing them as two positional
30/// parameters cost one argument slot in each of the signatures that carry them,
31/// and several of those signatures sat one slot over the
32/// `clippy::too_many_arguments` threshold for exactly that reason.
33///
34/// This is a plain aggregate on purpose: the two fields keep their own types,
35/// so nothing that reads them changes, and the struct is `Copy` so threading it
36/// through a call chain stays as cheap as threading the two enums was.
37#[derive(Copy, Clone, Debug, PartialEq, Eq)]
38pub struct BackendChoice {
39 /// Which LLM backend answers enrichment calls.
40 pub llm: LlmBackendChoice,
41 /// Which backend computes embeddings.
42 pub embedding: EmbeddingBackendChoice,
43}
44
45impl BackendChoice {
46 /// Builds the pair from the two CLI selectors.
47 pub fn new(llm: LlmBackendChoice, embedding: EmbeddingBackendChoice) -> Self {
48 Self { llm, embedding }
49 }
50}
51
52impl EmbeddingBackendChoice {
53 /// v1.0.93: produces a fallback chain that prepends OpenRouter when
54 /// the client is initialised.
55 pub fn to_chain(self, llm_choice: LlmBackendChoice) -> Vec<crate::embedder::LlmBackendKind> {
56 use crate::embedder::LlmBackendKind;
57 match self {
58 EmbeddingBackendChoice::Openrouter => vec![LlmBackendKind::OpenRouter],
59 EmbeddingBackendChoice::Auto => {
60 if crate::embedder::is_openrouter_initialized() {
61 let mut chain = vec![LlmBackendKind::OpenRouter];
62 chain.extend(llm_choice.to_chain());
63 chain
64 } else {
65 llm_choice.to_chain()
66 }
67 }
68 }
69 }
70}
71
72impl LlmBackendChoice {
73 /// Converts the CLI choice into an ordered chain of backends that
74 /// `embedder::embed_with_fallback` iterates. The first element of the
75 /// chain is the preferred backend; subsequent elements are fallbacks
76 /// used when the preferred one fails with `LlmBackendError`.
77 pub fn to_chain(self) -> Vec<crate::embedder::LlmBackendKind> {
78 use crate::embedder::LlmBackendKind;
79 match self {
80 LlmBackendChoice::OpenRouter => {
81 vec![LlmBackendKind::OpenRouter, LlmBackendKind::None]
82 }
83 LlmBackendChoice::None => vec![LlmBackendKind::None],
84 }
85 }
86}