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

45 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-09-08 09:20 +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 ReasoningMode, 

19 RerankerType, 

20 TableModel, 

21 WikiEntityMode, 

22) 

23from lilbee.core.config.model import FTS_LANGUAGES 

24 

25 

26class RenderStyle(StrEnum): 

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

28 

29 COMPACT = "compact" 

30 FULL = "full" 

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 "extraction_timeout": SettingDef( 

146 int, 

147 nullable=False, 

148 group=SettingGroup.INGEST, 

149 help_text=( 

150 "Wall-clock seconds one file gets to extract before ingest gives up" 

151 " on it (0 = no limit)" 

152 ), 

153 ), 

154 "ingest_workers": SettingDef( 

155 int, 

156 nullable=False, 

157 group=SettingGroup.INGEST, 

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

159 ), 

160 "ingest_processes": SettingDef( 

161 int, 

162 nullable=False, 

163 group=SettingGroup.INGEST, 

164 help_text=( 

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

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

167 " process" 

168 ), 

169 ), 

170 "mcp_tool_threads": SettingDef( 

171 int, 

172 nullable=False, 

173 group=SettingGroup.LOCAL_SERVERS, 

174 help_text=( 

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

176 " one daemon serves before retrieval calls queue" 

177 ), 

178 ), 

179 "crawl_convert_workers": SettingDef( 

180 int, 

181 nullable=False, 

182 group=SettingGroup.CRAWLING, 

183 help_text=( 

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

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

186 ), 

187 ), 

188 "auto_sync": SettingDef( 

189 bool, 

190 nullable=False, 

191 group=SettingGroup.INGEST, 

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

193 ), 

194 "entity_extraction": SettingDef( 

195 bool, 

196 nullable=False, 

197 group=SettingGroup.INGEST, 

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

199 ), 

200 "semantic_chunking": SettingDef( 

201 bool, 

202 nullable=False, 

203 group=SettingGroup.INGEST, 

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

205 ), 

206 "topic_threshold": SettingDef( 

207 float, 

208 nullable=False, 

209 group=SettingGroup.INGEST, 

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

211 ), 

212 "token_sizing": SettingDef( 

213 bool, 

214 nullable=False, 

215 group=SettingGroup.INGEST, 

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

217 ), 

218 "table_extraction": SettingDef( 

219 bool, 

220 nullable=False, 

221 group=SettingGroup.INGEST, 

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

223 ), 

224 "layout_detection": SettingDef( 

225 bool, 

226 nullable=False, 

227 group=SettingGroup.INGEST, 

228 help_text=( 

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

230 "stripping (changes invalidate the index)" 

231 ), 

232 ), 

233 "table_model": SettingDef( 

234 str, 

235 nullable=False, 

236 group=SettingGroup.INGEST, 

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

238 help_text=( 

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

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

241 "(changes invalidate the index)" 

242 ), 

243 ), 

244 "batch_extraction": SettingDef( 

245 bool, 

246 nullable=False, 

247 group=SettingGroup.INGEST, 

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

249 ), 

250 "batch_extraction_size": SettingDef( 

251 int, 

252 nullable=False, 

253 group=SettingGroup.INGEST, 

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

255 ), 

256 "embedding_model": SettingDef( 

257 str, 

258 nullable=False, 

259 writable=False, 

260 group=SettingGroup.MODELS, 

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

262 ), 

263 "reranker_model": SettingDef( 

264 str, 

265 nullable=True, 

266 writable=False, 

267 group=SettingGroup.MODELS, 

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

269 ), 

270 "reranker_type": SettingDef( 

271 str, 

272 nullable=False, 

273 group=SettingGroup.MODELS, 

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

275 help_text=( 

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

277 "cross_encoder, or llm" 

278 ), 

279 ), 

280 "reranker_prompt": SettingDef( 

281 str, 

282 nullable=False, 

283 group=SettingGroup.MODELS, 

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

285 ), 

286 "temperature": SettingDef( 

287 float, 

288 nullable=True, 

289 group=SettingGroup.GENERATION, 

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

291 ), 

292 "top_p": SettingDef( 

293 float, 

294 nullable=True, 

295 group=SettingGroup.GENERATION, 

296 help_text="Nucleus sampling cutoff probability", 

297 ), 

298 "top_k_sampling": SettingDef( 

299 int, 

300 nullable=True, 

301 group=SettingGroup.GENERATION, 

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

303 ), 

304 "repeat_penalty": SettingDef( 

305 float, 

306 nullable=True, 

307 group=SettingGroup.GENERATION, 

308 help_text="Penalty for repeating tokens", 

309 ), 

310 "num_ctx": SettingDef( 

311 int, 

312 nullable=True, 

313 group=SettingGroup.GENERATION, 

314 help_text=( 

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

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

317 ), 

318 ), 

319 "num_ctx_max": SettingDef( 

320 int, 

321 nullable=True, 

322 group=SettingGroup.GENERATION, 

323 help_text=( 

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

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

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

327 ), 

328 ), 

329 "chat_n_ctx_target": SettingDef( 

330 int, 

331 nullable=False, 

332 group=SettingGroup.GENERATION, 

333 help_text=( 

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

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

336 ), 

337 ), 

338 "flash_attention": SettingDef( 

339 bool, 

340 nullable=True, 

341 group=SettingGroup.GENERATION, 

342 help_text=( 

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

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

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

346 ), 

347 ), 

348 "kv_cache_type": SettingDef( 

349 str, 

350 nullable=False, 

351 group=SettingGroup.GENERATION, 

352 help_text=( 

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

354 "but require flash attention to be enabled." 

355 ), 

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

357 ), 

358 "n_gpu_layers": SettingDef( 

359 int, 

360 nullable=True, 

361 group=SettingGroup.GENERATION, 

362 help_text=( 

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

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

365 ), 

366 ), 

367 "cpu_moe": SettingDef( 

368 bool, 

369 nullable=False, 

370 group=SettingGroup.GENERATION, 

371 help_text=( 

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

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

374 ), 

375 ), 

376 "n_cpu_moe": SettingDef( 

377 int, 

378 nullable=True, 

379 group=SettingGroup.GENERATION, 

380 help_text=( 

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

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

383 ), 

384 ), 

385 "fast_model_downloads": SettingDef( 

386 bool, 

387 nullable=False, 

388 group=SettingGroup.GENERATION, 

389 help_text=( 

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

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

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

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

394 "Requires a restart to take effect." 

395 ), 

396 ), 

397 "gpu_devices": SettingDef( 

398 str, 

399 nullable=True, 

400 group=SettingGroup.GENERATION, 

401 help_text=( 

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

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

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

405 ), 

406 ), 

407 "main_gpu": SettingDef( 

408 int, 

409 nullable=True, 

410 group=SettingGroup.GENERATION, 

411 help_text=( 

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

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

414 "gpu_devices to pin inference to a specific card. Requires a restart " 

415 "to take effect." 

416 ), 

417 ), 

418 "seed": SettingDef( 

419 int, 

420 nullable=True, 

421 group=SettingGroup.GENERATION, 

422 help_text="Random seed for reproducible output", 

423 ), 

424 "rag_system_prompt": SettingDef( 

425 str, 

426 nullable=False, 

427 render=RenderStyle.MULTILINE, 

428 group=SettingGroup.GENERATION, 

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

430 ), 

431 "general_system_prompt": SettingDef( 

432 str, 

433 nullable=False, 

434 render=RenderStyle.MULTILINE, 

435 group=SettingGroup.GENERATION, 

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

437 ), 

438 "chat_compaction": SettingDef( 

439 bool, 

440 nullable=False, 

441 group=SettingGroup.GENERATION, 

442 help_text=( 

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

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

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

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

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

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

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

450 ), 

451 ), 

452 "sessions_enabled": SettingDef( 

453 bool, 

454 nullable=False, 

455 group=SettingGroup.GENERATION, 

456 help_text=( 

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

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

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

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

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

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

463 "sessions have their own setting." 

464 ), 

465 ), 

466 "mcp_sessions_enabled": SettingDef( 

467 bool, 

468 nullable=False, 

469 group=SettingGroup.GENERATION, 

470 help_text=( 

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

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

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

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

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

476 ), 

477 ), 

478 "chat_mode": SettingDef( 

479 str, 

480 nullable=False, 

481 group=SettingGroup.GENERATION, 

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

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

484 ), 

485 "top_k": SettingDef( 

486 int, 

487 nullable=False, 

488 group=SettingGroup.RETRIEVAL, 

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

490 ), 

491 "rerank_candidates": SettingDef( 

492 int, 

493 nullable=False, 

494 group=SettingGroup.RETRIEVAL, 

495 help_text="Candidate pool size for reranking", 

496 ), 

497 "rerank_blend": SettingDef( 

498 bool, 

499 nullable=False, 

500 group=SettingGroup.RETRIEVAL, 

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

502 ), 

503 "rerank_min_score": SettingDef( 

504 float, 

505 nullable=True, 

506 group=SettingGroup.RETRIEVAL, 

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

508 ), 

509 "show_reasoning": SettingDef( 

510 bool, 

511 nullable=False, 

512 group=SettingGroup.DISPLAY, 

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

514 ), 

515 "completions_reasoning": SettingDef( 

516 str, 

517 nullable=False, 

518 group=SettingGroup.GENERATION, 

519 help_text=( 

520 "How /v1/chat/completions presents thinking: separate " 

521 "reasoning_content field, inline thinking as plain content text, " 

522 "or off (ask the model not to think)" 

523 ), 

524 choices=tuple(m.value for m in ReasoningMode), 

525 ), 

526 "messages_reasoning": SettingDef( 

527 str, 

528 nullable=False, 

529 group=SettingGroup.GENERATION, 

530 help_text=( 

531 "How /v1/messages presents thinking: separate thinking block, " 

532 "inline thinking as plain answer text, or off (ask the model not " 

533 "to think)" 

534 ), 

535 choices=tuple(m.value for m in ReasoningMode), 

536 ), 

537 "lilbee_name": SettingDef( 

538 str, 

539 nullable=False, 

540 group=SettingGroup.DISPLAY, 

541 help_text=( 

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

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

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

545 ), 

546 ), 

547 "show_lilbee_path": SettingDef( 

548 bool, 

549 nullable=False, 

550 group=SettingGroup.DISPLAY, 

551 help_text=( 

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

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

554 ), 

555 ), 

556 "theme": SettingDef( 

557 str, 

558 nullable=False, 

559 group=SettingGroup.DISPLAY, 

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

561 choices=tuple(DARK_THEMES), 

562 ), 

563 "wiki": SettingDef( 

564 bool, 

565 nullable=False, 

566 group=SettingGroup.WIKI, 

567 help_text=( 

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

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

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

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

572 ), 

573 ), 

574 "wiki_auto_update": SettingDef( 

575 bool, 

576 nullable=False, 

577 group=SettingGroup.WIKI, 

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

579 ), 

580 "wiki_dir": SettingDef( 

581 str, 

582 nullable=False, 

583 writable=False, 

584 group=SettingGroup.WIKI, 

585 help_text=( 

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

587 ), 

588 ), 

589 "wiki_prune_raw": SettingDef( 

590 bool, 

591 nullable=False, 

592 group=SettingGroup.WIKI, 

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

594 ), 

595 "wiki_embedding_faithfulness_threshold": SettingDef( 

596 float, 

597 nullable=False, 

598 group=SettingGroup.WIKI, 

599 help_text=( 

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

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

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

603 ), 

604 ), 

605 "wiki_stale_citation_threshold": SettingDef( 

606 float, 

607 nullable=False, 

608 group=SettingGroup.WIKI, 

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

610 ), 

611 "wiki_drift_threshold": SettingDef( 

612 float, 

613 nullable=False, 

614 group=SettingGroup.WIKI, 

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

616 ), 

617 "wiki_clusterer": SettingDef( 

618 str, 

619 nullable=False, 

620 group=SettingGroup.WIKI, 

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

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

623 ), 

624 "wiki_entity_mode": SettingDef( 

625 str, 

626 nullable=False, 

627 group=SettingGroup.WIKI, 

628 help_text=( 

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

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

631 ), 

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

633 ), 

634 "wiki_entity_min_mentions": SettingDef( 

635 int, 

636 nullable=False, 

637 group=SettingGroup.WIKI, 

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

639 ), 

640 "wiki_stub_max_chunk_refs": SettingDef( 

641 int, 

642 nullable=False, 

643 group=SettingGroup.WIKI, 

644 help_text=( 

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

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

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

648 ), 

649 ), 

650 "wiki_ingest_update_cap": SettingDef( 

651 int, 

652 nullable=False, 

653 group=SettingGroup.WIKI, 

654 help_text=( 

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

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

657 ), 

658 ), 

659 "wiki_synthesis_prompt": SettingDef( 

660 str, 

661 nullable=False, 

662 render=RenderStyle.FULL, 

663 group=SettingGroup.WIKI, 

664 help_text=( 

665 "Prompt for cross-source synthesis pages. " 

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

667 ), 

668 ), 

669 "wiki_entity_page_prompt": SettingDef( 

670 str, 

671 nullable=False, 

672 render=RenderStyle.FULL, 

673 group=SettingGroup.WIKI, 

674 help_text=( 

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

676 "chunks across every source naming it. " 

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

678 ), 

679 ), 

680 "wiki_entity_batch_prompt": SettingDef( 

681 str, 

682 nullable=False, 

683 render=RenderStyle.FULL, 

684 group=SettingGroup.WIKI, 

685 help_text=( 

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

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

688 ), 

689 ), 

690 "wiki_extract_concepts": SettingDef( 

691 bool, 

692 nullable=False, 

693 group=SettingGroup.WIKI, 

694 help_text=( 

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

696 "alongside the pre-extracted entity list." 

697 ), 

698 ), 

699 "wiki_batch_min_chunks": SettingDef( 

700 int, 

701 nullable=False, 

702 group=SettingGroup.WIKI, 

703 help_text=( 

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

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

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

707 ), 

708 ), 

709 "wiki_clusterer_k": SettingDef( 

710 int, 

711 nullable=False, 

712 group=SettingGroup.WIKI, 

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

714 ), 

715 "memory_enabled": SettingDef( 

716 bool, 

717 nullable=False, 

718 group=SettingGroup.MEMORY, 

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

720 ), 

721 "memory_auto_extract": SettingDef( 

722 bool, 

723 nullable=False, 

724 group=SettingGroup.MEMORY, 

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

726 ), 

727 "memory_top_k": SettingDef( 

728 int, 

729 nullable=False, 

730 group=SettingGroup.MEMORY, 

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

732 ), 

733 "memory_max_distance": SettingDef( 

734 float, 

735 nullable=False, 

736 group=SettingGroup.MEMORY, 

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

738 ), 

739 "memory_token_budget": SettingDef( 

740 int, 

741 nullable=False, 

742 group=SettingGroup.MEMORY, 

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

744 ), 

745 "memory_max_per_owner": SettingDef( 

746 int, 

747 nullable=False, 

748 group=SettingGroup.MEMORY, 

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

750 hidden=True, 

751 ), 

752 "memory_dedup_distance": SettingDef( 

753 float, 

754 nullable=False, 

755 group=SettingGroup.MEMORY, 

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

757 hidden=True, 

758 ), 

759 "crawl_max_depth": SettingDef( 

760 int, 

761 nullable=True, 

762 group=SettingGroup.CRAWLING, 

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

764 ), 

765 "crawl_render_mode": SettingDef( 

766 str, 

767 nullable=False, 

768 group=SettingGroup.CRAWLING, 

769 help_text=( 

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

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

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

773 ), 

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

775 ), 

776 "crawl_browser_recycle_pages": SettingDef( 

777 int, 

778 nullable=False, 

779 group=SettingGroup.CRAWLING, 

780 help_text=( 

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

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

783 ), 

784 ), 

785 "crawl_browser_extra_args": SettingDef( 

786 list, 

787 nullable=False, 

788 group=SettingGroup.CRAWLING, 

789 help_text=( 

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

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

792 ), 

793 ), 

794 "crawl_max_pages": SettingDef( 

795 int, 

796 nullable=True, 

797 group=SettingGroup.CRAWLING, 

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

799 ), 

800 "crawl_safety_max_pages": SettingDef( 

801 int, 

802 nullable=False, 

803 group=SettingGroup.CRAWLING, 

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

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

806 "larger sites unbounded.", 

807 ), 

808 "crawl_timeout": SettingDef( 

809 int, 

810 nullable=False, 

811 group=SettingGroup.CRAWLING, 

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

813 ), 

814 "crawl_sync_interval": SettingDef( 

815 int, 

816 nullable=False, 

817 group=SettingGroup.CRAWLING, 

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

819 ), 

820 "crawl_mean_delay": SettingDef( 

821 float, 

822 nullable=False, 

823 group=SettingGroup.CRAWLING, 

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

825 ), 

826 "crawl_max_delay_range": SettingDef( 

827 float, 

828 nullable=False, 

829 group=SettingGroup.CRAWLING, 

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

831 ), 

832 "crawl_concurrent_requests": SettingDef( 

833 int, 

834 nullable=False, 

835 group=SettingGroup.CRAWLING, 

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

837 ), 

838 "crawl_retry_on_rate_limit": SettingDef( 

839 bool, 

840 nullable=False, 

841 group=SettingGroup.CRAWLING, 

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

843 ), 

844 "crawl_retry_base_delay_min": SettingDef( 

845 float, 

846 nullable=False, 

847 group=SettingGroup.CRAWLING, 

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

849 ), 

850 "crawl_retry_base_delay_max": SettingDef( 

851 float, 

852 nullable=False, 

853 group=SettingGroup.CRAWLING, 

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

855 ), 

856 "crawl_retry_max_backoff": SettingDef( 

857 float, 

858 nullable=False, 

859 group=SettingGroup.CRAWLING, 

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

861 ), 

862 "crawl_retry_max_attempts": SettingDef( 

863 int, 

864 nullable=False, 

865 group=SettingGroup.CRAWLING, 

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

867 ), 

868 "crawl_exclude_patterns": SettingDef( 

869 list, 

870 nullable=False, 

871 group=SettingGroup.CRAWLING, 

872 validate_regex=True, 

873 help_text=( 

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

875 "recursive crawls. One per line." 

876 ), 

877 ), 

878 "openrouter_api_key": SettingDef( 

879 str, 

880 nullable=False, 

881 group=SettingGroup.API_KEYS, 

882 secret=True, 

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

884 ), 

885 "gemini_api_key": SettingDef( 

886 str, 

887 nullable=False, 

888 group=SettingGroup.API_KEYS, 

889 secret=True, 

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

891 ), 

892 "anthropic_api_key": SettingDef( 

893 str, 

894 nullable=False, 

895 group=SettingGroup.API_KEYS, 

896 secret=True, 

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

898 ), 

899 "openai_api_key": SettingDef( 

900 str, 

901 nullable=False, 

902 group=SettingGroup.API_KEYS, 

903 secret=True, 

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

905 ), 

906 "mistral_api_key": SettingDef( 

907 str, 

908 nullable=False, 

909 group=SettingGroup.API_KEYS, 

910 secret=True, 

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

912 ), 

913 "deepseek_api_key": SettingDef( 

914 str, 

915 nullable=False, 

916 group=SettingGroup.API_KEYS, 

917 secret=True, 

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

919 ), 

920 "llm_api_key": SettingDef( 

921 str, 

922 nullable=False, 

923 group=SettingGroup.API_KEYS, 

924 secret=True, 

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

926 ), 

927 "hf_token": SettingDef( 

928 str, 

929 nullable=False, 

930 group=SettingGroup.SYSTEM, 

931 secret=True, 

932 help_text=( 

933 "HuggingFace access token. Avoids the unauthenticated download " 

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

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

936 ), 

937 ), 

938 "chunk_size": SettingDef( 

939 int, 

940 nullable=False, 

941 group=SettingGroup.INGEST, 

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

943 ), 

944 "chunk_overlap": SettingDef( 

945 int, 

946 nullable=False, 

947 group=SettingGroup.INGEST, 

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

949 ), 

950 "max_chunks_per_file": SettingDef( 

951 int, 

952 nullable=False, 

953 group=SettingGroup.INGEST, 

954 help_text=( 

955 "Most chunks one file can add to the index; a file over the limit is skipped, " 

956 "not embedded (0 = no limit). Raise it for a long document such as a " 

957 "thousand-page manual at a small chunk_size, then retry skipped files" 

958 ), 

959 ), 

960 "tesseract_timeout": SettingDef( 

961 float, 

962 nullable=False, 

963 group=SettingGroup.INGEST, 

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

965 ), 

966 "ocr_language": SettingDef( 

967 list, 

968 nullable=False, 

969 group=SettingGroup.INGEST, 

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

971 ), 

972 "worker_pool_eager_start": SettingDef( 

973 bool, 

974 nullable=False, 

975 group=SettingGroup.INGEST, 

976 help_text=( 

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

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

979 ), 

980 ), 

981 "keep_engine_warm": SettingDef( 

982 bool, 

983 nullable=False, 

984 group=SettingGroup.SYSTEM, 

985 help_text=( 

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

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

988 ), 

989 ), 

990 "engine_idle_ttl_minutes": SettingDef( 

991 int, 

992 nullable=False, 

993 group=SettingGroup.SYSTEM, 

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

995 ), 

996 "agent_mcp_enabled": SettingDef( 

997 bool, 

998 nullable=False, 

999 group=SettingGroup.SYSTEM, 

1000 help_text=( 

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

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

1003 ), 

1004 ), 

1005 "max_tokens": SettingDef( 

1006 int, 

1007 nullable=True, 

1008 group=SettingGroup.GENERATION, 

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

1010 ), 

1011 "max_reasoning_chars": SettingDef( 

1012 int, 

1013 nullable=False, 

1014 group=SettingGroup.GENERATION, 

1015 help_text=( 

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

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

1018 ), 

1019 ), 

1020 "model_keep_alive": SettingDef( 

1021 int, 

1022 nullable=False, 

1023 group=SettingGroup.GENERATION, 

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

1025 ), 

1026 "gpu_memory_fraction": SettingDef( 

1027 float, 

1028 nullable=False, 

1029 group=SettingGroup.GENERATION, 

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

1031 ), 

1032 "usable_vram_fraction": SettingDef( 

1033 float, 

1034 nullable=False, 

1035 group=SettingGroup.GENERATION, 

1036 help_text=( 

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

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

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

1040 ), 

1041 ), 

1042 "system_memory_reserve_gb": SettingDef( 

1043 float, 

1044 nullable=False, 

1045 group=SettingGroup.GENERATION, 

1046 help_text=( 

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

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

1049 ), 

1050 ), 

1051 "embed_replicas": SettingDef( 

1052 int, 

1053 nullable=False, 

1054 group=SettingGroup.GENERATION, 

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

1056 ), 

1057 "vision_replicas": SettingDef( 

1058 int, 

1059 nullable=False, 

1060 group=SettingGroup.GENERATION, 

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

1062 ), 

1063 "candidate_multiplier": SettingDef( 

1064 int, 

1065 nullable=False, 

1066 group=SettingGroup.RETRIEVAL, 

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

1068 ), 

1069 "title_search": SettingDef( 

1070 bool, 

1071 nullable=False, 

1072 group=SettingGroup.RETRIEVAL, 

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

1074 ), 

1075 "title_search_weight": SettingDef( 

1076 float, 

1077 nullable=False, 

1078 group=SettingGroup.RETRIEVAL, 

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

1080 ), 

1081 "lexical_fusion_weight": SettingDef( 

1082 float, 

1083 nullable=False, 

1084 group=SettingGroup.RETRIEVAL, 

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

1086 ), 

1087 "adaptive_fusion": SettingDef( 

1088 bool, 

1089 nullable=False, 

1090 group=SettingGroup.RETRIEVAL, 

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

1092 ), 

1093 "adaptive_fusion_margin": SettingDef( 

1094 float, 

1095 nullable=False, 

1096 group=SettingGroup.RETRIEVAL, 

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

1098 ), 

1099 "filter_structural_chunks": SettingDef( 

1100 bool, 

1101 nullable=False, 

1102 group=SettingGroup.RETRIEVAL, 

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

1104 ), 

1105 "fts_language": SettingDef( 

1106 str, 

1107 nullable=False, 

1108 group=SettingGroup.RETRIEVAL, 

1109 choices=tuple(sorted(FTS_LANGUAGES)), 

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

1111 ), 

1112 "embed_titles": SettingDef( 

1113 bool, 

1114 nullable=False, 

1115 group=SettingGroup.RETRIEVAL, 

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

1117 ), 

1118 "contextual_enrichment": SettingDef( 

1119 bool, 

1120 nullable=False, 

1121 group=SettingGroup.RETRIEVAL, 

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

1123 ), 

1124 "history_rewrite": SettingDef( 

1125 bool, 

1126 nullable=False, 

1127 group=SettingGroup.RETRIEVAL, 

1128 help_text=( 

1129 "Rewrite a follow-up that refers to earlier turns into a standalone " 

1130 "retrieval query (one extra chat-model call on those turns)" 

1131 ), 

1132 ), 

1133 "intent_routing": SettingDef( 

1134 bool, 

1135 nullable=False, 

1136 group=SettingGroup.RETRIEVAL, 

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

1138 ), 

1139 "intent_llm": SettingDef( 

1140 bool, 

1141 nullable=False, 

1142 group=SettingGroup.RETRIEVAL, 

1143 help_text=( 

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

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

1146 "LLM call to those turns)" 

1147 ), 

1148 ), 

1149 "ann_index_threshold": SettingDef( 

1150 int, 

1151 nullable=False, 

1152 group=SettingGroup.RETRIEVAL, 

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

1154 ), 

1155 "max_distance": SettingDef( 

1156 float, 

1157 nullable=False, 

1158 group=SettingGroup.RETRIEVAL, 

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

1160 ), 

1161 "min_relevance_score": SettingDef( 

1162 float, 

1163 nullable=False, 

1164 group=SettingGroup.RETRIEVAL, 

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

1166 ), 

1167 "max_context_sources": SettingDef( 

1168 int, 

1169 nullable=False, 

1170 group=SettingGroup.RETRIEVAL, 

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

1172 ), 

1173 "neighbor_expansion": SettingDef( 

1174 int, 

1175 nullable=False, 

1176 group=SettingGroup.RETRIEVAL, 

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

1178 ), 

1179 "diversity_max_per_source": SettingDef( 

1180 int, 

1181 nullable=False, 

1182 group=SettingGroup.RETRIEVAL, 

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

1184 ), 

1185 "mmr_lambda": SettingDef( 

1186 float, 

1187 nullable=False, 

1188 group=SettingGroup.RETRIEVAL, 

1189 help_text=( 

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

1191 ), 

1192 ), 

1193 "temporal_filtering": SettingDef( 

1194 bool, 

1195 nullable=False, 

1196 group=SettingGroup.RETRIEVAL, 

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

1198 ), 

1199 "hyde": SettingDef( 

1200 bool, 

1201 nullable=False, 

1202 group=SettingGroup.RETRIEVAL, 

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

1204 ), 

1205 "hyde_weight": SettingDef( 

1206 float, 

1207 nullable=False, 

1208 group=SettingGroup.RETRIEVAL, 

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

1210 ), 

1211 "query_expansion_count": SettingDef( 

1212 int, 

1213 nullable=False, 

1214 group=SettingGroup.RETRIEVAL, 

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

1216 ), 

1217 "expansion_similarity_threshold": SettingDef( 

1218 float, 

1219 nullable=False, 

1220 group=SettingGroup.RETRIEVAL, 

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

1222 ), 

1223 "expansion_short_query_tokens": SettingDef( 

1224 int, 

1225 nullable=False, 

1226 group=SettingGroup.RETRIEVAL, 

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

1228 ), 

1229 "expansion_guardrails": SettingDef( 

1230 bool, 

1231 nullable=False, 

1232 group=SettingGroup.RETRIEVAL, 

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

1234 ), 

1235 "adaptive_threshold": SettingDef( 

1236 bool, 

1237 nullable=False, 

1238 group=SettingGroup.RETRIEVAL, 

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

1240 ), 

1241 "adaptive_threshold_step": SettingDef( 

1242 float, 

1243 nullable=False, 

1244 group=SettingGroup.RETRIEVAL, 

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

1246 ), 

1247 "concept_graph": SettingDef( 

1248 bool, 

1249 nullable=False, 

1250 group=SettingGroup.RETRIEVAL, 

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

1252 ), 

1253 "concept_boost_weight": SettingDef( 

1254 float, 

1255 nullable=False, 

1256 group=SettingGroup.RETRIEVAL, 

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

1258 ), 

1259 "concept_max_per_chunk": SettingDef( 

1260 int, 

1261 nullable=False, 

1262 group=SettingGroup.RETRIEVAL, 

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

1264 ), 

1265 "documents_dir": SettingDef( 

1266 str, 

1267 nullable=False, 

1268 group=SettingGroup.SYSTEM, 

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

1270 ), 

1271 "vault_base": SettingDef( 

1272 str, 

1273 nullable=True, 

1274 group=SettingGroup.SYSTEM, 

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

1276 ), 

1277 "sse_heartbeat_interval": SettingDef( 

1278 float, 

1279 nullable=False, 

1280 group=SettingGroup.SYSTEM, 

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

1282 hidden=True, 

1283 ), 

1284 "llm_provider": SettingDef( 

1285 str, 

1286 nullable=False, 

1287 group=SettingGroup.API_KEYS, 

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

1289 help_text=( 

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

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

1292 ), 

1293 ), 

1294 "ollama_base_url": SettingDef( 

1295 str, 

1296 nullable=False, 

1297 group=SettingGroup.LOCAL_SERVERS, 

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

1299 ), 

1300 "lm_studio_base_url": SettingDef( 

1301 str, 

1302 nullable=False, 

1303 group=SettingGroup.LOCAL_SERVERS, 

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

1305 ), 

1306 "llama_server_path": SettingDef( 

1307 str, 

1308 nullable=False, 

1309 group=SettingGroup.API_KEYS, 

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

1311 ), 

1312 "wiki_summary_max_tokens": SettingDef( 

1313 int, 

1314 nullable=False, 

1315 group=SettingGroup.WIKI, 

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

1317 ), 

1318 "wiki_temperature": SettingDef( 

1319 float, 

1320 nullable=False, 

1321 group=SettingGroup.WIKI, 

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

1323 ), 

1324}