Coverage for src/lilbee/app/settings_map.py: 100%
46 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
1"""Shared settings map for interactive configuration."""
3from __future__ import annotations
5from dataclasses import dataclass, field, replace
6from enum import StrEnum
8from lilbee.app.themes import DARK_THEMES
9from lilbee.core.config.model import CLEARABLE_MODEL_FIELDS
10from lilbee.core.config.schema import field_value_set
13class RenderStyle(StrEnum):
14 """How a setting is displayed in /settings."""
16 COMPACT = "compact"
17 FULL = "full"
18 MULTILINE = "multiline"
21class SettingGroup(StrEnum):
22 """Logical bucket names rendered by ``/settings`` and ``settings_list``."""
24 MODELS = "Models"
25 GENERATION = "Generation"
26 RETRIEVAL = "Retrieval"
27 INGEST = "Ingest"
28 OCR_TUNING = "OCR-Tuning"
29 WIKI = "Wiki"
30 MEMORY = "Memory"
31 CRAWLING = "Crawling"
32 LOCAL_SERVERS = "Local-Servers"
33 API_KEYS = "API-Keys"
34 SYSTEM = "System"
35 DISPLAY = "Display"
38@dataclass(frozen=True)
39class SettingDef:
40 """Metadata for an interactive setting.
42 ``writable`` is a TUI rendering hint: fields marked ``writable=False``
43 (the model role slots) get a dedicated picker rather than an inline
44 editor, and the ``/set`` slash command refuses them, except that it
45 sets or clears a model role that can be off. The actual
46 write contract for HTTP / MCP / programmatic surfaces lives in
47 ``config_meta.WRITABLE_CONFIG_FIELDS`` + ``MODEL_ROLE_FIELDS`` and
48 is enforced by ``app.settings.apply_settings_update``.
50 ``choices`` is the closed value set a picker renders. It is read off
51 the Config field's own type, so only a field whose value set lives
52 outside that type declares one.
54 ``hidden`` keeps the setting out of the TUI settings screen while
55 leaving it reachable via ``lilbee set`` and the ``LILBEE_*`` env
56 var: use it for transport/server knobs that aren't relevant to a
57 typical TUI session.
59 ``advanced`` keeps the setting shown, but folded into a collapsed
60 section at the bottom of its tab: use it for a setting a normal
61 user never needs to get good results (engine internals, timeouts,
62 batch sizes, thresholds for internal heuristics, debug knobs).
63 """
65 type: type
66 nullable: bool
67 group: SettingGroup
68 writable: bool = True
69 render: RenderStyle = field(default=RenderStyle.COMPACT)
70 help_text: str = ""
71 choices: tuple[str, ...] | None = None
72 hidden: bool = False
73 advanced: bool = False
74 # List editors validate each line as a regex only when this is set; flag-style
75 # lists (e.g. crawl_browser_extra_args) would be wrongly rejected otherwise.
76 validate_regex: bool = False
77 # Credentials: the TUI masks the editor so the value is never on screen in
78 # plain text, including while it is being pasted.
79 secret: bool = False
82CHUNK_LIMIT_KEY = "max_chunks_per_file"
84SETTINGS_MAP: dict[str, SettingDef] = {
85 "chat_model": SettingDef(
86 str,
87 nullable="chat_model" in CLEARABLE_MODEL_FIELDS,
88 writable=False,
89 group=SettingGroup.MODELS,
90 help_text="LLM used for chat generation (vision and reranking are separate slots)",
91 ),
92 "vision_model": SettingDef(
93 str,
94 nullable="vision_model" in CLEARABLE_MODEL_FIELDS,
95 writable=False,
96 group=SettingGroup.MODELS,
97 help_text=(
98 "Picks the OCR engine: a set vision model reads scanned pages, an empty one "
99 "(including an empty LILBEE_VISION_MODEL) leaves them to Tesseract. "
100 "The ocr setting decides whether pages are read"
101 ),
102 ),
103 "ocr": SettingDef(
104 str,
105 nullable=False,
106 group=SettingGroup.INGEST,
107 help_text=(
108 "Scanned pages: auto reads each page without usable text and keeps the text of "
109 "every other page; all reads every page of every file on every future ingest; "
110 "off skips them. vision_model picks the engine"
111 ),
112 ),
113 "ocr_timeout": SettingDef(
114 float,
115 nullable=False,
116 group=SettingGroup.OCR_TUNING,
117 help_text="Per-page timeout in seconds for vision OCR (0 = no limit)",
118 advanced=True,
119 ),
120 "vision_load_budget_s": SettingDef(
121 float,
122 nullable=False,
123 group=SettingGroup.OCR_TUNING,
124 help_text=(
125 "Wall-clock seconds reserved for the vision worker to load the"
126 " model. Total PDF-OCR budget = load_budget + ocr_timeout * pages."
127 ),
128 advanced=True,
129 ),
130 "vision_ocr_max_tokens": SettingDef(
131 int,
132 nullable=False,
133 group=SettingGroup.OCR_TUNING,
134 help_text=(
135 "Hard cap on tokens generated per OCR page (bounds runaway repetition"
136 " loops); raising it lengthens page generation, so give ocr_timeout headroom"
137 ),
138 advanced=True,
139 ),
140 "vision_ocr_concurrency": SettingDef(
141 int,
142 nullable=False,
143 group=SettingGroup.OCR_TUNING,
144 help_text="Pages OCR'd concurrently per vision server; each slot adds KV cache memory",
145 advanced=True,
146 ),
147 "extraction_timeout": SettingDef(
148 int,
149 nullable=False,
150 group=SettingGroup.INGEST,
151 help_text=(
152 "Wall-clock seconds one file gets to extract before ingest gives up"
153 " on it (0 = no limit)"
154 ),
155 advanced=True,
156 ),
157 "ingest_workers": SettingDef(
158 int,
159 nullable=False,
160 group=SettingGroup.INGEST,
161 help_text="Workers for discovering and hashing files (0 = auto, all available cores)",
162 advanced=True,
163 ),
164 "ingest_processes": SettingDef(
165 int,
166 nullable=False,
167 group=SettingGroup.INGEST,
168 help_text=(
169 "Ingest worker processes, one GPU each (0 = auto, one per card). Used"
170 " once the corpus is big enough to pay for them; 1 keeps ingest in this"
171 " process"
172 ),
173 advanced=True,
174 ),
175 "mcp_tool_threads": SettingDef(
176 int,
177 nullable=False,
178 group=SettingGroup.LOCAL_SERVERS,
179 help_text=(
180 "Threads for synchronous MCP tool handlers; the ceiling on how many agents"
181 " one daemon serves before retrieval calls queue"
182 ),
183 advanced=True,
184 ),
185 "auto_sync": SettingDef(
186 bool,
187 nullable=False,
188 group=SettingGroup.INGEST,
189 help_text="Run a sync before `lilbee ask` (disable on large static corpora)",
190 ),
191 "entity_extraction": SettingDef(
192 bool,
193 nullable=False,
194 group=SettingGroup.INGEST,
195 help_text="Extract typed entities automatically at sync (schema induced on first run)",
196 ),
197 "semantic_chunking": SettingDef(
198 bool,
199 nullable=False,
200 group=SettingGroup.INGEST,
201 help_text=(
202 "Opt-in topic-aware chunker (default off; may fragment numbered procedures) "
203 "(changes invalidate the index)"
204 ),
205 ),
206 "topic_threshold": SettingDef(
207 float,
208 nullable=False,
209 group=SettingGroup.INGEST,
210 help_text=(
211 "Topic-boundary similarity threshold, 0.0-1.0, used when semantic chunking "
212 "is on (changes invalidate the index)"
213 ),
214 advanced=True,
215 ),
216 "token_sizing": SettingDef(
217 bool,
218 nullable=False,
219 group=SettingGroup.INGEST,
220 help_text=(
221 "Size chunks by real embedder tokens, not chars; on by itself when the "
222 "embedder's window is below the character budget (changes invalidate the index)"
223 ),
224 ),
225 "table_extraction": SettingDef(
226 bool,
227 nullable=False,
228 group=SettingGroup.INGEST,
229 help_text="Index each extracted table as its own chunk (changes invalidate the index)",
230 ),
231 "layout_detection": SettingDef(
232 bool,
233 nullable=False,
234 group=SettingGroup.INGEST,
235 help_text=(
236 "Layout-aware PDF extraction: reading order plus header/footer "
237 "stripping (changes invalidate the index)"
238 ),
239 ),
240 "table_model": SettingDef(
241 str,
242 nullable=False,
243 group=SettingGroup.INGEST,
244 help_text=(
245 "Table structure model used when layout detection is on: slanet_auto "
246 "(docling-parity default), other slanet variants, tatr, or disabled "
247 "(changes invalidate the index)"
248 ),
249 advanced=True,
250 ),
251 "batch_extraction": SettingDef(
252 bool,
253 nullable=False,
254 group=SettingGroup.INGEST,
255 help_text="Coalesce concurrent extractions into one xberg batch call",
256 ),
257 "batch_extraction_size": SettingDef(
258 int,
259 nullable=False,
260 group=SettingGroup.INGEST,
261 help_text="Max files per extract_batch call when batch extraction is on",
262 advanced=True,
263 ),
264 "analyze_max_files": SettingDef(
265 int,
266 nullable=False,
267 group=SettingGroup.INGEST,
268 help_text=(
269 "Most documents lilbee analyze reads; more are sampled evenly down to this many."
270 " Code, image and archive files are counted, not read. Raise it to cover more"
271 " documents for a slower run. The default is not yet measured on large corpora"
272 ),
273 ),
274 "extraction_threads": SettingDef(
275 int,
276 nullable=False,
277 group=SettingGroup.INGEST,
278 help_text=(
279 "Threads xberg uses for PDF rendering, OCR and layout models, and the"
280 " most Tesseract OCR sessions that run at once (0 = auto, half the"
281 " available cores). Takes full effect after a restart."
282 ),
283 advanced=True,
284 ),
285 "embedding_model": SettingDef(
286 str,
287 nullable="embedding_model" in CLEARABLE_MODEL_FIELDS,
288 writable=False,
289 group=SettingGroup.MODELS,
290 help_text="Model used to embed document chunks",
291 ),
292 "reranker_model": SettingDef(
293 str,
294 nullable="reranker_model" in CLEARABLE_MODEL_FIELDS,
295 writable=False,
296 group=SettingGroup.MODELS,
297 help_text="Cross-encoder model for result reranking (empty = off)",
298 ),
299 "reranker_type": SettingDef(
300 str,
301 nullable=False,
302 group=SettingGroup.MODELS,
303 help_text=(
304 "Reranker serving mode: auto (detect cross-encoder vs LLM by model), "
305 "cross_encoder, or llm"
306 ),
307 advanced=True,
308 ),
309 "reranker_prompt": SettingDef(
310 str,
311 nullable=False,
312 group=SettingGroup.MODELS,
313 help_text="Relevance prompt for LLM rerankers (blank uses the built-in template)",
314 ),
315 "include_uncensored": SettingDef(
316 bool,
317 nullable=False,
318 group=SettingGroup.MODELS,
319 help_text=(
320 "Recommend safety-stripped models in Picks and Discover"
321 " (browse and search always list them)"
322 ),
323 ),
324 "temperature": SettingDef(
325 float,
326 nullable=True,
327 group=SettingGroup.GENERATION,
328 help_text="Sampling temperature (higher = more creative)",
329 ),
330 "top_p": SettingDef(
331 float,
332 nullable=True,
333 group=SettingGroup.GENERATION,
334 help_text="Nucleus sampling cutoff probability",
335 ),
336 "top_k_sampling": SettingDef(
337 int,
338 nullable=True,
339 group=SettingGroup.GENERATION,
340 help_text="Top-K sampling: number of tokens to consider",
341 ),
342 "repeat_penalty": SettingDef(
343 float,
344 nullable=True,
345 group=SettingGroup.GENERATION,
346 help_text="Penalty for repeating tokens",
347 ),
348 "num_ctx": SettingDef(
349 int,
350 nullable=True,
351 group=SettingGroup.GENERATION,
352 help_text=(
353 "Context window size in tokens. Leave empty to size automatically "
354 "(aims for chat_n_ctx_target, ceiling at num_ctx_max or training_ctx)."
355 ),
356 ),
357 "num_ctx_max": SettingDef(
358 int,
359 nullable=True,
360 group=SettingGroup.GENERATION,
361 help_text=(
362 "Explicit ceiling for the dynamic context picker. Leave empty to "
363 "use the model's training_ctx from GGUF metadata as the only "
364 "ceiling. Set to cap below training_ctx (saves KV memory)."
365 ),
366 advanced=True,
367 ),
368 "chat_n_ctx_target": SettingDef(
369 int,
370 nullable=False,
371 group=SettingGroup.GENERATION,
372 help_text=(
373 "Working context the dynamic picker aims for. Fits a RAG turn "
374 "with reasoning headroom; raise for long-document chat."
375 ),
376 advanced=True,
377 ),
378 "flash_attention": SettingDef(
379 bool,
380 nullable=True,
381 group=SettingGroup.GENERATION,
382 help_text=(
383 "Flash attention. Empty (auto) enables it; disable for backends or "
384 "models where it misbehaves. Resolves the V-cache padding warning "
385 "on models with uneven per-layer V dims. On Vulkan and SYCL lilbee "
386 "sends auto, not on, even when this is true."
387 ),
388 advanced=True,
389 ),
390 "kv_cache_type": SettingDef(
391 str,
392 nullable=False,
393 group=SettingGroup.GENERATION,
394 help_text=(
395 "KV cache element type for both K and V. q8_0 / q4_0 halve or quarter "
396 "cache memory and apply only where lilbee sends flash attention on. On "
397 "Vulkan and SYCL lilbee sends auto, so the cache is f16 there; it is "
398 "also f16 when flash attention is off."
399 ),
400 advanced=True,
401 ),
402 "n_gpu_layers": SettingDef(
403 int,
404 nullable=True,
405 group=SettingGroup.GENERATION,
406 help_text=(
407 "Layers to offload to GPU. Empty = all (recommended), 0 = CPU only, "
408 "positive int = partial offload for tight VRAM."
409 ),
410 advanced=True,
411 ),
412 "cpu_moe": SettingDef(
413 bool,
414 nullable=False,
415 group=SettingGroup.GENERATION,
416 help_text=(
417 "Keep a mixture-of-experts model's expert weights in system memory so "
418 "it fits a smaller GPU. No effect on dense models."
419 ),
420 advanced=True,
421 ),
422 "n_cpu_moe": SettingDef(
423 int,
424 nullable=True,
425 group=SettingGroup.GENERATION,
426 help_text=(
427 "Offload only the first N layers' experts to system memory. Takes "
428 "precedence over the offload-everything setting; smaller N stays faster."
429 ),
430 advanced=True,
431 ),
432 "fast_model_downloads": SettingDef(
433 bool,
434 nullable=False,
435 group=SettingGroup.GENERATION,
436 help_text=(
437 "Warning: Hugging Face states this mode uses all available bandwidth "
438 "and CPU cores, and buffers far more of the download in memory. "
439 "Faster on a fast connection, at the cost of everything else running "
440 "on the machine. Leave it off unless the machine can spare that. "
441 "Requires a restart to take effect."
442 ),
443 advanced=True,
444 ),
445 "gpu_devices": SettingDef(
446 str,
447 nullable=True,
448 group=SettingGroup.GENERATION,
449 help_text=(
450 "Restrict llama.cpp to specific GPU indexes on dual-GPU machines "
451 "(e.g. NVIDIA dGPU + integrated). Comma-separated, like '0' or '0,1'. "
452 "Applies to Vulkan, CUDA, and ROCm. Requires a restart to take effect."
453 ),
454 advanced=True,
455 ),
456 "seed": SettingDef(
457 int,
458 nullable=True,
459 group=SettingGroup.GENERATION,
460 help_text=(
461 "Random seed for reproducible output. On a local engine, the search steps that "
462 "call the model (query expansion, hypothetical answers, history rewrite) use it "
463 "too. A hosted provider can ignore it"
464 ),
465 advanced=True,
466 ),
467 "rag_system_prompt": SettingDef(
468 str,
469 nullable=False,
470 render=RenderStyle.MULTILINE,
471 group=SettingGroup.GENERATION,
472 help_text="System prompt sent when answering with retrieved context",
473 ),
474 "general_system_prompt": SettingDef(
475 str,
476 nullable=False,
477 render=RenderStyle.MULTILINE,
478 group=SettingGroup.GENERATION,
479 help_text="System prompt sent when there are no documents to ground the answer",
480 ),
481 "chat_compaction": SettingDef(
482 bool,
483 nullable=False,
484 group=SettingGroup.GENERATION,
485 help_text=(
486 "Off (default): when a chat outgrows the model's context window the oldest "
487 "turns are dropped. They stay on screen but the model stops seeing them, and "
488 "the context chip by the prompt shows the window filling. Costs nothing. "
489 "On: those turns are condensed into a short summary the model keeps reading, "
490 "so it still knows roughly what was said. That costs one extra model call each "
491 "time it fires, pausing the reply for a few seconds on a GPU and considerably "
492 "longer on a CPU-only machine. Worth turning on if your hardware is quick."
493 ),
494 ),
495 "sessions_enabled": SettingDef(
496 bool,
497 nullable=False,
498 group=SettingGroup.GENERATION,
499 help_text=(
500 "On (default): conversations are saved automatically, and you can list, "
501 "resume, rename, and delete them from the Sessions drawer (ctrl+o), the "
502 "Sessions tab, and the /sessions command. Off: nothing is written to disk, "
503 "the ctrl+o binding leaves the footer, and opening the Sessions view shows a "
504 "notice that sessions are turned off. Turn it off if you would rather your "
505 "chats not persist. Covers the TUI, the HTTP server, and the CLI; agent "
506 "sessions have their own setting."
507 ),
508 ),
509 "mcp_sessions_enabled": SettingDef(
510 bool,
511 nullable=False,
512 group=SettingGroup.GENERATION,
513 help_text=(
514 "Off (default): the session tools are not offered over MCP, and a connected "
515 "agent cannot create or read agent sessions. On: an agent can keep its own "
516 "saved conversations, separate from yours. Most agent hosts already track "
517 "their own history, and the tools cost context on every request, so this "
518 "stays off unless you want an agent owning conversations."
519 ),
520 ),
521 "mcp_profiles_enabled": SettingDef(
522 bool,
523 nullable=False,
524 group=SettingGroup.GENERATION,
525 help_text=(
526 "Off (default): the profile tools are not offered over MCP. On: a connected "
527 "agent can list, compare, apply, save and manage profiles. The tools cost "
528 "context on every request, so this stays off unless you want an agent tuning "
529 "a project's settings through profiles."
530 ),
531 ),
532 "chat_mode": SettingDef(
533 str,
534 nullable=False,
535 group=SettingGroup.GENERATION,
536 help_text="search runs every chat turn through document retrieval; chat skips it",
537 ),
538 "top_k": SettingDef(
539 int,
540 nullable=False,
541 group=SettingGroup.RETRIEVAL,
542 help_text="Number of chunks returned by search",
543 ),
544 "rerank_candidates": SettingDef(
545 int,
546 nullable=False,
547 group=SettingGroup.RETRIEVAL,
548 help_text="Candidate pool size for reranking",
549 advanced=True,
550 ),
551 "rerank_blend": SettingDef(
552 bool,
553 nullable=False,
554 group=SettingGroup.RETRIEVAL,
555 help_text="Blend reranker scores with retrieval fusion (off = pure reranker order)",
556 ),
557 "rerank_min_score": SettingDef(
558 float,
559 nullable=True,
560 group=SettingGroup.RETRIEVAL,
561 help_text="Drop candidates whose raw reranker score is below this (unset = off)",
562 advanced=True,
563 ),
564 "show_reasoning": SettingDef(
565 bool,
566 nullable=False,
567 group=SettingGroup.DISPLAY,
568 help_text="Show model reasoning/thinking tokens in output",
569 ),
570 "completions_reasoning": SettingDef(
571 str,
572 nullable=False,
573 group=SettingGroup.GENERATION,
574 help_text=(
575 "How /v1/chat/completions presents thinking: separate "
576 "reasoning_content field, inline thinking as plain content text, "
577 "or off (ask the model not to think)"
578 ),
579 advanced=True,
580 ),
581 "messages_reasoning": SettingDef(
582 str,
583 nullable=False,
584 group=SettingGroup.GENERATION,
585 help_text=(
586 "How /v1/messages presents thinking: separate thinking block, "
587 "inline thinking as plain answer text, or off (ask the model not "
588 "to think)"
589 ),
590 advanced=True,
591 ),
592 "lilbee_name": SettingDef(
593 str,
594 nullable=False,
595 group=SettingGroup.DISPLAY,
596 help_text=(
597 "Human-readable label for this lilbee, shown in the status bar. "
598 "Empty falls back to 'global' for the platform default dir or "
599 "to the project path (~-substituted and left-truncated)."
600 ),
601 ),
602 "show_lilbee_path": SettingDef(
603 bool,
604 nullable=False,
605 group=SettingGroup.DISPLAY,
606 help_text=(
607 "Show the full absolute path in the status bar: expands 'global' "
608 "to its on-disk path and skips ~ substitution / truncation."
609 ),
610 ),
611 "theme": SettingDef(
612 str,
613 nullable=False,
614 group=SettingGroup.DISPLAY,
615 help_text="TUI color theme. Cycle with Ctrl+T; the active theme persists across sessions.",
616 choices=tuple(DARK_THEMES),
617 ),
618 "wiki": SettingDef(
619 bool,
620 nullable=False,
621 group=SettingGroup.WIKI,
622 help_text=(
623 "Enable the wiki layer (cited concept and entity pages). "
624 "GPU-heavy: a build spends one LLM call per source document, "
625 "so a large library takes hours. Enabling this generates nothing "
626 "on its own: you wikify explicitly, or turn on wiki_auto_update"
627 ),
628 ),
629 "wiki_auto_update": SettingDef(
630 bool,
631 nullable=False,
632 group=SettingGroup.WIKI,
633 help_text="Regenerate touched wiki pages after each sync (off: wikify explicitly)",
634 ),
635 "wiki_dir": SettingDef(
636 str,
637 nullable=False,
638 writable=False,
639 group=SettingGroup.WIKI,
640 help_text=(
641 "Directory under data_root where wiki pages live (set via env / config.toml only)"
642 ),
643 ),
644 "wiki_prune_raw": SettingDef(
645 bool,
646 nullable=False,
647 group=SettingGroup.WIKI,
648 help_text="Delete raw chunks after summarizing into the wiki",
649 ),
650 "wiki_embedding_faithfulness_threshold": SettingDef(
651 float,
652 nullable=False,
653 group=SettingGroup.WIKI,
654 help_text=(
655 "Minimum cosine similarity (0-1) between a generated page and "
656 "the mean of its source chunk vectors before publishing. "
657 "Pages below the threshold route to drafts/."
658 ),
659 advanced=True,
660 ),
661 "wiki_stale_citation_threshold": SettingDef(
662 float,
663 nullable=False,
664 group=SettingGroup.WIKI,
665 help_text="Fraction of stale citations before a page is flagged by wiki prune",
666 advanced=True,
667 ),
668 "wiki_drift_threshold": SettingDef(
669 float,
670 nullable=False,
671 group=SettingGroup.WIKI,
672 help_text="Max fraction of changed lines before regeneration requires review",
673 advanced=True,
674 ),
675 "wiki_clusterer": SettingDef(
676 str,
677 nullable=False,
678 group=SettingGroup.WIKI,
679 help_text="Synthesis clusterer backend (embedding or concepts)",
680 advanced=True,
681 ),
682 "wiki_entity_mode": SettingDef(
683 str,
684 nullable=False,
685 group=SettingGroup.WIKI,
686 help_text=(
687 "Entity extraction strategy. ner_entities (typed spaCy NER) is the "
688 "only implemented mode; the other values fall back to it with a warning"
689 ),
690 advanced=True,
691 ),
692 "wiki_entity_min_mentions": SettingDef(
693 int,
694 nullable=False,
695 group=SettingGroup.WIKI,
696 help_text="Minimum chunk mentions before an entity or concept gets its own page",
697 advanced=True,
698 ),
699 "wiki_stub_max_chunk_refs": SettingDef(
700 int,
701 nullable=False,
702 group=SettingGroup.WIKI,
703 help_text=(
704 "How many source chunks a page kept for lazy generation draws on. "
705 "Caps the browse index's size; already more than one page's context "
706 "budget admits, so raising it rarely changes what a page says"
707 ),
708 advanced=True,
709 ),
710 "wiki_ingest_update_cap": SettingDef(
711 int,
712 nullable=False,
713 group=SettingGroup.WIKI,
714 help_text=(
715 "Touched-page cap for auto-update after sync. "
716 "Beyond this count, run `lilbee wiki update` manually."
717 ),
718 advanced=True,
719 ),
720 "wiki_synthesis_prompt": SettingDef(
721 str,
722 nullable=False,
723 render=RenderStyle.FULL,
724 group=SettingGroup.WIKI,
725 help_text=(
726 "Prompt for cross-source synthesis pages. "
727 "Must keep {topic}, {source_list}, and {chunks_text}."
728 ),
729 ),
730 "wiki_entity_page_prompt": SettingDef(
731 str,
732 nullable=False,
733 render=RenderStyle.FULL,
734 group=SettingGroup.WIKI,
735 help_text=(
736 "Prompt for a single page generated on demand from one subject's "
737 "chunks across every source naming it. "
738 "Must keep {topic}, {source_list}, and {chunks_text}."
739 ),
740 ),
741 "wiki_entity_batch_prompt": SettingDef(
742 str,
743 nullable=False,
744 render=RenderStyle.FULL,
745 group=SettingGroup.WIKI,
746 help_text=(
747 "Prompt for the per-source batched call. "
748 "Must keep {source}, {entity_list}, {chunks_text}, and {concept_instruction}."
749 ),
750 ),
751 "wiki_extract_concepts": SettingDef(
752 bool,
753 nullable=False,
754 group=SettingGroup.WIKI,
755 help_text=(
756 "Whether the per-source batched call asks the LLM to curate concept pages "
757 "alongside the pre-extracted entity list."
758 ),
759 advanced=True,
760 ),
761 "wiki_batch_min_chunks": SettingDef(
762 int,
763 nullable=False,
764 group=SettingGroup.WIKI,
765 help_text=(
766 "Minimum chunks a source must contribute before its batched call includes "
767 "concept curation. Sources below the floor skip the concept-curation "
768 "instruction; sources with zero entities AND below the floor are skipped entirely."
769 ),
770 advanced=True,
771 ),
772 "wiki_clusterer_k": SettingDef(
773 int,
774 nullable=False,
775 group=SettingGroup.WIKI,
776 help_text="Mutual-kNN neighborhood size for the clusterer (0 = auto)",
777 advanced=True,
778 ),
779 "memory_enabled": SettingDef(
780 bool,
781 nullable=False,
782 group=SettingGroup.MEMORY,
783 help_text="Master switch for long-term chat memory (off by default)",
784 ),
785 "memory_auto_extract": SettingDef(
786 bool,
787 nullable=False,
788 group=SettingGroup.MEMORY,
789 help_text="Auto-save durable facts and preferences from each TUI turn (needs memory on)",
790 ),
791 "memory_top_k": SettingDef(
792 int,
793 nullable=False,
794 group=SettingGroup.MEMORY,
795 help_text="Maximum facts recalled into context per turn",
796 ),
797 "memory_max_distance": SettingDef(
798 float,
799 nullable=False,
800 group=SettingGroup.MEMORY,
801 help_text="Recall cutoff distance, 0.0-1.0 (lower is stricter)",
802 advanced=True,
803 ),
804 "memory_token_budget": SettingDef(
805 int,
806 nullable=False,
807 group=SettingGroup.MEMORY,
808 help_text="Token cap on the recalled-memory block added to the prompt",
809 advanced=True,
810 ),
811 "memory_max_per_owner": SettingDef(
812 int,
813 nullable=False,
814 group=SettingGroup.MEMORY,
815 help_text="Soft cap before the oldest memories are evicted",
816 hidden=True,
817 ),
818 "memory_dedup_distance": SettingDef(
819 float,
820 nullable=False,
821 group=SettingGroup.MEMORY,
822 help_text="Near-duplicate distance below which a new memory updates the old",
823 hidden=True,
824 ),
825 "crawl_max_depth": SettingDef(
826 int,
827 nullable=True,
828 group=SettingGroup.CRAWLING,
829 help_text="Optional recursion-depth cap, 0 to 100 (blank = no cap; per-crawl values win)",
830 ),
831 "crawl_render_mode": SettingDef(
832 str,
833 nullable=False,
834 group=SettingGroup.CRAWLING,
835 help_text=(
836 "How crawls fetch pages. http = lightweight, no browser (default, best "
837 "for static and server-rendered sites). browser = Chromium with "
838 "JavaScript enabled for client-rendered sites, at much higher memory cost."
839 ),
840 ),
841 "crawl_browser_extra_args": SettingDef(
842 list,
843 nullable=False,
844 group=SettingGroup.CRAWLING,
845 help_text=(
846 "Browser mode: extra Chromium launch flags, one per line. "
847 "Defaults trim shared-memory and GPU use."
848 ),
849 advanced=True,
850 ),
851 "crawl_browser_extra_wait_ms": SettingDef(
852 int,
853 nullable=False,
854 group=SettingGroup.CRAWLING,
855 help_text=(
856 "Browser mode: milliseconds to wait after a page has loaded and stopped changing, "
857 "for sites that draw their text later. Raise it when a page's text is missing; "
858 "0 = no extra wait, at most 600000 (ten minutes). http mode ignores it."
859 ),
860 ),
861 "crawl_login_host": SettingDef(
862 str,
863 nullable=False,
864 group=SettingGroup.CRAWLING,
865 help_text=(
866 "The one host that gets the stored crawl login, as a bare host name such as "
867 "wiki.example.com, with no scheme or path. The match ignores the "
868 "scheme and the port of the crawled URL. A crawl of any other host sends "
869 "no stored login. "
870 "Blank = no stored login"
871 ),
872 ),
873 "crawl_authorization": SettingDef(
874 str,
875 nullable=False,
876 group=SettingGroup.CRAWLING,
877 secret=True,
878 help_text=(
879 "Authorization header value sent to the crawl login host. Use it or a "
880 "username, not both. Stored in plain text in config.toml"
881 ),
882 ),
883 "crawl_username": SettingDef(
884 str,
885 nullable=False,
886 group=SettingGroup.CRAWLING,
887 help_text="Basic-auth username sent to the crawl login host. Blank = none",
888 ),
889 "crawl_password": SettingDef(
890 str,
891 nullable=False,
892 group=SettingGroup.CRAWLING,
893 secret=True,
894 help_text=(
895 "Basic-auth password sent with the crawl login username. Stored in plain "
896 "text in config.toml"
897 ),
898 ),
899 "crawl_cookie": SettingDef(
900 str,
901 nullable=False,
902 group=SettingGroup.CRAWLING,
903 secret=True,
904 help_text=(
905 "Cookie header value sent to the crawl login host. Stored in plain text in config.toml"
906 ),
907 ),
908 "crawl_user_agent": SettingDef(
909 str,
910 nullable=False,
911 group=SettingGroup.CRAWLING,
912 help_text=(
913 "User agent of every crawl request, for a site that answers some clients "
914 "differently. Blank = no override: browser mode sends Chrome's own, "
915 "http mode sends crawlberg's, and the sitemap lookup sends its HTTP "
916 "client's. A Chrome user agent also sets the matching "
917 "client hints in browser mode."
918 ),
919 ),
920 "crawl_respect_robots_txt": SettingDef(
921 bool,
922 nullable=False,
923 group=SettingGroup.CRAWLING,
924 help_text=(
925 "Skip the pages a site's robots.txt disallows, and every page of a site "
926 "whose robots.txt cannot be read (a server error, a rate limit or no answer). Turn off "
927 "to crawl a site you own or have permission to crawl in full."
928 ),
929 ),
930 "crawl_max_pages": SettingDef(
931 int,
932 nullable=True,
933 group=SettingGroup.CRAWLING,
934 help_text="Optional global cap on total pages per crawl, 1 to 10000000 (blank = no cap).",
935 ),
936 "crawl_safety_max_pages": SettingDef(
937 int,
938 nullable=False,
939 group=SettingGroup.CRAWLING,
940 help_text="Default page bound for an unbounded crawl, so a hostile site cannot "
941 "exhaust the disk, 1 to 10000000. An explicit max-pages overrides it; raise this "
942 "to crawl larger sites unbounded.",
943 advanced=True,
944 ),
945 "crawl_timeout": SettingDef(
946 int,
947 nullable=False,
948 group=SettingGroup.CRAWLING,
949 help_text="Per-page fetch timeout in seconds, 1 to 3600",
950 advanced=True,
951 ),
952 "crawl_sync_interval": SettingDef(
953 int,
954 nullable=False,
955 group=SettingGroup.CRAWLING,
956 help_text="Seconds between periodic re-syncs during a crawl (0 = sync only at end)",
957 ),
958 "crawl_mean_delay": SettingDef(
959 float,
960 nullable=False,
961 group=SettingGroup.CRAWLING,
962 help_text="Seconds each concurrent request waits between requests to a site, 0 to 3600",
963 advanced=True,
964 ),
965 "crawl_max_delay_range": SettingDef(
966 float,
967 nullable=False,
968 group=SettingGroup.CRAWLING,
969 help_text="Random jitter (seconds) added on top of mean delay, 0 to 3600",
970 advanced=True,
971 ),
972 "crawl_concurrent_requests": SettingDef(
973 int,
974 nullable=False,
975 group=SettingGroup.CRAWLING,
976 help_text="Concurrent in-flight URLs within one crawl, 1 to 256",
977 advanced=True,
978 ),
979 "crawl_retry_on_rate_limit": SettingDef(
980 bool,
981 nullable=False,
982 group=SettingGroup.CRAWLING,
983 help_text="Retry a page that answers HTTP 429 or 503. Off: no retries",
984 ),
985 "crawl_retry_base_delay_min": SettingDef(
986 float,
987 nullable=False,
988 group=SettingGroup.CRAWLING,
989 help_text=(
990 "Base delay range, low end (seconds, 0 to 3600). The first retry waits halfway"
991 " between the low and high end, and each later retry waits twice as long"
992 ),
993 advanced=True,
994 ),
995 "crawl_retry_base_delay_max": SettingDef(
996 float,
997 nullable=False,
998 group=SettingGroup.CRAWLING,
999 help_text=(
1000 "Base delay range, high end (seconds, 0 to 3600). The first retry waits halfway"
1001 " between the low and high end, and each later retry waits twice as long"
1002 ),
1003 advanced=True,
1004 ),
1005 "crawl_retry_max_backoff": SettingDef(
1006 float,
1007 nullable=False,
1008 group=SettingGroup.CRAWLING,
1009 help_text="Upper bound on any single backoff wait (seconds, 0 to 3600)",
1010 advanced=True,
1011 ),
1012 "crawl_retry_max_attempts": SettingDef(
1013 int,
1014 nullable=False,
1015 group=SettingGroup.CRAWLING,
1016 help_text="Retries per page. A crawl makes at most 20, whatever the value",
1017 advanced=True,
1018 ),
1019 "crawl_exclude_patterns": SettingDef(
1020 list,
1021 nullable=False,
1022 group=SettingGroup.CRAWLING,
1023 validate_regex=True,
1024 help_text=(
1025 "Regex patterns that skip URLs at link-discovery time during "
1026 "recursive crawls. One per line."
1027 ),
1028 ),
1029 "openrouter_api_key": SettingDef(
1030 str,
1031 nullable=False,
1032 group=SettingGroup.API_KEYS,
1033 secret=True,
1034 help_text="OpenRouter API key (enables frontier models in chat picker)",
1035 ),
1036 "gemini_api_key": SettingDef(
1037 str,
1038 nullable=False,
1039 group=SettingGroup.API_KEYS,
1040 secret=True,
1041 help_text="Google Gemini API key (enables frontier models in chat picker)",
1042 ),
1043 "anthropic_api_key": SettingDef(
1044 str,
1045 nullable=False,
1046 group=SettingGroup.API_KEYS,
1047 secret=True,
1048 help_text="Anthropic API key (enables frontier models in chat picker)",
1049 ),
1050 "openai_api_key": SettingDef(
1051 str,
1052 nullable=False,
1053 group=SettingGroup.API_KEYS,
1054 secret=True,
1055 help_text="OpenAI API key (enables frontier models in chat picker)",
1056 ),
1057 "mistral_api_key": SettingDef(
1058 str,
1059 nullable=False,
1060 group=SettingGroup.API_KEYS,
1061 secret=True,
1062 help_text="Mistral API key (enables frontier models in chat picker)",
1063 ),
1064 "deepseek_api_key": SettingDef(
1065 str,
1066 nullable=False,
1067 group=SettingGroup.API_KEYS,
1068 secret=True,
1069 help_text="DeepSeek API key (enables frontier models in chat picker)",
1070 ),
1071 "llm_api_key": SettingDef(
1072 str,
1073 nullable=False,
1074 group=SettingGroup.API_KEYS,
1075 secret=True,
1076 help_text=(
1077 "API key for the remote OpenAI-compatible endpoint and local model servers "
1078 "such as LM Studio. Ollama and hosted models never get this key; hosted "
1079 "models use their own provider key"
1080 ),
1081 ),
1082 "hf_token": SettingDef(
1083 str,
1084 nullable=False,
1085 group=SettingGroup.SYSTEM,
1086 secret=True,
1087 help_text=(
1088 "HuggingFace access token. Avoids the unauthenticated download "
1089 "rate limit and unlocks gated repos. Stored in plain text in "
1090 "config.toml. Env vars (LILBEE_HF_TOKEN, HF_TOKEN) override."
1091 ),
1092 ),
1093 "chunk_size": SettingDef(
1094 int,
1095 nullable=False,
1096 group=SettingGroup.INGEST,
1097 help_text="Document chunk size in tokens (changes invalidate the index)",
1098 ),
1099 "chunk_overlap": SettingDef(
1100 int,
1101 nullable=False,
1102 group=SettingGroup.INGEST,
1103 help_text="Tokens of overlap between adjacent chunks (preserves context across boundaries)",
1104 ),
1105 CHUNK_LIMIT_KEY: SettingDef(
1106 int,
1107 nullable=False,
1108 group=SettingGroup.INGEST,
1109 help_text=(
1110 "Most chunks one file can add to the index; a file over the limit is skipped, "
1111 "not embedded (0 = no limit). Raise it for a long document such as a "
1112 "thousand-page manual at a small chunk_size, then retry skipped files"
1113 ),
1114 ),
1115 "ocr_language": SettingDef(
1116 list,
1117 nullable=False,
1118 group=SettingGroup.INGEST,
1119 help_text="Tesseract OCR languages when no vision model is set; '+'-join, e.g. eng+deu",
1120 ),
1121 "ocr_strategy": SettingDef(
1122 str,
1123 nullable=False,
1124 group=SettingGroup.OCR_TUNING,
1125 help_text=(
1126 "PDF pages to OCR: auto (each page without usable text: its text layer is"
1127 " missing, garbled or under 32 non-blank characters) or scanned_pages (also every"
1128 " page that looks like a scan, e.g. a scanned page with a hidden text layer)"
1129 ),
1130 advanced=True,
1131 ),
1132 "ocr_scan_confidence": SettingDef(
1133 float,
1134 nullable=False,
1135 group=SettingGroup.OCR_TUNING,
1136 help_text=(
1137 "How sure xberg must be that a page is a scan before it OCRs it (0-1)."
1138 " Applies only when ocr_strategy is scanned_pages. Lower it to 0.5 to"
1139 " also OCR slides with a full-page background image"
1140 ),
1141 advanced=True,
1142 ),
1143 "force_ocr_pages": SettingDef(
1144 list,
1145 nullable=False,
1146 group=SettingGroup.OCR_TUNING,
1147 help_text=(
1148 "PDF page numbers that lilbee OCRs in every PDF, comma-separated (e.g. 1,3)"
1149 " or one per line"
1150 ),
1151 advanced=True,
1152 ),
1153 "worker_pool_eager_start": SettingDef(
1154 bool,
1155 nullable=False,
1156 group=SettingGroup.INGEST,
1157 help_text=(
1158 "Spawn every configured role server at TUI startup instead of on first use. "
1159 "Trades cold-start time per role for first-call latency"
1160 ),
1161 advanced=True,
1162 ),
1163 "keep_engine_warm": SettingDef(
1164 bool,
1165 nullable=False,
1166 group=SettingGroup.SYSTEM,
1167 help_text=(
1168 "Keep models loaded while lilbee runs and the engine up for warm launches; "
1169 "after every lilbee process has exited, models unload after the idle time. "
1170 "Off stops the engine on last exit unless another lilbee asked to keep it. "
1171 "A change applies at the next engine start"
1172 ),
1173 ),
1174 "engine_idle_ttl_minutes": SettingDef(
1175 int,
1176 nullable=False,
1177 group=SettingGroup.SYSTEM,
1178 help_text=(
1179 "Idle minutes before the engine unloads its weights; with keep_engine_warm, "
1180 "counted from the last lilbee exit; 0 keeps them loaded. "
1181 "A change applies at the next engine start"
1182 ),
1183 advanced=True,
1184 ),
1185 "agent_mcp_enabled": SettingDef(
1186 bool,
1187 nullable=False,
1188 group=SettingGroup.SYSTEM,
1189 help_text=(
1190 "Register lilbee's MCP search tool into agent launchers (opencode, hermes). "
1191 "Disable to bring your own MCP servers; lilbee stays the model provider"
1192 ),
1193 ),
1194 "max_tokens": SettingDef(
1195 int,
1196 nullable=True,
1197 group=SettingGroup.GENERATION,
1198 help_text="Hard cap on generated tokens per response (blank = the model's context window)",
1199 ),
1200 "max_reasoning_chars": SettingDef(
1201 int,
1202 nullable=False,
1203 group=SettingGroup.GENERATION,
1204 help_text=(
1205 "Maximum reasoning characters before lilbee forces the model to answer "
1206 "(0 = unlimited; per-model overrides apply on top)"
1207 ),
1208 advanced=True,
1209 ),
1210 "model_keep_alive": SettingDef(
1211 int,
1212 nullable=False,
1213 group=SettingGroup.GENERATION,
1214 help_text="Seconds the loaded model stays warm between calls (0 = unload immediately)",
1215 advanced=True,
1216 ),
1217 "gpu_memory_fraction": SettingDef(
1218 float,
1219 nullable=False,
1220 group=SettingGroup.GENERATION,
1221 help_text="Fraction of GPU memory the model is allowed to claim (0.1-1.0)",
1222 advanced=True,
1223 ),
1224 "usable_vram_fraction": SettingDef(
1225 float,
1226 nullable=False,
1227 group=SettingGroup.GENERATION,
1228 help_text=(
1229 "Share of a GPU placement may fill, leaving room for fragmentation and driver "
1230 "overhead (0.5-1.0). Raise it if a model that should fit is being refused; "
1231 "lower it if loads fail near the top of the card."
1232 ),
1233 advanced=True,
1234 ),
1235 "system_memory_reserve_gb": SettingDef(
1236 float,
1237 nullable=False,
1238 group=SettingGroup.GENERATION,
1239 help_text=(
1240 "RAM held back for the OS in GiB when serving from system memory (no discrete "
1241 "GPU). Capped at a quarter of total RAM either way."
1242 ),
1243 advanced=True,
1244 ),
1245 "embed_replicas": SettingDef(
1246 int,
1247 nullable=False,
1248 group=SettingGroup.GENERATION,
1249 help_text="Embedding servers in parallel (0 = auto, one per GPU; positive pins the count)",
1250 advanced=True,
1251 ),
1252 "vision_replicas": SettingDef(
1253 int,
1254 nullable=False,
1255 group=SettingGroup.OCR_TUNING,
1256 help_text="Vision OCR servers in parallel (0 = auto, one per GPU; positive pins the count)",
1257 advanced=True,
1258 ),
1259 "candidate_multiplier": SettingDef(
1260 int,
1261 nullable=False,
1262 group=SettingGroup.RETRIEVAL,
1263 help_text="Candidate-pool multiplier over top_k before reranking",
1264 advanced=True,
1265 ),
1266 "title_search": SettingDef(
1267 bool,
1268 nullable=False,
1269 group=SettingGroup.RETRIEVAL,
1270 help_text="Match queries against document titles as a third hybrid-search arm",
1271 ),
1272 "title_search_weight": SettingDef(
1273 float,
1274 nullable=False,
1275 group=SettingGroup.RETRIEVAL,
1276 help_text="Title arm weight in rank fusion (1.0 = equal voice with the other arms)",
1277 advanced=True,
1278 ),
1279 "lexical_fusion_weight": SettingDef(
1280 float,
1281 nullable=False,
1282 group=SettingGroup.RETRIEVAL,
1283 help_text="BM25 arm weight in fusion (1.0 = equal to vector; lower to favor dense)",
1284 advanced=True,
1285 ),
1286 "adaptive_fusion": SettingDef(
1287 bool,
1288 nullable=False,
1289 group=SettingGroup.RETRIEVAL,
1290 help_text="Scale the BM25 weight per query by vector-arm confidence, not a fixed value",
1291 ),
1292 "adaptive_fusion_margin": SettingDef(
1293 float,
1294 nullable=False,
1295 group=SettingGroup.RETRIEVAL,
1296 help_text="Vector-similarity margin at which adaptive fusion fully silences the BM25 arm",
1297 advanced=True,
1298 ),
1299 "filter_structural_chunks": SettingDef(
1300 bool,
1301 nullable=False,
1302 group=SettingGroup.RETRIEVAL,
1303 help_text="Drop tables-of-contents and classification-banner cover pages from results",
1304 ),
1305 "fts_language": SettingDef(
1306 str,
1307 nullable=False,
1308 group=SettingGroup.RETRIEVAL,
1309 help_text="Stemmer/stop-word language for BM25 indexes (rebuild to apply)",
1310 ),
1311 "embed_titles": SettingDef(
1312 bool,
1313 nullable=False,
1314 group=SettingGroup.RETRIEVAL,
1315 help_text="Prefix document titles to chunk embeddings (rebuild to apply)",
1316 ),
1317 "contextual_enrichment": SettingDef(
1318 bool,
1319 nullable=False,
1320 group=SettingGroup.RETRIEVAL,
1321 help_text="LLM context sentence per chunk embedding (slow ingest; rebuild to apply)",
1322 ),
1323 "history_rewrite": SettingDef(
1324 bool,
1325 nullable=False,
1326 group=SettingGroup.RETRIEVAL,
1327 help_text=(
1328 "Rewrite a follow-up that refers to earlier turns into a standalone "
1329 "retrieval query (one extra chat-model call on those turns)"
1330 ),
1331 ),
1332 "intent_routing": SettingDef(
1333 bool,
1334 nullable=False,
1335 group=SettingGroup.RETRIEVAL,
1336 help_text="Route document-name lookups to exact retrieval, count questions to a scan",
1337 ),
1338 "intent_llm": SettingDef(
1339 bool,
1340 nullable=False,
1341 group=SettingGroup.RETRIEVAL,
1342 help_text=(
1343 "Classify count questions with the chat model when the fast patterns "
1344 "miss (covers phrasing variants and other languages; adds one short "
1345 "LLM call to those turns)"
1346 ),
1347 ),
1348 "ann_index_threshold": SettingDef(
1349 int,
1350 nullable=False,
1351 group=SettingGroup.RETRIEVAL,
1352 help_text="Chunk count to start building an ANN vector index (0 = always flat search)",
1353 advanced=True,
1354 ),
1355 "max_distance": SettingDef(
1356 float,
1357 nullable=False,
1358 group=SettingGroup.RETRIEVAL,
1359 help_text="Maximum vector distance for retrieval matches (lower = stricter)",
1360 ),
1361 "min_relevance_score": SettingDef(
1362 float,
1363 nullable=False,
1364 group=SettingGroup.RETRIEVAL,
1365 help_text="Minimum RRF relevance score for hybrid search results (0.0 = no filter)",
1366 advanced=True,
1367 ),
1368 "max_context_sources": SettingDef(
1369 int,
1370 nullable=False,
1371 group=SettingGroup.RETRIEVAL,
1372 help_text="Maximum unique sources contributing chunks to a single answer",
1373 ),
1374 "neighbor_expansion": SettingDef(
1375 int,
1376 nullable=False,
1377 group=SettingGroup.RETRIEVAL,
1378 help_text="Adjacent chunks merged into each retrieved passage per side (0 = off)",
1379 advanced=True,
1380 ),
1381 "diversity_max_per_source": SettingDef(
1382 int,
1383 nullable=False,
1384 group=SettingGroup.RETRIEVAL,
1385 help_text=(
1386 "Maximum chunks accepted from any one source (caps source dominance). "
1387 "A query that names one document is not capped."
1388 ),
1389 advanced=True,
1390 ),
1391 "mmr_lambda": SettingDef(
1392 float,
1393 nullable=False,
1394 group=SettingGroup.RETRIEVAL,
1395 help_text=(
1396 "MMR lambda balancing relevance vs diversity (0 = max diversity, 1 = max relevance)"
1397 ),
1398 advanced=True,
1399 ),
1400 "temporal_filtering": SettingDef(
1401 bool,
1402 nullable=False,
1403 group=SettingGroup.RETRIEVAL,
1404 help_text="Detect temporal queries and bias retrieval toward recent chunks",
1405 ),
1406 "hyde": SettingDef(
1407 bool,
1408 nullable=False,
1409 group=SettingGroup.RETRIEVAL,
1410 help_text="Use HyDE (hypothetical answer expansion) to broaden retrieval",
1411 ),
1412 "hyde_weight": SettingDef(
1413 float,
1414 nullable=False,
1415 group=SettingGroup.RETRIEVAL,
1416 help_text="Weight on the HyDE-generated query vector when blending with the original",
1417 advanced=True,
1418 ),
1419 "query_expansion_count": SettingDef(
1420 int,
1421 nullable=False,
1422 group=SettingGroup.RETRIEVAL,
1423 help_text="Number of paraphrase expansions per query (0 disables expansion)",
1424 advanced=True,
1425 ),
1426 "expansion_similarity_threshold": SettingDef(
1427 float,
1428 nullable=False,
1429 group=SettingGroup.RETRIEVAL,
1430 help_text="Minimum cosine similarity an expansion must keep with the original query",
1431 advanced=True,
1432 ),
1433 "expansion_short_query_tokens": SettingDef(
1434 int,
1435 nullable=False,
1436 group=SettingGroup.RETRIEVAL,
1437 help_text="Queries at or below this token count skip expansion (saves a model call)",
1438 advanced=True,
1439 ),
1440 "expansion_guardrails": SettingDef(
1441 bool,
1442 nullable=False,
1443 group=SettingGroup.RETRIEVAL,
1444 help_text="Drop expansions that diverge from the original intent",
1445 advanced=True,
1446 ),
1447 "adaptive_threshold": SettingDef(
1448 bool,
1449 nullable=False,
1450 group=SettingGroup.RETRIEVAL,
1451 help_text="Widen the distance cutoff when too few results pass (vector-only fallback path)",
1452 ),
1453 "adaptive_threshold_step": SettingDef(
1454 float,
1455 nullable=False,
1456 group=SettingGroup.RETRIEVAL,
1457 help_text="Step size for adaptive relevance-score relaxation when initial recall is empty",
1458 advanced=True,
1459 ),
1460 "concept_graph": SettingDef(
1461 bool,
1462 nullable=False,
1463 group=SettingGroup.RETRIEVAL,
1464 help_text="Boost retrieval scores for chunks that share concepts with the query",
1465 ),
1466 "concept_boost_weight": SettingDef(
1467 float,
1468 nullable=False,
1469 group=SettingGroup.RETRIEVAL,
1470 help_text="Maximum boost (0-1) the concept graph can add to a chunk's relevance",
1471 advanced=True,
1472 ),
1473 "concept_max_per_chunk": SettingDef(
1474 int,
1475 nullable=False,
1476 group=SettingGroup.RETRIEVAL,
1477 help_text="Maximum concept tags stored per chunk (caps graph density)",
1478 advanced=True,
1479 ),
1480 "documents_dir": SettingDef(
1481 str,
1482 nullable=False,
1483 group=SettingGroup.SYSTEM,
1484 help_text="Local documents root that lilbee sync ingests (blank = data_root/documents)",
1485 ),
1486 "vault_base": SettingDef(
1487 str,
1488 nullable=True,
1489 group=SettingGroup.SYSTEM,
1490 help_text="Markdown vault root; results carry a vault-relative path (blank = none)",
1491 ),
1492 "sse_heartbeat_interval": SettingDef(
1493 float,
1494 nullable=False,
1495 group=SettingGroup.SYSTEM,
1496 help_text="Seconds between SSE keep-alive frames sent to idle HTTP stream clients",
1497 hidden=True,
1498 ),
1499 "llm_provider": SettingDef(
1500 str,
1501 nullable=False,
1502 group=SettingGroup.API_KEYS,
1503 help_text=(
1504 "Inference provider: auto (default, runs models locally on llama-server) "
1505 "or remote (external OpenAI-compatible endpoint)"
1506 ),
1507 ),
1508 "ollama_base_url": SettingDef(
1509 str,
1510 nullable=False,
1511 group=SettingGroup.LOCAL_SERVERS,
1512 help_text="Ollama server URL (blank uses http://localhost:11434)",
1513 ),
1514 "lm_studio_base_url": SettingDef(
1515 str,
1516 nullable=False,
1517 group=SettingGroup.LOCAL_SERVERS,
1518 help_text="LM Studio server URL (blank uses http://localhost:1234/v1)",
1519 ),
1520 "llama_server_path": SettingDef(
1521 str,
1522 nullable=False,
1523 group=SettingGroup.API_KEYS,
1524 help_text="Path to a llama-server binary (empty: bundled wheel or PATH)",
1525 advanced=True,
1526 ),
1527 "wiki_summary_max_tokens": SettingDef(
1528 int,
1529 nullable=False,
1530 group=SettingGroup.WIKI,
1531 help_text="Maximum tokens generated per wiki page",
1532 advanced=True,
1533 ),
1534 "wiki_temperature": SettingDef(
1535 float,
1536 nullable=False,
1537 group=SettingGroup.WIKI,
1538 help_text="Temperature used for wiki page synthesis (low = stay close to sources)",
1539 advanced=True,
1540 ),
1541}
1544def _fill_value_sets(settings: dict[str, SettingDef]) -> None:
1545 """Give every setting the closed value set Config declares for it.
1547 An explicit ``choices`` wins, for a field whose value set lives outside
1548 its type (``theme``) and for a deliberate narrowing.
1549 """
1550 for key, definition in settings.items():
1551 if definition.choices is not None:
1552 continue
1553 declared = field_value_set(key)
1554 if declared is not None:
1555 settings[key] = replace(definition, choices=declared)
1558_fill_value_sets(SETTINGS_MAP)