[Backend] Pass LLMConfig through the generation paths instead of destructuring it into loose parameters #484
Closed
opened 2026-09-01 20:20:51 +00:00 by claude-bot
·
2 comments
No Branch/Tag specified
main
feat/1328-fill-it-in
feat/1328-fill-frontend
feat/1328-fill-backend
feat/9-foundry-push
fix/9-foundry-live
feat/9-foundry-push-frontend
renovate/celery-5.x
feat/5-foundry-connect
feat/9-foundry-push-backend
feat/9-foundry-push-module
feat/5-foundry-frontend
feat/5-foundry-backend
feat/5-foundry-module
release/v4.8.0-rc1
docs/d341-convert-draft
feat/958-update-to-version
feat/958-convert-sheet
feat/958-pathbuilder-import-frontend
fix/1360-heading-order
feat/958-pathbuilder-import-backend
feat/11-weekly-digest
feat/1341-auto-update
docs/d332-superseded
chore/1345-names-cleanup
feat/1342-mentioned-pages
feat/1328-recipes
fix/1346-pgdg-key
perf/1247-shared-recording-stream
chore/132-gitleaks-allowlist
release/v4.7.1
spike/foundry-module
release/v4.7.1-rc1
docs/decisions-d332-d337
fix/bot-certifi
chore/release-4.7.0
chore/release-4.7.0-rc4
chore/deps-4.7.0
chore/release-4.7.0-rc3
fix/1280-quote-must-say-it
fix/1257-names-row-squeeze
chore/release-4.7.0-rc2
fix/1245-pg-upgrade-count
chore/release-4.7.0-rc1
feat/1257-names-in-prep
feat/1257-be
feat/1257-fe
feat/1257-rating-fixes
design/1328-fill-it-in
feat/1257-name-splits
fix/1326-redis-503
feat/1280-fact-suggestions
feat/1280-be
feat/1280-fe
design/1280-fact-suggestions
feat/1279-infobox
feat/1279-be
feat/1279-fe
feat/1321-shelf-copies
design/1279-infobox
feat/1243-table-find
feat/1257-who-rated
feat/1243-prep-drawer
feat/1243-sheet-pickers
design/1243-browse-in-place
feat/1315-hotwords-default
feat/1242-system-filters
feat/1257-more-name-styles
feat/506-leg3-hotwords
design/1242-system-filters
fix/1302-packs-polish
feat/1241-rules-library
design/1241-rules-library
feat/1245-postgres-18
integ/4.6.2
fix/1263-map-ui-2
fix/1263-map-server
fix/1263-map-ui-1
fix/1261-editor-link-menu
fix/1229-axe-4.14-name-mismatch
renovate/axllent-mailpit-1.x
renovate/eslint-monorepo
renovate/authlib-1.x
chore/release-4.6.0
chore/release-4.6.0-rc2
fix/460-rc2-screens
fix/460-rc2-server
fix/release-prerelease-exit
chore/release-4.6.0-rc1
feat/125-campaign-templates
feat/126-calendar
feat/1181-pictures-and-files
feat/1190-event-people
integ/4.6.0
feat/1191-line-of-the-night
feat/1200-starter-packs
chore/bot-multidict-and-playwright-group
fix/1202-this-week-answered-count
fix/122-124-leftovers
fix/1178-get-routes-that-write
fix/1203-journeys-check-names
design/125-campaign-templates
feat/126-calendar-engine
design/126-in-world-calendar
fix/1189-cited-stamp-in-range
fix/1189-v4-looser-check
fix/1189-richer-beats
fix/1113-export-bind-lists
fix/1154-release-notes-by-file
chore/release-4.5.0
chore/release-4.5.0-rc12
fix/1135-wiki-markup-as-text
fix/demo-usage-cost-in-window
chore/release-4.5.0-rc11
fix/1121-sheet-consistency
fix/1121-sheet-5e-terms-and-card-hp
copy/1122-busiest-pages
fix/1121-relevant-choices
fix/1121-wiki-create-npc-save-sheet-links
fix/1029-menu-stays-on-screen
docs/prototype-transcript-bin-wording
fix/1023-editor-save-row-back-scroll
fix/1020-search-exact-first
fix/1020-table-prep
fix/1020-shell-wordmark-footer-bell
fix/1020-wiki-article-party
fix/872-admin-kept-until-phone-tables
feat/l13-milestone-level-waiting
fix/1000-1001-bell-older-setup-context
fix/999-pronouns-in-prompts
fix/l19-other-class-feats
fix/1010-transcript-window-ordinary-confirm
docs/d254-d269-decisions
fix/702-member-search
fix/680-transcript-bin
fix/993-unidentified-prompts
fix/841-redis-timeouts
fix/990-binned-not-exported
fix/830-export-prep-shelf
fix/deps-audit-2026-10-01
fix/877-title-reads
fix/798-audio-received-at
build/lane11-17-j7
build/lane11-16-levelup
build/lane11-14-seams
build/lane11-14-seed
build/lane11-15-start
build/lane11-13-old-block-out
fix/980-review-r3
build/lane11-12-block
build/lane11-8-packs
fix/demo-seed-canonical-names
build/lane11-11-hot-half
docs/l11-decisions
build/lane11-7-convert
build/lane11-10-editing
fix/npm-audit-brace-expansion
build/lane11-9-sheet-page
fix/bot-pyjwt
build/lane11-6-content
fix/lane11-rank-names
build/lane11-2b-language
build/lane11-4-js-evaluator
build/lane11-3-server
build/lane11-5a-pf2e
build/lane11-5b-5e
feat/design-admin
build/lane11-2-evaluator
build/lane11-1-sheet-pieces
build/lane11-0-drawing
fix/943-quest-objectives-fk
fix/prepsheet-shelf-flake
fix/945-leftovers
build/demo-gm-copy
fix/901-email-landings
build/lane10-8-journeys
build/demo-seed-lanes-8-10
fix/887-gm-own-confirmation
build/lane9-5-j1-admin
fix/918-audience-click
fix/useasync-overtaken
ci/939-critical-path
test/937-backend-suite-speed
chore/claude-orchestration
fix/924-xp-gm-only
fix/session-stale-poll
docs/decisions-d239-d242
fix/930-pypdf
build/lane8-journeys
build/925-quest-editor
fix/894-private-page-pictures
build/lane8-p4-drafts
build/lane8-p7-map
build/lane8-p6-handouts-rules
fix/wiki-fixture-names
build/lane10-d227-test-usage
fix/rsvp-horizon-test-tuesday
fix/media-volume-owner
build/lane8-p3-editor
fix/912-unidentified-rename
build/lane8-p5-party-quests
build/lane8-handout-takeback
build/lane10-pickers
build/lane8-p2-article
build/lane8-6-map
build/lane10-7b-email-pages
build/lane8-5-handouts
build/lane10-7a-test-the-bot-pages
build/lane8-p1-pages
build/lane8-4-quests
build/lane8-2b-feeds
build/lane8-3-parties
build/lane8-2a-links
build/lane10-5-commands
build/lane9-6-seed
build/lane10-4b-reactions
build/lane9-4d-pages
build/lane9-4c-pages
build/lane10-4a-posts
build/lane10-3-email
fix/891-web-fonts
build/lane9-4b-pages
build/lane10-6-test-the-bot
build/lane10-2-notify-out
build/lane8-1-wiki-text
build/lane9-4a-frame
design/lane10-drawings
fix/j6-budget
fix/j5-phone-flake
fix/869-test-leaks
fix/843-record-start-followups
build/lane7-all
build/lane6-all
build/lane9-b2-server
chore/changelog-fragments
docs/decisions-d218
build/lane9-a-owner-overview
fix/rule-card-level-label
fix/834-retention-semantics
fix/o3c-record-without-announcement
docs/decisions-d179
docs/decisions-d131-d170
fix/851-commit-before-response
build/lane6-j3-seed
build/lane9-b1-d67-identity
fix/841-blocking-loop
build/lane9-c-backups-audit
fix/vote-race-and-start-commit
fix/recording-announcement
fix/proposal-read-leak
fix/last-admin-guard
build/lane6-p4c-planning
build/lane6-p4b-margin
build/lane6-p4a-sheet
build/lane5-j4-seed
build/lane6-base
build/lane7-pages-talk
build/lane5-pages
build/lane7-pages-safety
build/lane7-p3-safety
build/lane7-p2-notify
build/lane6-p3-planning
build/lane6-p2-history
build/lane6-p1b-items
build/lane7-p1-threads
build/lane5-p6-faces
build/lane6-p1a-sheet
build/lane5-p5-find
build/lane5-p4a-board
build/lane5-p4b-scenes
build/lane5-p3-live
build/lane5-base
build/lane5-p2-cards
docs/lane5-decisions
build/review-r2-wide
fix/review-r1
build/lane5-p1-scenes
build/demo-seed
build/lane4-journeys-j5
build/lane4-page-review-b
build/lane4-page-review-a
build/lane4-page-read
build/lane4-journeys
build/lane4-page-sess2
build/lane4-page-sess1
build/lane4-notes
build/lane4-discord-on-approval
build/lane4-scheduling
build/lane4-versions
build/760-session-number
build/lane4-decisions
build/797-erase-no-regenerate
build/test-race-audit
build/lane3-completions
build/794-dark-only
build/796-titles-scroll
build/791-retention-anchor
build/a11y-sweep
build/shell-followups
build/retention-preview
build/753-backup-readback
docs/d91-d93
build/lane3-j1
build/ci-harness-paths-selftest
fix/useauth-test-race
build/ci-harness-paths
build/777-ai-settings-read
build/lane3-settings-c
build/lane3-settings-b
build/lane3-j6
build/lane3-sessionform
build/lane3-settings-a
build/lane3-members
build/lane3-thisweek
build/lane3-story
chore/87-align-python-3.12
v4.8.0-rc1
v4.7.1
v4.7.1-rc1
v4.7.0
v4.7.0-rc4
v4.7.0-rc3
v4.7.0-rc2
v4.7.0-rc1
v4.6.2
v4.6.2-rc4
v4.6.2-rc3
v4.6.2-rc2
v4.6.2-rc1
v4.6.1
v4.6.1-rc1
v4.6.0
v4.6.0-rc2
v4.6.0-rc1
v4.5.3
v4.5.3-rc1
v4.5.2
v4.5.2-rc1
v4.5.1
v4.5.1-rc2
v4.5.1-rc1
v4.5.0
v4.5.0-rc12
v4.5.0-rc11
v4.5.0-rc10
v4.5.0-rc9
v4.5.0-rc8
v4.5.0-rc7
v4.5.0-rc6
v4.5.0-rc5
v4.5.0-rc4
v4.5.0-rc3
v4.5.0-rc2
v4.5.0-rc1
v4.4.1
v4.4.0
v4.3.0
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.2
v4.1.1
v4.1.0
v4.0.1
v4.0.0
v3.11.4
v3.11.3
v3.11.2
v3.11.1
v3.11.0
v3.10.0
v3.9.1
v3.9.0
v3.8.0
v3.7.0
v3.6.1
v3.6.0
v3.5.0
v3.4.1
v3.4.0
v3.3.0
v3.2.0
v3.1.0
v3.0.0
v2.1.0
v2.0.0
v1.1.0
v1.0.0
v0.12.0
v0.11.0
v0.10.0
v0.9.1
v0.3.3
v0.9.0
v0.8.0
v0.3.2
v0.3.1
v0.3.0
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.2.1
v0.2.0
v0.1.1
v0.1.0
Labels
Clear labels
backend
Issue related to the backend
bot
Issue related to the bot
bug
Something is not working
duplicate
This issue or pull request already exists
enhancement
New feature
frontend
Issue related to the frontend
help wanted
Need some help
invalid
Something is wrong
question
More information is needed
recording
Issue related to the recording system
security
Security hardening / vulnerability work
wontfix
This won't be fixed
No labels
backend
bot
bug
duplicate
enhancement
frontend
help wanted
invalid
question
recording
security
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Quest-Board#484
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while implementing #351, and deliberately not fixed there — see the discussion on PR #483.
The pattern
Every generation path takes the LLM target as four separate parameters —
endpoint_url,api_key,model,context_tokens— destructured fromLLMConfigat the boundary and re-threaded by hand through every helper. As of#351 it is five, because
llm_providerhad to travel the same way.Concretely, adding one field to
LLMConfigcost:llm_provider=llm_cfg.provider)That is ~58 edits and four rounds of test-double breakage for a field that
already existed on an object every one of those call sites started from.
Why it matters beyond the churn
The repo already knows this does not hold. From
DEFAULT_CONTEXT_TOKENS(llm_service.py:79-82):
That comment was written about
context_tokensand it is now equally true ofllm_provider. The mitigation both times was to make the absence benign — aconservative default window, a fallback to URL sniffing — which works, but it
means the safety comes from the default rather than from the structure, and the
next field may not have a benign default.
context_tokensis the cautionary case: its silent-absence bug (#337) shippedbecause seventeen call sites did not opt in, and the fix was to make the
parameter impossible to omit meaningfully, not impossible to omit.
Proposed fix
Pass the config object (or a small frozen
LlmTarget) instead of its fields.A new call site then cannot forget a field of an object it is already holding,
and adding a sixth attribute is one edit rather than fifty-eight.
Rough shape:
LlmTarget(endpoint_url, api_key, model, context_tokens, provider)— or justreuse
LLMConfig, which is already exactly this.generate_structured_text(prompt, target, *, system_prompt, ...), keeping theloose-parameter form as a thin deprecated shim during migration so the change
can land in reviewable pieces rather than one 6-file commit.
audio_service,beat_service,lore_service,generation_service,reminder_tasksandplanning_taskstaketargetandpass it on unchanged.
Scope notes
to do and easy to verify: the existing suite should pass untouched apart from
the doubles.
LLMConfiginstances rather thanSimpleNamespace, which is what let them drift from the dataclass in thefirst place (#441's concern — a double that diverges is a test that passes
while production breaks).
_qa_*functions inaudio_servicebuild their own request bodiesand bypass the transport entirely (#426). They should be folded in at the same
time, or the count goes back up.
Milestone
Filed under v4.6.0 with the rest of the simplification work (#443), but it
gets cheaper the sooner it happens — v4.2.0 and v4.3.0 will both add
generation call sites, and each one is another hand-threaded copy. Worth pulling
forward if there is an appetite for it before then.
Picking this up now, ahead of the rest of v4.2.0, and moving it from v4.6.0 to v4.2.0. Reasoning: #356, #357 and #358 each need the provider object at the generation call sites, so every one of them done before this adds another hand-threaded parameter through the same signatures. Doing this first makes each of those a one-object change.
Measured on
mainat233dc71(post-#483)endpoint_url: stracrossaudio_service,beat_service,generation_service,lore_service,reminder_tasks,planning_tasksgenerate_structured_textcall sitesllm_provider=occurrencesgenerate_structured_text_qa_*this issue names and the four_summarise_*get_llm_config/require_llm_configApproach
LLMConfigas the target. It is already exactly the five fields; a second type would be one more thing to keep in sync.generate_structured_text(prompt, target, *, ...)with the loose form removed outright, not shimmed. The point of this issue is that a new call site cannot omit a field of an object it is holding; a deprecated shim keeps the omit-able path alive for exactly the "eighteenth call site written next month" theDEFAULT_CONTEXT_TOKENScomment warns about. All call sites are in this repo, so there is nothing for a shim to protect.target: LLMConfigand pass it on unchanged. Origin sites stop destructuring.targettoo, so the count cannot climb back up. Merging them into one transport is out of scope here — that is the simplification pass (#443) and it changes behaviour surface; this issue does not.LLMConfiginstances.Will land as one PR, committed in reviewable pieces (transport →
audio_service→ other services → tasks/routers → tests).claude-bot referenced this issue2026-09-05 00:17:15 +00:00
Landed in PR #489 (merged 2026-09-05, CI green). Nine commits, 45 files, 1716 tests passing.
What was done
LLMConfignow lives inapp/providers/llm.py, frozen, next toLlmCapabilities/LlmProvider, withDEFAULT_CONTEXT_TOKENSand its rationale block. It had to move:settings_serviceimports fromllm_service, so the transport could not take the config from settings without a cycle.settings_servicere-exports the name.registry.llm_provider_for(target)is the one-object resolve;resolve_llm_providerstays for the two callers that genuinely hold the fields loose.generate_structured_text(prompt, target, *, ...)— the loose quintet is gone with no shim, and aninspect.signatureguard test keeps it gone. All four_structured_*, four_summarise_*, four_qa_*and four_test_llm_*transports taketargettoo, so the destructuring did not just move one frame down.audio_serviceplusbeat_service,generation_service,lore_service,reminder_tasks,planning_tasksand two routers converted; origin sites passllm_cfgwhole. The eight Whisper ASR functions were deliberately left alone (#350).evals/LiveProviderwas the one caller outsideapp/and had no test cover; it now carries anLLMConfigand gainedQB_EVAL_PROVIDER.LLMConfiginstances everywhere; noSimpleNamespacelook-alikes remain.endpoint_url: strsignatures inapp/llm_provider=inapp/Verified no behaviour change
An AST walk over the pre-change tree found zero production call sites reaching the transport with
context_tokensomitted (45 in tests, all migrated), so no production path's effective window moved. The three places the window is now read offprovider.capabilities.context_tokensare identical to the oldresolve_window(...)for any positive int, andget_llm_configalways resolves one.Found on the way
#488 —
_dispatch_proseandtest_llmaccepted the explicit provider and ignored it. Fixed in the same PR as its own commit; see that issue.Left open, on purpose
LLMConfigbeing frozen means a variant is made withdataclasses.replace;evals/providers.py:with_windowis the first user.