Coverage for src / lilbee / cli / settings_map.py: 100%
26 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
1"""Shared settings map for interactive configuration."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from enum import StrEnum
8from pydantic_core import PydanticUndefined
10from lilbee.config import ClustererBackend, KvCacheType, WikiEntityMode, cfg
13class RenderStyle(StrEnum):
14 """How a setting is displayed in /settings."""
16 COMPACT = "compact"
17 FULL = "full"
18 LIST_COLLAPSED = "list_collapsed"
21@dataclass(frozen=True)
22class SettingDef:
23 """Metadata for an interactive setting."""
25 type: type
26 nullable: bool
27 writable: bool = True
28 render: RenderStyle = field(default=RenderStyle.COMPACT)
29 group: str = "General"
30 help_text: str = ""
31 choices: tuple[str, ...] | None = None
34def get_default(key: str) -> object:
35 """Return the cfg default for a setting key."""
36 field_info = type(cfg).model_fields[key]
37 if field_info.default_factory is not None:
38 return field_info.default_factory() # type: ignore[call-arg]
39 if field_info.default is PydanticUndefined:
40 return None
41 return field_info.default
44SETTINGS_MAP: dict[str, SettingDef] = {
45 "chat_model": SettingDef(
46 str,
47 nullable=False,
48 writable=False,
49 group="Models",
50 help_text="LLM used for chat generation (vision and reranking are separate slots)",
51 ),
52 "vision_model": SettingDef(
53 str,
54 nullable=True,
55 writable=False,
56 group="Models",
57 help_text="Vision model for scanned PDF OCR (empty = disabled; Tesseract only)",
58 ),
59 "enable_ocr": SettingDef(
60 bool,
61 nullable=True,
62 group="Ingest",
63 help_text="Vision OCR for scanned PDFs (empty = auto-detect from vision_model)",
64 ),
65 "ocr_timeout": SettingDef(
66 float,
67 nullable=False,
68 group="Ingest",
69 help_text="Per-page timeout in seconds for vision OCR (0 = no limit)",
70 ),
71 "semantic_chunking": SettingDef(
72 bool,
73 nullable=False,
74 group="Ingest",
75 help_text="Opt-in topic-aware chunker (default off; may fragment numbered procedures)",
76 ),
77 "topic_threshold": SettingDef(
78 float,
79 nullable=False,
80 group="Ingest",
81 help_text="Topic-boundary similarity threshold, 0.0-1.0, used when semantic chunking is on",
82 ),
83 "embedding_model": SettingDef(
84 str,
85 nullable=False,
86 writable=False,
87 group="Models",
88 help_text="Model used to embed document chunks",
89 ),
90 "reranker_model": SettingDef(
91 str,
92 nullable=True,
93 writable=False,
94 group="Models",
95 help_text="Cross-encoder model for result reranking",
96 ),
97 "temperature": SettingDef(
98 float,
99 nullable=True,
100 group="Generation",
101 help_text="Sampling temperature (higher = more creative)",
102 ),
103 "top_p": SettingDef(
104 float,
105 nullable=True,
106 group="Generation",
107 help_text="Nucleus sampling cutoff probability",
108 ),
109 "top_k_sampling": SettingDef(
110 int,
111 nullable=True,
112 group="Generation",
113 help_text="Top-K sampling: number of tokens to consider",
114 ),
115 "repeat_penalty": SettingDef(
116 float,
117 nullable=True,
118 group="Generation",
119 help_text="Penalty for repeating tokens",
120 ),
121 "num_ctx": SettingDef(
122 int,
123 nullable=True,
124 group="Generation",
125 help_text=(
126 "Context window size in tokens. Leave empty to size automatically "
127 "to the host's available memory (capped at num_ctx_max)."
128 ),
129 ),
130 "num_ctx_max": SettingDef(
131 int,
132 nullable=False,
133 group="Generation",
134 help_text=(
135 "Upper bound for the dynamic context picker when num_ctx is unset. "
136 "Higher allows more retrieval context on hosts with spare memory."
137 ),
138 ),
139 "flash_attention": SettingDef(
140 bool,
141 nullable=True,
142 group="Generation",
143 help_text=(
144 "Flash attention. Empty (auto) tries it on with a fallback for older "
145 "llama-cpp-python builds; resolves the V-cache padding warning on "
146 "models with uneven per-layer V dims."
147 ),
148 ),
149 "kv_cache_type": SettingDef(
150 str,
151 nullable=False,
152 group="Generation",
153 help_text=(
154 "KV cache element type. q8_0 / q4_0 halve or quarter cache memory "
155 "but require flash attention to be enabled."
156 ),
157 choices=tuple(t.value for t in KvCacheType),
158 ),
159 "n_gpu_layers": SettingDef(
160 int,
161 nullable=True,
162 group="Generation",
163 help_text=(
164 "Layers to offload to GPU. Empty = all (recommended), 0 = CPU only, "
165 "positive int = partial offload for tight VRAM."
166 ),
167 ),
168 "seed": SettingDef(
169 int,
170 nullable=True,
171 group="Generation",
172 help_text="Random seed for reproducible output",
173 ),
174 "system_prompt": SettingDef(
175 str,
176 nullable=False,
177 render=RenderStyle.FULL,
178 group="Generation",
179 help_text="System prompt sent before every conversation",
180 ),
181 "top_k": SettingDef(
182 int,
183 nullable=False,
184 group="Retrieval",
185 help_text="Number of chunks returned by search",
186 ),
187 "rerank_candidates": SettingDef(
188 int,
189 nullable=False,
190 group="Retrieval",
191 help_text="Candidate pool size for reranking",
192 ),
193 "show_reasoning": SettingDef(
194 bool,
195 nullable=False,
196 group="Display",
197 help_text="Show model reasoning/thinking tokens in output",
198 ),
199 "theme": SettingDef(
200 str,
201 nullable=False,
202 group="Display",
203 help_text="TUI color theme. Cycle with Ctrl+T; the active theme persists across sessions.",
204 ),
205 "wiki": SettingDef(
206 bool,
207 nullable=False,
208 group="Wiki",
209 help_text="Enable the wiki layer (synthesis pages with citations)",
210 ),
211 "wiki_dir": SettingDef(
212 str,
213 nullable=False,
214 group="Wiki",
215 help_text="Directory under data_root where wiki pages are stored",
216 ),
217 "wiki_prune_raw": SettingDef(
218 bool,
219 nullable=False,
220 group="Wiki",
221 help_text="Delete raw chunks after summarizing into the wiki",
222 ),
223 "wiki_embedding_faithfulness_threshold": SettingDef(
224 float,
225 nullable=False,
226 group="Wiki",
227 help_text=(
228 "Minimum cosine similarity (0-1) between a generated page and "
229 "the mean of its source chunk vectors before publishing. "
230 "Pages below the threshold route to drafts/."
231 ),
232 ),
233 "wiki_stale_citation_threshold": SettingDef(
234 float,
235 nullable=False,
236 group="Wiki",
237 help_text="Fraction of stale citations that triggers page regeneration",
238 ),
239 "wiki_drift_threshold": SettingDef(
240 float,
241 nullable=False,
242 group="Wiki",
243 help_text="Max fraction of changed lines before regeneration requires review",
244 ),
245 "wiki_clusterer": SettingDef(
246 str,
247 nullable=False,
248 group="Wiki",
249 help_text="Synthesis clusterer backend (embedding or concepts)",
250 choices=tuple(b.value for b in ClustererBackend),
251 ),
252 "wiki_entity_mode": SettingDef(
253 str,
254 nullable=False,
255 group="Wiki",
256 help_text=(
257 "Entity extraction strategy "
258 "(ner_entities = default, typed NER entities; "
259 "plus_llm_types = NER + LLM-proposed schema; "
260 "llm_tagged = LLM tags every chunk)"
261 ),
262 choices=tuple(m.value for m in WikiEntityMode),
263 ),
264 "wiki_entity_min_mentions": SettingDef(
265 int,
266 nullable=False,
267 group="Wiki",
268 help_text="Minimum chunk mentions before an entity or concept gets its own page",
269 ),
270 "wiki_concept_max_chunks_per_page": SettingDef(
271 int,
272 nullable=False,
273 group="Wiki",
274 help_text="Maximum chunks passed into each concept or entity page generation call",
275 ),
276 "wiki_related_max": SettingDef(
277 int,
278 nullable=False,
279 group="Wiki",
280 help_text="Maximum related concepts listed in the `## Related` section of each page",
281 ),
282 "wiki_ingest_update_cap": SettingDef(
283 int,
284 nullable=False,
285 group="Wiki",
286 help_text=(
287 "Touched-page cap for auto-update after sync. "
288 "Beyond this count, run `lilbee wiki update` manually."
289 ),
290 ),
291 "wiki_summary_prompt": SettingDef(
292 str,
293 nullable=False,
294 render=RenderStyle.FULL,
295 group="Wiki",
296 help_text=(
297 "Prompt for per-source summary pages. "
298 "Must keep the {source_name} and {chunks_text} placeholders."
299 ),
300 ),
301 "wiki_synthesis_prompt": SettingDef(
302 str,
303 nullable=False,
304 render=RenderStyle.FULL,
305 group="Wiki",
306 help_text=(
307 "Prompt for cross-source synthesis pages. "
308 "Must keep {topic}, {source_list}, and {chunks_text}."
309 ),
310 ),
311 "wiki_entity_batch_prompt": SettingDef(
312 str,
313 nullable=False,
314 render=RenderStyle.FULL,
315 group="Wiki",
316 help_text=(
317 "Prompt for the per-source batched call. "
318 "Must keep {source}, {entity_list}, {chunks_text}, and {concept_instruction}."
319 ),
320 ),
321 "wiki_extract_concepts": SettingDef(
322 bool,
323 nullable=False,
324 group="Wiki",
325 help_text=(
326 "Whether the per-source batched call asks the LLM to curate concept pages "
327 "alongside the pre-extracted entity list."
328 ),
329 ),
330 "wiki_batch_min_chunks": SettingDef(
331 int,
332 nullable=False,
333 group="Wiki",
334 help_text=(
335 "Minimum chunks a source must contribute before its batched call includes "
336 "concept curation. Sources below the floor skip the concept-curation "
337 "instruction; sources with zero entities AND below the floor are skipped entirely."
338 ),
339 ),
340 "wiki_clusterer_k": SettingDef(
341 int,
342 nullable=False,
343 group="Wiki",
344 help_text="Mutual-kNN neighborhood size for the clusterer (0 = auto)",
345 ),
346 "crawl_max_depth": SettingDef(
347 int,
348 nullable=True,
349 group="Crawling",
350 help_text="Optional recursion-depth cap (blank = no cap; per-crawl values win)",
351 ),
352 "crawl_max_pages": SettingDef(
353 int,
354 nullable=True,
355 group="Crawling",
356 help_text="Optional global cap on total pages per crawl (blank = no cap).",
357 ),
358 "crawl_timeout": SettingDef(
359 int,
360 nullable=False,
361 group="Crawling",
362 help_text="Per-page fetch timeout in seconds",
363 ),
364 "crawl_sync_interval": SettingDef(
365 int,
366 nullable=False,
367 group="Crawling",
368 help_text="Seconds between periodic re-syncs during a crawl (0 = sync only at end)",
369 ),
370 "crawl_mean_delay": SettingDef(
371 float,
372 nullable=False,
373 group="Crawling",
374 help_text="Seconds between in-flight requests within a single crawl",
375 ),
376 "crawl_max_delay_range": SettingDef(
377 float,
378 nullable=False,
379 group="Crawling",
380 help_text="Random jitter (seconds) added on top of mean delay",
381 ),
382 "crawl_concurrent_requests": SettingDef(
383 int,
384 nullable=False,
385 group="Crawling",
386 help_text="Concurrent in-flight URLs within one crawl",
387 ),
388 "crawl_retry_on_rate_limit": SettingDef(
389 bool,
390 nullable=False,
391 group="Crawling",
392 help_text="Enable per-domain backoff and retries on HTTP 429/503",
393 ),
394 "crawl_retry_base_delay_min": SettingDef(
395 float,
396 nullable=False,
397 group="Crawling",
398 help_text="Minimum base-delay (seconds) on rate-limit responses",
399 ),
400 "crawl_retry_base_delay_max": SettingDef(
401 float,
402 nullable=False,
403 group="Crawling",
404 help_text="Maximum base-delay (seconds) on rate-limit responses",
405 ),
406 "crawl_retry_max_backoff": SettingDef(
407 float,
408 nullable=False,
409 group="Crawling",
410 help_text="Upper bound on any single backoff wait (seconds)",
411 ),
412 "crawl_retry_max_attempts": SettingDef(
413 int,
414 nullable=False,
415 group="Crawling",
416 help_text="Retry count per URL when a rate-limit code comes back",
417 ),
418 "crawl_exclude_patterns": SettingDef(
419 list,
420 nullable=False,
421 group="Crawling",
422 render=RenderStyle.LIST_COLLAPSED,
423 help_text=(
424 "Regex patterns that skip URLs at link-discovery time during "
425 "recursive crawls. One per line."
426 ),
427 ),
428 "openai_api_key": SettingDef(
429 str,
430 nullable=False,
431 group="API-Keys",
432 help_text="OpenAI API key (enables frontier models in chat picker)",
433 ),
434 "anthropic_api_key": SettingDef(
435 str,
436 nullable=False,
437 group="API-Keys",
438 help_text="Anthropic API key (enables frontier models in chat picker)",
439 ),
440 "gemini_api_key": SettingDef(
441 str,
442 nullable=False,
443 group="API-Keys",
444 help_text="Google Gemini API key (enables frontier models in chat picker)",
445 ),
446}