sqlite_graphrag/constants.rs
1//! Compile-time constants shared across the crate.
2//!
3//! Grouped into embedding configuration, length and size limits, SQLite
4//! pragmas and retrieval tuning knobs. Values are taken from the PRD and
5//! must stay in sync with the migrations under `migrations/`.
6//!
7//! ## Cálculo dinâmico de permits de concorrência
8//!
9//! O número máximo de instâncias simultâneas pode ser ajustado em runtime
10//! usando a fórmula:
11//!
12//! ```text
13//! permits = min(cpus, available_memory_mb / EMBEDDING_LOAD_EXPECTED_RSS_MB) * 0.5
14//! ```
15//!
16//! onde `available_memory_mb` é obtido via `sysinfo::System::available_memory()`
17//! convertido para MiB. O resultado é limitado superiormente por
18//! `MAX_CONCURRENT_CLI_INSTANCES` e inferiorizado em 1.
19
20/// Embedding vector dimensionality produced by `multilingual-e5-small`.
21pub const EMBEDDING_DIM: usize = 384;
22
23/// Default `fastembed` model identifier used by `remember` and `recall`.
24pub const FASTEMBED_MODEL_DEFAULT: &str = "multilingual-e5-small";
25
26/// Batch size for `fastembed` encoding calls.
27pub const FASTEMBED_BATCH_SIZE: usize = 32;
28
29/// Maximum byte length for a memory `name` field in kebab-case.
30pub const MAX_MEMORY_NAME_LEN: usize = 80;
31
32/// Maximum character length for a memory `description` field.
33pub const MAX_MEMORY_DESCRIPTION_LEN: usize = 500;
34
35/// Hard upper bound on memory `body` length in bytes.
36pub const MAX_MEMORY_BODY_LEN: usize = 512_000;
37
38/// Body character count above which the body is split into chunks.
39pub const MAX_BODY_CHARS_BEFORE_CHUNK: usize = 8_000;
40
41/// Maximum attempts when a statement returns `SQLITE_BUSY`.
42pub const MAX_SQLITE_BUSY_RETRIES: u32 = 5;
43
44/// Base delay in milliseconds for the first SQLITE_BUSY retry.
45///
46/// Each subsequent attempt doubles the delay (exponential backoff):
47/// 300 ms → 600 ms → 1200 ms → 2400 ms → 4800 ms (≈ 9.3 s total).
48pub const SQLITE_BUSY_BASE_DELAY_MS: u64 = 300;
49
50/// Query timeout applied to statements in milliseconds.
51pub const QUERY_TIMEOUT_MILLIS: u64 = 5_000;
52
53/// Jaccard threshold above which two memories are considered fuzzy duplicates.
54pub const DEDUP_FUZZY_THRESHOLD: f64 = 0.8;
55
56/// Cosine distance threshold below which two memories are semantic duplicates.
57pub const DEDUP_SEMANTIC_THRESHOLD: f32 = 0.1;
58
59/// Maximum number of hops allowed in graph traversals.
60pub const MAX_GRAPH_HOPS: u32 = 2;
61
62/// Minimum relationship weight required for traversal inclusion.
63pub const MIN_RELATION_WEIGHT: f64 = 0.3;
64
65/// Default traversal depth for `related` when `--hops` is omitted.
66pub const DEFAULT_MAX_HOPS: u32 = 2;
67
68/// Default minimum weight filter applied during graph traversal.
69pub const DEFAULT_MIN_WEIGHT: f64 = 0.3;
70
71/// Default weight assigned to newly created relationships.
72pub const DEFAULT_RELATION_WEIGHT: f64 = 0.5;
73
74/// Default `k` used by `recall` when the caller omits `--k`.
75pub const DEFAULT_K_RECALL: usize = 10;
76
77/// Default `k` for memory KNN searches when the caller omits `--k`.
78pub const K_MEMORIES_DEFAULT: usize = 10;
79
80/// Default `k` for entity KNN searches during graph expansion.
81pub const K_ENTITIES_SEARCH: usize = 5;
82
83/// Upper bound on distinct entities persisted per memory.
84pub const MAX_ENTITIES_PER_MEMORY: usize = 30;
85
86/// Upper bound on distinct relationships persisted per memory.
87pub const MAX_RELATIONSHIPS_PER_MEMORY: usize = 50;
88
89/// Resolve o cap de relacionamentos por memória, respeitando override por env var.
90///
91/// v1.0.22: torna o cap (default 50) configurável via `SQLITE_GRAPHRAG_MAX_RELATIONS_PER_MEMORY`.
92/// Auditoria identificou que documentos ricos batiam o cap silenciosamente; usuários
93/// com corpus técnico denso podem aumentar via env. Valores fora de [1, 10000] caem no default.
94pub fn max_relationships_per_memory() -> usize {
95 std::env::var("SQLITE_GRAPHRAG_MAX_RELATIONS_PER_MEMORY")
96 .ok()
97 .and_then(|v| v.parse::<usize>().ok())
98 .filter(|&n| (1..=10_000).contains(&n))
99 .unwrap_or(MAX_RELATIONSHIPS_PER_MEMORY)
100}
101
102/// Character length of the description preview shown in `list` output.
103pub const TEXT_DESCRIPTION_PREVIEW_LEN: usize = 100;
104
105/// `PRAGMA busy_timeout` value applied on every connection.
106pub const BUSY_TIMEOUT_MILLIS: i32 = 5_000;
107
108/// `PRAGMA cache_size` value in kibibytes (negative means KiB).
109pub const CACHE_SIZE_KB: i32 = -64_000;
110
111/// `PRAGMA mmap_size` value in bytes applied to each connection.
112pub const MMAP_SIZE_BYTES: i64 = 268_435_456;
113
114/// `PRAGMA wal_autocheckpoint` threshold in pages.
115pub const WAL_AUTOCHECKPOINT_PAGES: i32 = 1_000;
116
117/// Default `k` constant used by Reciprocal Rank Fusion in `hybrid-search`.
118pub const RRF_K_DEFAULT: u32 = 60;
119
120/// Chunk size expressed in tokens for body splitting.
121pub const CHUNK_SIZE_TOKENS: usize = 400;
122
123/// Token overlap between consecutive chunks.
124pub const CHUNK_OVERLAP_TOKENS: usize = 50;
125
126/// Guard operacional explícito para documentos multi-chunk no `remember`.
127///
128/// O caminho multi-chunk usa embeddings seriais para evitar amplificação de memória no ONNX.
129/// Este limite preserva um teto operacional claro para agentes e scripts.
130pub const REMEMBER_MAX_SAFE_MULTI_CHUNKS: usize = 512;
131
132/// Teto de chunks por micro-batch controlado no `remember`.
133///
134/// O runtime do `fastembed` usa padding `BatchLongest`, então batches muito grandes amplificam
135/// o custo do maior chunk. Este teto mantém batches pequenos mesmo quando os chunks são curtos.
136pub const REMEMBER_MAX_CONTROLLED_BATCH_CHUNKS: usize = 4;
137
138/// Orçamento máximo de tokens preenchidos por micro-batch controlado no `remember`.
139///
140/// O orçamento usa `max_tokens_no_batch * tamanho_do_batch`, aproximando o custo real do
141/// padding `BatchLongest`. Valores acima disso voltam para batches menores ou serialização.
142pub const REMEMBER_MAX_CONTROLLED_BATCH_PADDED_TOKENS: usize = 512;
143
144/// Timeout in milliseconds for a single ping probe against the daemon socket.
145pub const DAEMON_PING_TIMEOUT_MS: u64 = 10;
146
147/// Idle duration in seconds before the daemon shuts itself down.
148pub const DAEMON_IDLE_SHUTDOWN_SECS: u64 = 600;
149
150/// Tempo máximo de espera para o daemon ficar saudável após auto-start.
151pub const DAEMON_AUTO_START_MAX_WAIT_MS: u64 = 5_000;
152
153/// Intervalo inicial de polling para verificar se o daemon ficou saudável.
154pub const DAEMON_AUTO_START_INITIAL_BACKOFF_MS: u64 = 50;
155
156/// Teto do backoff entre tentativas automáticas de spawn do daemon.
157pub const DAEMON_AUTO_START_MAX_BACKOFF_MS: u64 = 30_000;
158
159/// Backoff base usado após falhas de spawn/health do daemon.
160pub const DAEMON_SPAWN_BACKOFF_BASE_MS: u64 = 500;
161
162/// Tempo máximo de espera para obter o lock de spawn do daemon.
163pub const DAEMON_SPAWN_LOCK_WAIT_MS: u64 = 2_000;
164
165/// Prefix prepended to bodies before embedding as required by E5 models.
166pub const PASSAGE_PREFIX: &str = "passage: ";
167
168/// Prefix prepended to queries before embedding as required by E5 models.
169pub const QUERY_PREFIX: &str = "query: ";
170
171/// Crate version string sourced from `CARGO_PKG_VERSION` at build time.
172pub const SQLITE_GRAPHRAG_VERSION: &str = env!("CARGO_PKG_VERSION");
173
174/// Batch size for BERT NER forward passes.
175///
176/// Larger values amortise fixed forward-pass overhead but increase peak RAM.
177/// Memory guide (CPU only, max 512-token windows):
178/// N=4 → ~54 MiB peak
179/// N=8 → ~108 MiB peak ← default
180/// N=16 → ~216 MiB peak
181/// N=32 → ~432 MiB peak (not recommended without 16+ GiB RAM)
182///
183/// Override via `GRAPHRAG_NER_BATCH_SIZE` env var. Values outside [1, 32] are
184/// clamped silently.
185pub fn ner_batch_size() -> usize {
186 std::env::var("GRAPHRAG_NER_BATCH_SIZE")
187 .ok()
188 .and_then(|v| v.parse::<usize>().ok())
189 .unwrap_or(8)
190 .clamp(1, 32)
191}
192
193/// PRD-canonical regex que valida nomes e namespaces. Permite 1 char `[a-z0-9]`
194/// OU string de 2-80 chars começando com letra e terminando com letra/dígito,
195/// contendo apenas `[a-z0-9-]`. Rejeita prefixo `__` (internal reserved).
196pub const NAME_SLUG_REGEX: &str = r"^[a-z][a-z0-9-]{0,78}[a-z0-9]$|^[a-z0-9]$";
197
198/// Retenção padrão (dias) usada por `purge` quando `--retention-days` é omitido.
199pub const PURGE_RETENTION_DAYS_DEFAULT: u32 = 90;
200
201/// Limite máximo de namespaces ativos (deleted_at IS NULL) simultâneos. Exit 5 ao exceder.
202pub const MAX_NAMESPACES_ACTIVE: u32 = 100;
203
204/// Máximo de tokens aceito por embedding input antes de chunking.
205pub const EMBEDDING_MAX_TOKENS: usize = 512;
206
207/// Limite máximo de resultados da CTE recursiva de grafo em `recall`.
208pub const K_GRAPH_MATCHES_LIMIT: usize = 20;
209
210/// Default `--limit` para `list` quando omitido.
211pub const K_LIST_DEFAULT_LIMIT: usize = 100;
212
213/// Default `--limit` para `graph entities` quando omitido.
214pub const K_GRAPH_ENTITIES_DEFAULT_LIMIT: usize = 50;
215
216/// Default `--limit` para `related` quando omitido.
217pub const K_RELATED_DEFAULT_LIMIT: usize = 10;
218
219/// Default `--limit` para `history` quando omitido.
220pub const K_HISTORY_DEFAULT_LIMIT: usize = 20;
221
222/// Peso padrão da contribuição vetorial na fórmula RRF de `hybrid-search`.
223pub const WEIGHT_VEC_DEFAULT: f64 = 1.0;
224
225/// Peso padrão da contribuição textual BM25 na fórmula RRF de `hybrid-search`.
226pub const WEIGHT_FTS_DEFAULT: f64 = 1.0;
227
228/// Tamanho em caracteres do preview do body emitido em formatos text/markdown.
229pub const TEXT_BODY_PREVIEW_LEN: usize = 200;
230
231/// Valor default injetado em ORT_NUM_THREADS quando não definido pelo usuário.
232pub const ORT_NUM_THREADS_DEFAULT: &str = "1";
233
234/// Valor default injetado em ORT_INTRA_OP_NUM_THREADS quando não definido.
235pub const ORT_INTRA_OP_NUM_THREADS_DEFAULT: &str = "1";
236
237/// Valor default injetado em OMP_NUM_THREADS quando não definido pelo usuário.
238pub const OMP_NUM_THREADS_DEFAULT: &str = "1";
239
240/// Exit code para falha parcial de batch (PRD linha 1822). Conflita com DbBusy em v1.x;
241/// em v2.0.0 DbBusy migra para 15 e este código assume 13 conforme PRD.
242pub const BATCH_PARTIAL_FAILURE_EXIT_CODE: i32 = 13;
243
244/// Exit code para DbBusy em v2.0.0 (migrado de 13 para liberar 13 para batch failure).
245pub const DB_BUSY_EXIT_CODE: i32 = 15;
246
247/// Filename used for the advisory exclusive lock that prevents parallel invocations.
248pub const CLI_LOCK_FILE: &str = "cli.lock";
249
250/// Polling interval em milliseconds usado por `--wait-lock` entre tentativas de `try_lock_exclusive`.
251pub const CLI_LOCK_POLL_INTERVAL_MS: u64 = 500;
252
253/// Process exit code returned when the lock is busy and no wait was requested (EX_TEMPFAIL).
254pub const CLI_LOCK_EXIT_CODE: i32 = 75;
255
256/// Número máximo de instâncias CLI em execução simultânea.
257///
258/// Alinhado com `DAEMON_MAX_CONCURRENT_CLIENTS` do PRD. Limita o semáforo de
259/// contagem em [`crate::lock`] para evitar sobrecarga de memória quando múltiplas
260/// invocações paralelas tentam carregar o modelo ONNX simultaneamente.
261pub const MAX_CONCURRENT_CLI_INSTANCES: usize = 4;
262
263/// Memória disponível mínima em MiB exigida antes de iniciar o carregamento do modelo.
264///
265/// Se `sysinfo::System::available_memory() / 1_048_576` estiver abaixo deste
266/// valor, a invocação é abortada com [`crate::errors::AppError::LowMemory`]
267/// (exit code [`LOW_MEMORY_EXIT_CODE`]).
268pub const MIN_AVAILABLE_MEMORY_MB: u64 = 2_048;
269
270/// Tempo máximo em segundos que uma instância aguarda para adquirir um slot de concorrência.
271///
272/// Passado como default de `--max-wait-secs` na CLI. Após esgotar este limite,
273/// a invocação retorna [`crate::errors::AppError::AllSlotsFull`] com exit code
274/// [`CLI_LOCK_EXIT_CODE`] (75).
275pub const CLI_LOCK_DEFAULT_WAIT_SECS: u64 = 300;
276
277/// RSS esperado em MiB de uma única instância com o modelo ONNX carregado via fastembed.
278///
279/// Usado na fórmula `min(cpus, available_memory_mb / EMBEDDING_LOAD_EXPECTED_RSS_MB) * 0.5`
280/// para calcular o número dinâmico de permits.
281///
282/// Valor calibrado em 2026-04-23 com `/usr/bin/time -v` sobre `sqlite-graphrag v1.0.3`
283/// nos comandos pesados `remember`, `recall` e `hybrid-search`, todos com pico de RSS
284/// próximo de 1.03 GiB por processo. O valor abaixo arredonda para cima com margem defensiva.
285pub const EMBEDDING_LOAD_EXPECTED_RSS_MB: u64 = 1_100;
286
287/// Process exit code retornado quando memória disponível está abaixo de [`MIN_AVAILABLE_MEMORY_MB`].
288///
289/// Valor `77` é `EX_NOPERM` na glibc sysexits, reutilizado aqui para indicar
290/// "recurso de sistema insuficiente para prosseguir".
291pub const LOW_MEMORY_EXIT_CODE: i32 = 77;
292
293/// Valor canônico de `PRAGMA user_version` gravado após migrações.
294///
295/// Deve permanecer em sincronia com o identificador legível-por-humanos
296/// da versão do schema. Refinery usa sua própria tabela de histórico;
297/// `user_version` é um campo auxiliar de diagnóstico para ferramentas
298/// externas (ex: `sqlite3 db.sqlite "PRAGMA user_version"`).
299pub const SCHEMA_USER_VERSION: i64 = 49;