phase: 178_split_thinking_levels
## Phase 178 — final verification pass: 100% green
**Verified (all 5 tasks complete in `complete/178_split_thinking_levels/`):**
- `config.py` rename (`llm_chat_thinking_level` default `low`) + new `llm_summary_thinking_level` (default `""`); two `CATALOG` rows; `.env.example` replaced; no stale old-key references (only intentional negative pins in tests)
- Wire split: `chat_stream()` → chat level, one-shot `chat()` → summary level (absent `extra_body` when empty); per-turn line reads chat level only
**Gates:**
- `uv run pytest -k "config and thinking" -v` → 7 passed (defaults + env overrides both knobs)
- `uv run pytest` (unit + integration) → 4170 tests, exit 0; `--cov=app` → **99%** (>90% ✓)
- `uv run ruff check .` clean; `uv run pyright` clean
- `uv run pytest tests/e2e/test_split_thinking_levels.py -v --no-cov` → 3 passed in isolation
- `test_admin_settings_panel.py` + `test_admin_settings_layout.py` → 17 passed in isolation
**Completion criteria:** all 6 test/doc criteria ✅ (admin GET lists both keys, old key gone — integration + E2E pins); commit + phase-dir move = harness's job.
**Documentation:** README.md — both new env vars correctly documented in the LLM (aipi) table with defaults/wire-mapping/off-rule, defaults match `config.py`, all `.env.example` vars covered: verified consistent, no changes needed. AGENTS.md — all referenced files/commands verified to exist, no phase-178 impact (no UI code change, A4): verified consistent, no changes needed.
**Decisions:** none required — no live changes made (E2E booted its own app subprocess against the existing DB).
**Next pending phase:** none — 178 is the last phase in `todo/`.
This commit is contained in:
@@ -10,9 +10,10 @@ snapshot, ``app/api/chat.py::get_llm``), and the chat concurrency
|
||||
semaphore (``chat_max_concurrent``). Plus the kill-switch counterpart
|
||||
(``BOR_SETTINGS_DB=0`` with rows present → env values are back) and
|
||||
the ``scripts/``-consumer surface (``import_extension_set``), and the
|
||||
phase-172 thinking level (``llm_thinking_level`` — the value the LLM
|
||||
layer reads per request via ``get_settings()``; the off switch is
|
||||
live-settable, A2).
|
||||
phase-172 thinking level (``llm_chat_thinking_level`` — the value the
|
||||
LLM layer reads per request via ``get_settings()``; the off switch is
|
||||
live-settable, A2) and its phase-178 summary twin (task 02:
|
||||
``llm_summary_thinking_level``, default ``""`` = off, A2).
|
||||
|
||||
The rows are inserted directly through the ``db`` session (the admin
|
||||
API is task 04); the save itself is ``invalidate_settings()`` —
|
||||
@@ -164,44 +165,88 @@ def test_llm_client_repoints_on_the_next_save(db: Session) -> None:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# the thinking level: save/reset resolve the live value (phase 172, task 02)
|
||||
# the chat thinking level: save/reset resolve the live value
|
||||
# (phase 178, task 01 — the phase-172 test, renamed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_llm_thinking_level_live_apply_no_restart(
|
||||
def test_llm_chat_thinking_level_live_apply_no_restart(
|
||||
db: Session, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Phase 172 (task 02): the thinking level follows the overlay
|
||||
"""Phase 178 (task 01): the chat thinking level follows the overlay
|
||||
live — a save (the PUT's write, minus the API: ``_save`` +
|
||||
``invalidate_settings()`` is exactly what task 04's PUT/DELETE
|
||||
call after the write) resolves
|
||||
``get_settings().llm_thinking_level`` WITHOUT a restart (the proof
|
||||
the LLM layer — task 03 reads the setting per request via
|
||||
``get_settings().llm_chat_thinking_level`` WITHOUT a restart (the
|
||||
proof the LLM layer — task 03 reads the setting per request via
|
||||
``get_settings()`` — picks the override up live), the EMPTY value
|
||||
resolves live too (the off switch is live-settable, A2/A4), and a
|
||||
reset (the DELETE) returns the env value.
|
||||
"""
|
||||
# A deterministic base (machine-independent, the kill-switch test's
|
||||
# idiom): the code default, no row in the table.
|
||||
monkeypatch.delenv("BOR_LLM_THINKING_LEVEL", raising=False)
|
||||
monkeypatch.delenv("BOR_LLM_CHAT_THINKING_LEVEL", raising=False)
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_thinking_level == "low"
|
||||
_save(db, "llm_thinking_level", "high")
|
||||
assert get_settings().llm_chat_thinking_level == "low"
|
||||
_save(db, "llm_chat_thinking_level", "high")
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_thinking_level == "high" # NO restart
|
||||
assert get_settings().llm_chat_thinking_level == "high" # NO restart
|
||||
# The off switch is live-settable too (A2/A4): the empty value
|
||||
# resolves through the overlay, not stripped or coerced.
|
||||
_save(db, "llm_thinking_level", "")
|
||||
_save(db, "llm_chat_thinking_level", "")
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_thinking_level == ""
|
||||
assert get_settings().llm_chat_thinking_level == ""
|
||||
# The reset returns the env value (no process restart in between —
|
||||
# the LLM layer re-reads per request, task 03).
|
||||
db.execute(
|
||||
text("DELETE FROM app_settings WHERE key = 'llm_thinking_level'")
|
||||
text("DELETE FROM app_settings WHERE key = 'llm_chat_thinking_level'")
|
||||
)
|
||||
db.commit()
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_thinking_level == "low"
|
||||
assert get_settings().llm_chat_thinking_level == "low"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# the summary thinking level: save/reset resolve the live value
|
||||
# (phase 178, task 02 — the chat twin's summary counterpart)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_llm_summary_thinking_level_live_apply_no_restart(
|
||||
db: Session, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Phase 178 (task 02): the summary thinking level follows the
|
||||
overlay live — a save (the PUT's write, minus the API: ``_save`` +
|
||||
``invalidate_settings()`` is exactly what task 04's PUT/DELETE call
|
||||
after the write) resolves ``get_settings().llm_summary_thinking_level``
|
||||
WITHOUT a restart (the proof the LLM layer — task 03 reads the
|
||||
setting per request via ``get_settings()`` — picks the override up
|
||||
live), the EMPTY value resolves live too (the off switch is
|
||||
live-settable, A2/A4), and a reset (the DELETE) returns the env
|
||||
value (``""`` — the code default; the chat twin's reset returns
|
||||
``"low"``, the two levels are independent).
|
||||
"""
|
||||
# A deterministic base (machine-independent, the kill-switch test's
|
||||
# idiom): the code default, no row in the table.
|
||||
monkeypatch.delenv("BOR_LLM_SUMMARY_THINKING_LEVEL", raising=False)
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_summary_thinking_level == ""
|
||||
_save(db, "llm_summary_thinking_level", "high")
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_summary_thinking_level == "high" # NO restart
|
||||
# The off switch is live-settable too (A2/A4): the empty value
|
||||
# resolves through the overlay, not stripped or coerced.
|
||||
_save(db, "llm_summary_thinking_level", "")
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_summary_thinking_level == ""
|
||||
# The reset returns the env value (no process restart in between —
|
||||
# the LLM layer re-reads per request, task 03).
|
||||
db.execute(
|
||||
text("DELETE FROM app_settings WHERE key = 'llm_summary_thinking_level'")
|
||||
)
|
||||
db.commit()
|
||||
invalidate_settings()
|
||||
assert get_settings().llm_summary_thinking_level == ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user