Coverage for src/lilbee/app/settings_map.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-14 11:46 +0000

1"""Shared settings map for interactive configuration.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from enum import StrEnum 

7 

8from pydantic_core import PydanticUndefined 

9 

10from lilbee.app.themes import DARK_THEMES 

11from lilbee.core.config import cfg 

12from lilbee.core.config.enums import ( 

13 ChatMode, 

14 ClustererBackend, 

15 CrawlRenderMode, 

16 KvCacheType, 

17 LlmProvider, 

18 RerankerType, 

19 TableModel, 

20 WikiEntityMode, 

21) 

22from lilbee.core.config.model import FTS_LANGUAGES 

23 

24 

25class RenderStyle(StrEnum): 

26 """How a setting is displayed in /settings.""" 

27 

28 COMPACT = "compact" 

29 FULL = "full" 

30 LIST_COLLAPSED = "list_collapsed" 

31 MULTILINE = "multiline" 

32 

33 

34class SettingGroup(StrEnum): 

35 """Logical bucket names rendered by ``/settings`` and ``settings_list``.""" 

36 

37 MODELS = "Models" 

38 GENERATION = "Generation" 

39 RETRIEVAL = "Retrieval" 

40 INGEST = "Ingest" 

41 WIKI = "Wiki" 

42 MEMORY = "Memory" 

43 CRAWLING = "Crawling" 

44 LOCAL_SERVERS = "Local-Servers" 

45 API_KEYS = "API-Keys" 

46 SYSTEM = "System" 

47 DISPLAY = "Display" 

48 GENERAL = "General" 

49 

50 

51@dataclass(frozen=True) 

52class SettingDef: 

53 """Metadata for an interactive setting. 

54 

55 ``writable`` is a TUI rendering hint: fields marked ``writable=False`` 

56 (the model role slots) get a dedicated picker rather than an inline 

57 editor, and the ``/set`` slash command refuses them. The actual 

58 write contract for HTTP / MCP / programmatic surfaces lives in 

59 ``config_meta.WRITABLE_CONFIG_FIELDS`` + ``MODEL_ROLE_FIELDS`` and 

60 is enforced by ``app.settings.apply_settings_update``. 

61 

62 ``hidden`` keeps the setting out of the TUI settings screen while 

63 leaving it reachable via ``lilbee set`` and the ``LILBEE_*`` env 

64 var: use it for transport/server knobs that aren't relevant to a 

65 typical TUI session. 

66 """ 

67 

68 type: type 

69 nullable: bool 

70 writable: bool = True 

71 render: RenderStyle = field(default=RenderStyle.COMPACT) 

72 group: SettingGroup = SettingGroup.GENERAL 

73 help_text: str = "" 

74 choices: tuple[str, ...] | None = None 

75 hidden: bool = False 

76 # List editors validate each line as a regex only when this is set; flag-style 

77 # lists (e.g. crawl_browser_extra_args) would be wrongly rejected otherwise. 

78 validate_regex: bool = False 

79 # Credentials: the TUI masks the editor so the value is never on screen in 

80 # plain text, including while it is being pasted. 

81 secret: bool = False 

82 

83 

84def get_default(key: str) -> object: 

85 """Return the cfg default for a setting key.""" 

86 field_info = type(cfg).model_fields[key] 

87 if field_info.default_factory is not None: 

88 return field_info.default_factory() # type: ignore[call-arg] 

89 if field_info.default is PydanticUndefined: 

90 return None 

91 return field_info.default 

92 

93 

94SETTINGS_MAP: dict[str, SettingDef] = { 

95 "chat_model": SettingDef( 

96 str, 

97 nullable=False, 

98 writable=False, 

99 group=SettingGroup.MODELS, 

100 help_text="LLM used for chat generation (vision and reranking are separate slots)", 

101 ), 

102 "vision_model": SettingDef( 

103 str, 

104 nullable=True, 

105 writable=False, 

106 group=SettingGroup.MODELS, 

107 help_text="Vision model for scanned PDF OCR (empty = disabled; Tesseract only)", 

108 ), 

109 "enable_ocr": SettingDef( 

110 bool, 

111 nullable=True, 

112 group=SettingGroup.INGEST, 

113 help_text="Vision OCR for scanned PDFs (empty = auto-detect from vision_model)", 

114 ), 

115 "ocr_timeout": SettingDef( 

116 float, 

117 nullable=False, 

118 group=SettingGroup.INGEST, 

119 help_text="Per-page timeout in seconds for vision OCR (0 = no limit)", 

120 ), 

121 "vision_load_budget_s": SettingDef( 

122 float, 

123 nullable=False, 

124 group=SettingGroup.INGEST, 

125 help_text=( 

126 "Wall-clock seconds reserved for the vision worker to load the" 

127 " model. Total PDF-OCR budget = load_budget + ocr_timeout * pages." 

128 ), 

129 ), 

130 "vision_ocr_max_tokens": SettingDef( 

131 int, 

132 nullable=False, 

133 group=SettingGroup.INGEST, 

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 ), 

139 "vision_ocr_concurrency": SettingDef( 

140 int, 

141 nullable=False, 

142 group=SettingGroup.INGEST, 

143 help_text="Pages OCR'd concurrently per vision server; each slot adds KV cache memory", 

144 ), 

145 "ingest_workers": SettingDef( 

146 int, 

147 nullable=False, 

148 group=SettingGroup.INGEST, 

149 help_text="Workers for discovering and hashing files (0 = auto, all available cores)", 

150 ), 

151 "ingest_processes": SettingDef( 

152 int, 

153 nullable=False, 

154 group=SettingGroup.INGEST, 

155 help_text=( 

156 "Ingest worker processes, one GPU each (0 = auto, one per card). Used" 

157 " once the corpus is big enough to pay for them; 1 keeps ingest in this" 

158 " process" 

159 ), 

160 ), 

161 "mcp_tool_threads": SettingDef( 

162 int, 

163 nullable=False, 

164 group=SettingGroup.LOCAL_SERVERS, 

165 help_text=( 

166 "Threads for synchronous MCP tool handlers; the ceiling on how many agents" 

167 " one daemon serves before retrieval calls queue" 

168 ), 

169 ), 

170 "crawl_convert_workers": SettingDef( 

171 int, 

172 nullable=False, 

173 group=SettingGroup.CRAWLING, 

174 help_text=( 

175 "Crawled pages converted to markdown on worker threads at once, so a crawl" 

176 " does not block request handling; 0 converts on the event loop" 

177 ), 

178 ), 

179 "auto_sync": SettingDef( 

180 bool, 

181 nullable=False, 

182 group=SettingGroup.INGEST, 

183 help_text="Run a sync before `lilbee ask` (disable on large static corpora)", 

184 ), 

185 "entity_extraction": SettingDef( 

186 bool, 

187 nullable=False, 

188 group=SettingGroup.INGEST, 

189 help_text="Extract typed entities automatically at sync (schema induced on first run)", 

190 ), 

191 "semantic_chunking": SettingDef( 

192 bool, 

193 nullable=False, 

194 group=SettingGroup.INGEST, 

195 help_text="Opt-in topic-aware chunker (default off; may fragment numbered procedures)", 

196 ), 

197 "topic_threshold": SettingDef( 

198 float, 

199 nullable=False, 

200 group=SettingGroup.INGEST, 

201 help_text="Topic-boundary similarity threshold, 0.0-1.0, used when semantic chunking is on", 

202 ), 

203 "token_sizing": SettingDef( 

204 bool, 

205 nullable=False, 

206 group=SettingGroup.INGEST, 

207 help_text="Size chunks by real embedder tokens, not chars (changes invalidate the index)", 

208 ), 

209 "table_extraction": SettingDef( 

210 bool, 

211 nullable=False, 

212 group=SettingGroup.INGEST, 

213 help_text="Index each extracted table as its own chunk (changes invalidate the index)", 

214 ), 

215 "layout_detection": SettingDef( 

216 bool, 

217 nullable=False, 

218 group=SettingGroup.INGEST, 

219 help_text=( 

220 "Layout-aware PDF extraction: reading order plus header/footer " 

221 "stripping (changes invalidate the index)" 

222 ), 

223 ), 

224 "table_model": SettingDef( 

225 str, 

226 nullable=False, 

227 group=SettingGroup.INGEST, 

228 choices=tuple(m.value for m in TableModel), 

229 help_text=( 

230 "Table structure model used when layout detection is on: slanet_auto " 

231 "(docling-parity default), other slanet variants, tatr, or disabled " 

232 "(changes invalidate the index)" 

233 ), 

234 ), 

235 "batch_extraction": SettingDef( 

236 bool, 

237 nullable=False, 

238 group=SettingGroup.INGEST, 

239 help_text="Coalesce concurrent extractions into one xberg batch call", 

240 ), 

241 "batch_extraction_size": SettingDef( 

242 int, 

243 nullable=False, 

244 group=SettingGroup.INGEST, 

245 help_text="Max files per extract_batch call when batch extraction is on", 

246 ), 

247 "embedding_model": SettingDef( 

248 str, 

249 nullable=False, 

250 writable=False, 

251 group=SettingGroup.MODELS, 

252 help_text="Model used to embed document chunks", 

253 ), 

254 "reranker_model": SettingDef( 

255 str, 

256 nullable=True, 

257 writable=False, 

258 group=SettingGroup.MODELS, 

259 help_text="Cross-encoder model for result reranking", 

260 ), 

261 "reranker_type": SettingDef( 

262 str, 

263 nullable=False, 

264 group=SettingGroup.MODELS, 

265 choices=tuple(t.value for t in RerankerType), 

266 help_text=( 

267 "Reranker serving mode: auto (detect cross-encoder vs LLM by model), " 

268 "cross_encoder, or llm" 

269 ), 

270 ), 

271 "reranker_prompt": SettingDef( 

272 str, 

273 nullable=False, 

274 group=SettingGroup.MODELS, 

275 help_text="Relevance prompt for LLM rerankers (blank uses the built-in template)", 

276 ), 

277 "temperature": SettingDef( 

278 float, 

279 nullable=True, 

280 group=SettingGroup.GENERATION, 

281 help_text="Sampling temperature (higher = more creative)", 

282 ), 

283 "top_p": SettingDef( 

284 float, 

285 nullable=True, 

286 group=SettingGroup.GENERATION, 

287 help_text="Nucleus sampling cutoff probability", 

288 ), 

289 "top_k_sampling": SettingDef( 

290 int, 

291 nullable=True, 

292 group=SettingGroup.GENERATION, 

293 help_text="Top-K sampling: number of tokens to consider", 

294 ), 

295 "repeat_penalty": SettingDef( 

296 float, 

297 nullable=True, 

298 group=SettingGroup.GENERATION, 

299 help_text="Penalty for repeating tokens", 

300 ), 

301 "num_ctx": SettingDef( 

302 int, 

303 nullable=True, 

304 group=SettingGroup.GENERATION, 

305 help_text=( 

306 "Context window size in tokens. Leave empty to size automatically " 

307 "(aims for chat_n_ctx_target, ceiling at num_ctx_max or training_ctx)." 

308 ), 

309 ), 

310 "num_ctx_max": SettingDef( 

311 int, 

312 nullable=True, 

313 group=SettingGroup.GENERATION, 

314 help_text=( 

315 "Explicit ceiling for the dynamic context picker. Leave empty to " 

316 "use the model's training_ctx from GGUF metadata as the only " 

317 "ceiling. Set to cap below training_ctx (saves KV memory)." 

318 ), 

319 ), 

320 "chat_n_ctx_target": SettingDef( 

321 int, 

322 nullable=False, 

323 group=SettingGroup.GENERATION, 

324 help_text=( 

325 "Working context the dynamic picker aims for. Fits a RAG turn " 

326 "with reasoning headroom; raise for long-document chat." 

327 ), 

328 ), 

329 "flash_attention": SettingDef( 

330 bool, 

331 nullable=True, 

332 group=SettingGroup.GENERATION, 

333 help_text=( 

334 "Flash attention. Empty (auto) enables it; disable for backends or " 

335 "models where it misbehaves. Resolves the V-cache padding warning " 

336 "on models with uneven per-layer V dims." 

337 ), 

338 ), 

339 "kv_cache_type": SettingDef( 

340 str, 

341 nullable=False, 

342 group=SettingGroup.GENERATION, 

343 help_text=( 

344 "KV cache element type. q8_0 / q4_0 halve or quarter cache memory " 

345 "but require flash attention to be enabled." 

346 ), 

347 choices=tuple(t.value for t in KvCacheType), 

348 ), 

349 "n_gpu_layers": SettingDef( 

350 int, 

351 nullable=True, 

352 group=SettingGroup.GENERATION, 

353 help_text=( 

354 "Layers to offload to GPU. Empty = all (recommended), 0 = CPU only, " 

355 "positive int = partial offload for tight VRAM." 

356 ), 

357 ), 

358 "cpu_moe": SettingDef( 

359 bool, 

360 nullable=False, 

361 group=SettingGroup.GENERATION, 

362 help_text=( 

363 "Keep a mixture-of-experts model's expert weights in system memory so " 

364 "it fits a smaller GPU. No effect on dense models." 

365 ), 

366 ), 

367 "n_cpu_moe": SettingDef( 

368 int, 

369 nullable=True, 

370 group=SettingGroup.GENERATION, 

371 help_text=( 

372 "Offload only the first N layers' experts to system memory. Takes " 

373 "precedence over the offload-everything setting; smaller N stays faster." 

374 ), 

375 ), 

376 "fast_model_downloads": SettingDef( 

377 bool, 

378 nullable=False, 

379 group=SettingGroup.GENERATION, 

380 help_text=( 

381 "Warning: Hugging Face states this mode uses all available bandwidth " 

382 "and CPU cores, and buffers far more of the download in memory. " 

383 "Faster on a fast connection, at the cost of everything else running " 

384 "on the machine. Leave it off unless the machine can spare that. " 

385 "Requires a restart to take effect." 

386 ), 

387 ), 

388 "gpu_devices": SettingDef( 

389 str, 

390 nullable=True, 

391 group=SettingGroup.GENERATION, 

392 help_text=( 

393 "Restrict llama.cpp to specific GPU indexes on dual-GPU machines " 

394 "(e.g. NVIDIA dGPU + integrated). Comma-separated, like '0' or '0,1'. " 

395 "Applies to Vulkan, CUDA, and ROCm. Requires a restart to take effect." 

396 ), 

397 ), 

398 "main_gpu": SettingDef( 

399 int, 

400 nullable=True, 

401 group=SettingGroup.GENERATION, 

402 help_text=( 

403 "Primary GPU index for llama.cpp when multiple devices are visible. " 

404 "Empty = let llama.cpp pick (index 0). Set this together with " 

405 "gpu_devices to pin inference to a specific card." 

406 ), 

407 ), 

408 "seed": SettingDef( 

409 int, 

410 nullable=True, 

411 group=SettingGroup.GENERATION, 

412 help_text="Random seed for reproducible output", 

413 ), 

414 "rag_system_prompt": SettingDef( 

415 str, 

416 nullable=False, 

417 render=RenderStyle.MULTILINE, 

418 group=SettingGroup.GENERATION, 

419 help_text="System prompt sent when answering with retrieved context", 

420 ), 

421 "general_system_prompt": SettingDef( 

422 str, 

423 nullable=False, 

424 render=RenderStyle.MULTILINE, 

425 group=SettingGroup.GENERATION, 

426 help_text="System prompt sent when there are no documents to ground the answer", 

427 ), 

428 "chat_compaction": SettingDef( 

429 bool, 

430 nullable=False, 

431 group=SettingGroup.GENERATION, 

432 help_text=( 

433 "Off (default): when a chat outgrows the model's context window the oldest " 

434 "turns are dropped. They stay on screen but the model stops seeing them, and " 

435 "the context chip by the prompt shows the window filling. Costs nothing. " 

436 "On: those turns are condensed into a short summary the model keeps reading, " 

437 "so it still knows roughly what was said. That costs one extra model call each " 

438 "time it fires, pausing the reply for a few seconds on a GPU and considerably " 

439 "longer on a CPU-only machine. Worth turning on if your hardware is quick." 

440 ), 

441 ), 

442 "sessions_enabled": SettingDef( 

443 bool, 

444 nullable=False, 

445 group=SettingGroup.GENERATION, 

446 help_text=( 

447 "On (default): conversations are saved automatically, and you can list, " 

448 "resume, rename, and delete them from the Sessions drawer (ctrl+o), the " 

449 "Sessions tab, and the /sessions command. Off: nothing is written to disk, " 

450 "the ctrl+o binding leaves the footer, and opening the Sessions view shows a " 

451 "notice that sessions are turned off. Turn it off if you would rather your " 

452 "chats not persist. Covers the TUI, the HTTP server, and the CLI; agent " 

453 "sessions have their own setting." 

454 ), 

455 ), 

456 "mcp_sessions_enabled": SettingDef( 

457 bool, 

458 nullable=False, 

459 group=SettingGroup.GENERATION, 

460 help_text=( 

461 "Off (default): the session tools are not offered over MCP, and a connected " 

462 "agent cannot create or read agent sessions. On: an agent can keep its own " 

463 "saved conversations, separate from yours. Most agent hosts already track " 

464 "their own history, and the tools cost context on every request, so this " 

465 "stays off unless you want an agent owning conversations." 

466 ), 

467 ), 

468 "chat_mode": SettingDef( 

469 str, 

470 nullable=False, 

471 group=SettingGroup.GENERATION, 

472 choices=tuple(m.value for m in ChatMode), 

473 help_text="search runs every chat turn through document retrieval; chat skips it", 

474 ), 

475 "top_k": SettingDef( 

476 int, 

477 nullable=False, 

478 group=SettingGroup.RETRIEVAL, 

479 help_text="Number of chunks returned by search", 

480 ), 

481 "rerank_candidates": SettingDef( 

482 int, 

483 nullable=False, 

484 group=SettingGroup.RETRIEVAL, 

485 help_text="Candidate pool size for reranking", 

486 ), 

487 "rerank_blend": SettingDef( 

488 bool, 

489 nullable=False, 

490 group=SettingGroup.RETRIEVAL, 

491 help_text="Blend reranker scores with retrieval fusion (off = pure reranker order)", 

492 ), 

493 "rerank_min_score": SettingDef( 

494 float, 

495 nullable=True, 

496 group=SettingGroup.RETRIEVAL, 

497 help_text="Drop candidates whose raw reranker score is below this (unset = off)", 

498 ), 

499 "show_reasoning": SettingDef( 

500 bool, 

501 nullable=False, 

502 group=SettingGroup.DISPLAY, 

503 help_text="Show model reasoning/thinking tokens in output", 

504 ), 

505 "lilbee_name": SettingDef( 

506 str, 

507 nullable=False, 

508 group=SettingGroup.DISPLAY, 

509 help_text=( 

510 "Human-readable label for this lilbee, shown in the status bar. " 

511 "Empty falls back to 'global' for the platform default dir or " 

512 "to the project path (~-substituted and left-truncated)." 

513 ), 

514 ), 

515 "show_lilbee_path": SettingDef( 

516 bool, 

517 nullable=False, 

518 group=SettingGroup.DISPLAY, 

519 help_text=( 

520 "Show the full absolute path in the status bar: expands 'global' " 

521 "to its on-disk path and skips ~ substitution / truncation." 

522 ), 

523 ), 

524 "theme": SettingDef( 

525 str, 

526 nullable=False, 

527 group=SettingGroup.DISPLAY, 

528 help_text="TUI color theme. Cycle with Ctrl+T; the active theme persists across sessions.", 

529 choices=tuple(DARK_THEMES), 

530 ), 

531 "wiki": SettingDef( 

532 bool, 

533 nullable=False, 

534 group=SettingGroup.WIKI, 

535 help_text=( 

536 "Enable the wiki layer (cited concept and entity pages). " 

537 "GPU-heavy: a build spends one LLM call per source document, " 

538 "so a large library takes hours. Enabling this generates nothing " 

539 "on its own: you wikify explicitly, or turn on wiki_auto_update" 

540 ), 

541 ), 

542 "wiki_auto_update": SettingDef( 

543 bool, 

544 nullable=False, 

545 group=SettingGroup.WIKI, 

546 help_text="Regenerate touched wiki pages after each sync (off: wikify explicitly)", 

547 ), 

548 "wiki_dir": SettingDef( 

549 str, 

550 nullable=False, 

551 writable=False, 

552 group=SettingGroup.WIKI, 

553 help_text=( 

554 "Directory under data_root where wiki pages live (set via env / config.toml only)" 

555 ), 

556 ), 

557 "wiki_prune_raw": SettingDef( 

558 bool, 

559 nullable=False, 

560 group=SettingGroup.WIKI, 

561 help_text="Delete raw chunks after summarizing into the wiki", 

562 ), 

563 "wiki_embedding_faithfulness_threshold": SettingDef( 

564 float, 

565 nullable=False, 

566 group=SettingGroup.WIKI, 

567 help_text=( 

568 "Minimum cosine similarity (0-1) between a generated page and " 

569 "the mean of its source chunk vectors before publishing. " 

570 "Pages below the threshold route to drafts/." 

571 ), 

572 ), 

573 "wiki_stale_citation_threshold": SettingDef( 

574 float, 

575 nullable=False, 

576 group=SettingGroup.WIKI, 

577 help_text="Fraction of stale citations before a page is flagged by wiki prune", 

578 ), 

579 "wiki_drift_threshold": SettingDef( 

580 float, 

581 nullable=False, 

582 group=SettingGroup.WIKI, 

583 help_text="Max fraction of changed lines before regeneration requires review", 

584 ), 

585 "wiki_clusterer": SettingDef( 

586 str, 

587 nullable=False, 

588 group=SettingGroup.WIKI, 

589 help_text="Synthesis clusterer backend (embedding or concepts)", 

590 choices=tuple(b.value for b in ClustererBackend), 

591 ), 

592 "wiki_entity_mode": SettingDef( 

593 str, 

594 nullable=False, 

595 group=SettingGroup.WIKI, 

596 help_text=( 

597 "Entity extraction strategy. ner_entities (typed spaCy NER) is the " 

598 "only implemented mode; the other values fall back to it with a warning" 

599 ), 

600 choices=tuple(m.value for m in WikiEntityMode), 

601 ), 

602 "wiki_entity_min_mentions": SettingDef( 

603 int, 

604 nullable=False, 

605 group=SettingGroup.WIKI, 

606 help_text="Minimum chunk mentions before an entity or concept gets its own page", 

607 ), 

608 "wiki_stub_max_chunk_refs": SettingDef( 

609 int, 

610 nullable=False, 

611 group=SettingGroup.WIKI, 

612 help_text=( 

613 "How many source chunks a page kept for lazy generation draws on. " 

614 "Caps the browse index's size; already more than one page's context " 

615 "budget admits, so raising it rarely changes what a page says" 

616 ), 

617 ), 

618 "wiki_ingest_update_cap": SettingDef( 

619 int, 

620 nullable=False, 

621 group=SettingGroup.WIKI, 

622 help_text=( 

623 "Touched-page cap for auto-update after sync. " 

624 "Beyond this count, run `lilbee wiki update` manually." 

625 ), 

626 ), 

627 "wiki_synthesis_prompt": SettingDef( 

628 str, 

629 nullable=False, 

630 render=RenderStyle.FULL, 

631 group=SettingGroup.WIKI, 

632 help_text=( 

633 "Prompt for cross-source synthesis pages. " 

634 "Must keep {topic}, {source_list}, and {chunks_text}." 

635 ), 

636 ), 

637 "wiki_entity_page_prompt": SettingDef( 

638 str, 

639 nullable=False, 

640 render=RenderStyle.FULL, 

641 group=SettingGroup.WIKI, 

642 help_text=( 

643 "Prompt for a single page generated on demand from one subject's " 

644 "chunks across every source naming it. " 

645 "Must keep {topic}, {source_list}, and {chunks_text}." 

646 ), 

647 ), 

648 "wiki_entity_batch_prompt": SettingDef( 

649 str, 

650 nullable=False, 

651 render=RenderStyle.FULL, 

652 group=SettingGroup.WIKI, 

653 help_text=( 

654 "Prompt for the per-source batched call. " 

655 "Must keep {source}, {entity_list}, {chunks_text}, and {concept_instruction}." 

656 ), 

657 ), 

658 "wiki_extract_concepts": SettingDef( 

659 bool, 

660 nullable=False, 

661 group=SettingGroup.WIKI, 

662 help_text=( 

663 "Whether the per-source batched call asks the LLM to curate concept pages " 

664 "alongside the pre-extracted entity list." 

665 ), 

666 ), 

667 "wiki_batch_min_chunks": SettingDef( 

668 int, 

669 nullable=False, 

670 group=SettingGroup.WIKI, 

671 help_text=( 

672 "Minimum chunks a source must contribute before its batched call includes " 

673 "concept curation. Sources below the floor skip the concept-curation " 

674 "instruction; sources with zero entities AND below the floor are skipped entirely." 

675 ), 

676 ), 

677 "wiki_clusterer_k": SettingDef( 

678 int, 

679 nullable=False, 

680 group=SettingGroup.WIKI, 

681 help_text="Mutual-kNN neighborhood size for the clusterer (0 = auto)", 

682 ), 

683 "memory_enabled": SettingDef( 

684 bool, 

685 nullable=False, 

686 group=SettingGroup.MEMORY, 

687 help_text="Master switch for long-term chat memory (off by default)", 

688 ), 

689 "memory_auto_extract": SettingDef( 

690 bool, 

691 nullable=False, 

692 group=SettingGroup.MEMORY, 

693 help_text="Auto-save durable facts and preferences from each TUI turn (needs memory on)", 

694 ), 

695 "memory_top_k": SettingDef( 

696 int, 

697 nullable=False, 

698 group=SettingGroup.MEMORY, 

699 help_text="Maximum facts recalled into context per turn", 

700 ), 

701 "memory_max_distance": SettingDef( 

702 float, 

703 nullable=False, 

704 group=SettingGroup.MEMORY, 

705 help_text="Recall cutoff distance, 0.0-1.0 (lower is stricter)", 

706 ), 

707 "memory_token_budget": SettingDef( 

708 int, 

709 nullable=False, 

710 group=SettingGroup.MEMORY, 

711 help_text="Token cap on the recalled-memory block added to the prompt", 

712 ), 

713 "memory_max_per_owner": SettingDef( 

714 int, 

715 nullable=False, 

716 group=SettingGroup.MEMORY, 

717 help_text="Soft cap before the oldest memories are evicted", 

718 hidden=True, 

719 ), 

720 "memory_dedup_distance": SettingDef( 

721 float, 

722 nullable=False, 

723 group=SettingGroup.MEMORY, 

724 help_text="Near-duplicate distance below which a new memory updates the old", 

725 hidden=True, 

726 ), 

727 "crawl_max_depth": SettingDef( 

728 int, 

729 nullable=True, 

730 group=SettingGroup.CRAWLING, 

731 help_text="Optional recursion-depth cap (blank = no cap; per-crawl values win)", 

732 ), 

733 "crawl_render_mode": SettingDef( 

734 str, 

735 nullable=False, 

736 group=SettingGroup.CRAWLING, 

737 help_text=( 

738 "How crawls fetch pages. http = lightweight, no browser (default, best " 

739 "for static and server-rendered sites). browser = Chromium with " 

740 "JavaScript enabled for client-rendered sites, at much higher memory cost." 

741 ), 

742 choices=tuple(m.value for m in CrawlRenderMode), 

743 ), 

744 "crawl_browser_recycle_pages": SettingDef( 

745 int, 

746 nullable=False, 

747 group=SettingGroup.CRAWLING, 

748 help_text=( 

749 "Browser mode: recycle the Chromium process every N pages to cap memory " 

750 "growth on long crawls (0 = never recycle)." 

751 ), 

752 ), 

753 "crawl_browser_extra_args": SettingDef( 

754 list, 

755 nullable=False, 

756 group=SettingGroup.CRAWLING, 

757 render=RenderStyle.LIST_COLLAPSED, 

758 help_text=( 

759 "Browser mode: extra Chromium launch flags, one per line. " 

760 "Defaults trim shared-memory and GPU use." 

761 ), 

762 ), 

763 "crawl_max_pages": SettingDef( 

764 int, 

765 nullable=True, 

766 group=SettingGroup.CRAWLING, 

767 help_text="Optional global cap on total pages per crawl (blank = no cap).", 

768 ), 

769 "crawl_safety_max_pages": SettingDef( 

770 int, 

771 nullable=False, 

772 group=SettingGroup.CRAWLING, 

773 help_text="Default page bound for an unbounded crawl, so a hostile site cannot " 

774 "exhaust the disk. An explicit max-pages overrides it; raise this to crawl " 

775 "larger sites unbounded.", 

776 ), 

777 "crawl_timeout": SettingDef( 

778 int, 

779 nullable=False, 

780 group=SettingGroup.CRAWLING, 

781 help_text="Per-page fetch timeout in seconds", 

782 ), 

783 "crawl_sync_interval": SettingDef( 

784 int, 

785 nullable=False, 

786 group=SettingGroup.CRAWLING, 

787 help_text="Seconds between periodic re-syncs during a crawl (0 = sync only at end)", 

788 ), 

789 "crawl_mean_delay": SettingDef( 

790 float, 

791 nullable=False, 

792 group=SettingGroup.CRAWLING, 

793 help_text="Seconds between in-flight requests within a single crawl", 

794 ), 

795 "crawl_max_delay_range": SettingDef( 

796 float, 

797 nullable=False, 

798 group=SettingGroup.CRAWLING, 

799 help_text="Random jitter (seconds) added on top of mean delay", 

800 ), 

801 "crawl_concurrent_requests": SettingDef( 

802 int, 

803 nullable=False, 

804 group=SettingGroup.CRAWLING, 

805 help_text="Concurrent in-flight URLs within one crawl", 

806 ), 

807 "crawl_retry_on_rate_limit": SettingDef( 

808 bool, 

809 nullable=False, 

810 group=SettingGroup.CRAWLING, 

811 help_text="Enable per-domain backoff and retries on HTTP 429/503", 

812 ), 

813 "crawl_retry_base_delay_min": SettingDef( 

814 float, 

815 nullable=False, 

816 group=SettingGroup.CRAWLING, 

817 help_text="Minimum base-delay (seconds) on rate-limit responses", 

818 ), 

819 "crawl_retry_base_delay_max": SettingDef( 

820 float, 

821 nullable=False, 

822 group=SettingGroup.CRAWLING, 

823 help_text="Maximum base-delay (seconds) on rate-limit responses", 

824 ), 

825 "crawl_retry_max_backoff": SettingDef( 

826 float, 

827 nullable=False, 

828 group=SettingGroup.CRAWLING, 

829 help_text="Upper bound on any single backoff wait (seconds)", 

830 ), 

831 "crawl_retry_max_attempts": SettingDef( 

832 int, 

833 nullable=False, 

834 group=SettingGroup.CRAWLING, 

835 help_text="Retry count per URL when a rate-limit code comes back", 

836 ), 

837 "crawl_exclude_patterns": SettingDef( 

838 list, 

839 nullable=False, 

840 group=SettingGroup.CRAWLING, 

841 render=RenderStyle.LIST_COLLAPSED, 

842 validate_regex=True, 

843 help_text=( 

844 "Regex patterns that skip URLs at link-discovery time during " 

845 "recursive crawls. One per line." 

846 ), 

847 ), 

848 "openrouter_api_key": SettingDef( 

849 str, 

850 nullable=False, 

851 group=SettingGroup.API_KEYS, 

852 secret=True, 

853 help_text="OpenRouter API key (enables frontier models in chat picker)", 

854 ), 

855 "gemini_api_key": SettingDef( 

856 str, 

857 nullable=False, 

858 group=SettingGroup.API_KEYS, 

859 secret=True, 

860 help_text="Google Gemini API key (enables frontier models in chat picker)", 

861 ), 

862 "anthropic_api_key": SettingDef( 

863 str, 

864 nullable=False, 

865 group=SettingGroup.API_KEYS, 

866 secret=True, 

867 help_text="Anthropic API key (enables frontier models in chat picker)", 

868 ), 

869 "openai_api_key": SettingDef( 

870 str, 

871 nullable=False, 

872 group=SettingGroup.API_KEYS, 

873 secret=True, 

874 help_text="OpenAI API key (enables frontier models in chat picker)", 

875 ), 

876 "mistral_api_key": SettingDef( 

877 str, 

878 nullable=False, 

879 group=SettingGroup.API_KEYS, 

880 secret=True, 

881 help_text="Mistral API key (enables frontier models in chat picker)", 

882 ), 

883 "deepseek_api_key": SettingDef( 

884 str, 

885 nullable=False, 

886 group=SettingGroup.API_KEYS, 

887 secret=True, 

888 help_text="DeepSeek API key (enables frontier models in chat picker)", 

889 ), 

890 "llm_api_key": SettingDef( 

891 str, 

892 nullable=False, 

893 group=SettingGroup.API_KEYS, 

894 secret=True, 

895 help_text="API key for the remote OpenAI-compatible endpoint (llm_provider = remote)", 

896 ), 

897 "hf_token": SettingDef( 

898 str, 

899 nullable=False, 

900 group=SettingGroup.SYSTEM, 

901 secret=True, 

902 help_text=( 

903 "HuggingFace access token. Avoids the unauthenticated download " 

904 "rate limit and unlocks gated repos. Stored in plain text in " 

905 "config.toml. Env vars (LILBEE_HF_TOKEN, HF_TOKEN) override." 

906 ), 

907 ), 

908 "chunk_size": SettingDef( 

909 int, 

910 nullable=False, 

911 group=SettingGroup.INGEST, 

912 help_text="Document chunk size in tokens (changes invalidate the index)", 

913 ), 

914 "chunk_overlap": SettingDef( 

915 int, 

916 nullable=False, 

917 group=SettingGroup.INGEST, 

918 help_text="Tokens of overlap between adjacent chunks (preserves context across boundaries)", 

919 ), 

920 "tesseract_timeout": SettingDef( 

921 float, 

922 nullable=False, 

923 group=SettingGroup.INGEST, 

924 help_text="Per-page Tesseract timeout in seconds (used when no vision model is set)", 

925 ), 

926 "ocr_language": SettingDef( 

927 list, 

928 nullable=False, 

929 group=SettingGroup.INGEST, 

930 help_text="Tesseract OCR languages when no vision model is set; '+'-join, e.g. eng+deu", 

931 ), 

932 "worker_pool_eager_start": SettingDef( 

933 bool, 

934 nullable=False, 

935 group=SettingGroup.INGEST, 

936 help_text=( 

937 "Spawn every configured role server at TUI startup instead of on first use. " 

938 "Trades cold-start time per role for first-call latency" 

939 ), 

940 ), 

941 "keep_engine_warm": SettingDef( 

942 bool, 

943 nullable=False, 

944 group=SettingGroup.SYSTEM, 

945 help_text=( 

946 "Let the engine outlive lilbee for warm launches; off stops it on last " 

947 "exit unless another lilbee sharing the engine asked to keep it" 

948 ), 

949 ), 

950 "engine_idle_ttl_minutes": SettingDef( 

951 int, 

952 nullable=False, 

953 group=SettingGroup.SYSTEM, 

954 help_text="Idle minutes before the engine unloads its weights; 0 keeps them loaded", 

955 ), 

956 "agent_mcp_enabled": SettingDef( 

957 bool, 

958 nullable=False, 

959 group=SettingGroup.SYSTEM, 

960 help_text=( 

961 "Register lilbee's MCP search tool into agent launchers (opencode, hermes). " 

962 "Disable to bring your own MCP servers; lilbee stays the model provider" 

963 ), 

964 ), 

965 "max_tokens": SettingDef( 

966 int, 

967 nullable=True, 

968 group=SettingGroup.GENERATION, 

969 help_text="Hard cap on generated tokens per response (blank = no cap)", 

970 ), 

971 "max_reasoning_chars": SettingDef( 

972 int, 

973 nullable=False, 

974 group=SettingGroup.GENERATION, 

975 help_text=( 

976 "Maximum reasoning characters before lilbee forces the model to answer " 

977 "(0 = unlimited; per-model overrides apply on top)" 

978 ), 

979 ), 

980 "model_keep_alive": SettingDef( 

981 int, 

982 nullable=False, 

983 group=SettingGroup.GENERATION, 

984 help_text="Seconds the loaded model stays warm between calls (0 = unload immediately)", 

985 ), 

986 "gpu_memory_fraction": SettingDef( 

987 float, 

988 nullable=False, 

989 group=SettingGroup.GENERATION, 

990 help_text="Fraction of GPU memory the model is allowed to claim (0.1-1.0)", 

991 ), 

992 "usable_vram_fraction": SettingDef( 

993 float, 

994 nullable=False, 

995 group=SettingGroup.GENERATION, 

996 help_text=( 

997 "Share of a GPU placement may fill, leaving room for fragmentation and driver " 

998 "overhead (0.5-1.0). Raise it if a model that should fit is being refused; " 

999 "lower it if loads fail near the top of the card." 

1000 ), 

1001 ), 

1002 "system_memory_reserve_gb": SettingDef( 

1003 float, 

1004 nullable=False, 

1005 group=SettingGroup.GENERATION, 

1006 help_text=( 

1007 "RAM held back for the OS in GiB when serving from system memory (no discrete " 

1008 "GPU). Capped at a quarter of total RAM either way." 

1009 ), 

1010 ), 

1011 "embed_replicas": SettingDef( 

1012 int, 

1013 nullable=False, 

1014 group=SettingGroup.GENERATION, 

1015 help_text="Embedding servers in parallel (0 = auto, one per GPU; positive pins the count)", 

1016 ), 

1017 "vision_replicas": SettingDef( 

1018 int, 

1019 nullable=False, 

1020 group=SettingGroup.GENERATION, 

1021 help_text="Vision OCR servers in parallel (0 = auto, one per GPU; positive pins the count)", 

1022 ), 

1023 "candidate_multiplier": SettingDef( 

1024 int, 

1025 nullable=False, 

1026 group=SettingGroup.RETRIEVAL, 

1027 help_text="Candidate-pool multiplier over top_k before reranking", 

1028 ), 

1029 "title_search": SettingDef( 

1030 bool, 

1031 nullable=False, 

1032 group=SettingGroup.RETRIEVAL, 

1033 help_text="Match queries against document titles as a third hybrid-search arm", 

1034 ), 

1035 "title_search_weight": SettingDef( 

1036 float, 

1037 nullable=False, 

1038 group=SettingGroup.RETRIEVAL, 

1039 help_text="Title arm weight in rank fusion (1.0 = equal voice with the other arms)", 

1040 ), 

1041 "lexical_fusion_weight": SettingDef( 

1042 float, 

1043 nullable=False, 

1044 group=SettingGroup.RETRIEVAL, 

1045 help_text="BM25 arm weight in fusion (1.0 = equal to vector; lower to favor dense)", 

1046 ), 

1047 "adaptive_fusion": SettingDef( 

1048 bool, 

1049 nullable=False, 

1050 group=SettingGroup.RETRIEVAL, 

1051 help_text="Scale the BM25 weight per query by vector-arm confidence, not a fixed value", 

1052 ), 

1053 "adaptive_fusion_margin": SettingDef( 

1054 float, 

1055 nullable=False, 

1056 group=SettingGroup.RETRIEVAL, 

1057 help_text="Vector-similarity margin at which adaptive fusion fully silences the BM25 arm", 

1058 ), 

1059 "filter_structural_chunks": SettingDef( 

1060 bool, 

1061 nullable=False, 

1062 group=SettingGroup.RETRIEVAL, 

1063 help_text="Drop tables-of-contents and classification-banner cover pages from results", 

1064 ), 

1065 "fts_language": SettingDef( 

1066 str, 

1067 nullable=False, 

1068 group=SettingGroup.RETRIEVAL, 

1069 choices=tuple(sorted(FTS_LANGUAGES)), 

1070 help_text="Stemmer/stop-word language for BM25 indexes (rebuild to apply)", 

1071 ), 

1072 "embed_titles": SettingDef( 

1073 bool, 

1074 nullable=False, 

1075 group=SettingGroup.RETRIEVAL, 

1076 help_text="Prefix document titles to chunk embeddings (rebuild to apply)", 

1077 ), 

1078 "contextual_enrichment": SettingDef( 

1079 bool, 

1080 nullable=False, 

1081 group=SettingGroup.RETRIEVAL, 

1082 help_text="LLM context sentence per chunk embedding (slow ingest; rebuild to apply)", 

1083 ), 

1084 "history_rewrite": SettingDef( 

1085 bool, 

1086 nullable=False, 

1087 group=SettingGroup.RETRIEVAL, 

1088 help_text="Rewrite follow-ups into standalone retrieval queries using chat history", 

1089 ), 

1090 "intent_routing": SettingDef( 

1091 bool, 

1092 nullable=False, 

1093 group=SettingGroup.RETRIEVAL, 

1094 help_text="Route document-name lookups to exact retrieval, count questions to a scan", 

1095 ), 

1096 "intent_llm": SettingDef( 

1097 bool, 

1098 nullable=False, 

1099 group=SettingGroup.RETRIEVAL, 

1100 help_text=( 

1101 "Classify count questions with the chat model when the fast patterns " 

1102 "miss (covers phrasing variants and other languages; adds one short " 

1103 "LLM call to those turns)" 

1104 ), 

1105 ), 

1106 "ann_index_threshold": SettingDef( 

1107 int, 

1108 nullable=False, 

1109 group=SettingGroup.RETRIEVAL, 

1110 help_text="Chunk count to start building an ANN vector index (0 = always flat search)", 

1111 ), 

1112 "max_distance": SettingDef( 

1113 float, 

1114 nullable=False, 

1115 group=SettingGroup.RETRIEVAL, 

1116 help_text="Maximum vector distance for retrieval matches (lower = stricter)", 

1117 ), 

1118 "min_relevance_score": SettingDef( 

1119 float, 

1120 nullable=False, 

1121 group=SettingGroup.RETRIEVAL, 

1122 help_text="Minimum RRF relevance score for hybrid search results (0.0 = no filter)", 

1123 ), 

1124 "max_context_sources": SettingDef( 

1125 int, 

1126 nullable=False, 

1127 group=SettingGroup.RETRIEVAL, 

1128 help_text="Maximum unique sources contributing chunks to a single answer", 

1129 ), 

1130 "neighbor_expansion": SettingDef( 

1131 int, 

1132 nullable=False, 

1133 group=SettingGroup.RETRIEVAL, 

1134 help_text="Adjacent chunks merged into each retrieved passage per side (0 = off)", 

1135 ), 

1136 "diversity_max_per_source": SettingDef( 

1137 int, 

1138 nullable=False, 

1139 group=SettingGroup.RETRIEVAL, 

1140 help_text="Maximum chunks accepted from any one source (caps source dominance)", 

1141 ), 

1142 "mmr_lambda": SettingDef( 

1143 float, 

1144 nullable=False, 

1145 group=SettingGroup.RETRIEVAL, 

1146 help_text=( 

1147 "MMR lambda balancing relevance vs diversity (0 = max diversity, 1 = max relevance)" 

1148 ), 

1149 ), 

1150 "temporal_filtering": SettingDef( 

1151 bool, 

1152 nullable=False, 

1153 group=SettingGroup.RETRIEVAL, 

1154 help_text="Detect temporal queries and bias retrieval toward recent chunks", 

1155 ), 

1156 "hyde": SettingDef( 

1157 bool, 

1158 nullable=False, 

1159 group=SettingGroup.RETRIEVAL, 

1160 help_text="Use HyDE (hypothetical answer expansion) to broaden retrieval", 

1161 ), 

1162 "hyde_weight": SettingDef( 

1163 float, 

1164 nullable=False, 

1165 group=SettingGroup.RETRIEVAL, 

1166 help_text="Weight on the HyDE-generated query vector when blending with the original", 

1167 ), 

1168 "query_expansion_count": SettingDef( 

1169 int, 

1170 nullable=False, 

1171 group=SettingGroup.RETRIEVAL, 

1172 help_text="Number of paraphrase expansions per query (0 disables expansion)", 

1173 ), 

1174 "expansion_similarity_threshold": SettingDef( 

1175 float, 

1176 nullable=False, 

1177 group=SettingGroup.RETRIEVAL, 

1178 help_text="Minimum cosine similarity an expansion must keep with the original query", 

1179 ), 

1180 "expansion_short_query_tokens": SettingDef( 

1181 int, 

1182 nullable=False, 

1183 group=SettingGroup.RETRIEVAL, 

1184 help_text="Queries at or below this token count skip expansion (saves a model call)", 

1185 ), 

1186 "expansion_guardrails": SettingDef( 

1187 bool, 

1188 nullable=False, 

1189 group=SettingGroup.RETRIEVAL, 

1190 help_text="Drop expansions that diverge from the original intent", 

1191 ), 

1192 "adaptive_threshold": SettingDef( 

1193 bool, 

1194 nullable=False, 

1195 group=SettingGroup.RETRIEVAL, 

1196 help_text="Widen the distance cutoff when too few results pass (vector-only fallback path)", 

1197 ), 

1198 "adaptive_threshold_step": SettingDef( 

1199 float, 

1200 nullable=False, 

1201 group=SettingGroup.RETRIEVAL, 

1202 help_text="Step size for adaptive relevance-score relaxation when initial recall is empty", 

1203 ), 

1204 "concept_graph": SettingDef( 

1205 bool, 

1206 nullable=False, 

1207 group=SettingGroup.RETRIEVAL, 

1208 help_text="Boost retrieval scores for chunks that share concepts with the query", 

1209 ), 

1210 "concept_boost_weight": SettingDef( 

1211 float, 

1212 nullable=False, 

1213 group=SettingGroup.RETRIEVAL, 

1214 help_text="Maximum boost (0-1) the concept graph can add to a chunk's relevance", 

1215 ), 

1216 "concept_max_per_chunk": SettingDef( 

1217 int, 

1218 nullable=False, 

1219 group=SettingGroup.RETRIEVAL, 

1220 help_text="Maximum concept tags stored per chunk (caps graph density)", 

1221 ), 

1222 "documents_dir": SettingDef( 

1223 str, 

1224 nullable=False, 

1225 group=SettingGroup.SYSTEM, 

1226 help_text="Local documents root that lilbee sync ingests (blank = data_root/documents)", 

1227 ), 

1228 "vault_base": SettingDef( 

1229 str, 

1230 nullable=True, 

1231 group=SettingGroup.SYSTEM, 

1232 help_text="Markdown vault root; results carry a vault-relative path (blank = none)", 

1233 ), 

1234 "sse_heartbeat_interval": SettingDef( 

1235 float, 

1236 nullable=False, 

1237 group=SettingGroup.SYSTEM, 

1238 help_text="Seconds between SSE keep-alive frames sent to idle HTTP stream clients", 

1239 hidden=True, 

1240 ), 

1241 "llm_provider": SettingDef( 

1242 str, 

1243 nullable=False, 

1244 group=SettingGroup.API_KEYS, 

1245 choices=tuple(p.value for p in LlmProvider), 

1246 help_text=( 

1247 "Inference provider: auto (default, runs models locally on llama-server) " 

1248 "or remote (external OpenAI-compatible endpoint)" 

1249 ), 

1250 ), 

1251 "ollama_base_url": SettingDef( 

1252 str, 

1253 nullable=False, 

1254 group=SettingGroup.LOCAL_SERVERS, 

1255 help_text="Ollama server URL (blank uses http://localhost:11434)", 

1256 ), 

1257 "lm_studio_base_url": SettingDef( 

1258 str, 

1259 nullable=False, 

1260 group=SettingGroup.LOCAL_SERVERS, 

1261 help_text="LM Studio server URL (blank uses http://localhost:1234/v1)", 

1262 ), 

1263 "llama_server_path": SettingDef( 

1264 str, 

1265 nullable=False, 

1266 group=SettingGroup.API_KEYS, 

1267 help_text="Path to a llama-server binary (empty: bundled wheel or PATH)", 

1268 ), 

1269 "wiki_summary_max_tokens": SettingDef( 

1270 int, 

1271 nullable=False, 

1272 group=SettingGroup.WIKI, 

1273 help_text="Maximum tokens generated per wiki page", 

1274 ), 

1275 "wiki_temperature": SettingDef( 

1276 float, 

1277 nullable=False, 

1278 group=SettingGroup.WIKI, 

1279 help_text="Temperature used for wiki page synthesis (low = stay close to sources)", 

1280 ), 

1281}