fix(llm,web): chat and completion prompts keep the active model's full context, embedding cold starts and deploy windows stop breaking chat, and API/error responses report real status codes - #52
Conversation
…dates Bumps the npm_and_yarn group with 7 updates in the /frontend directory: | Package | From | To | | --- | --- | --- | | [dompurify](https://github.com/cure53/DOMPurify) | `3.3.1` | `3.4.0` | | [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) | `5.48.0` | `5.55.7` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `6.4.1` | `6.4.2` | | [minimatch](https://github.com/isaacs/minimatch) | `3.1.2` | `3.1.5` | | [minimatch](https://github.com/isaacs/minimatch) | `9.0.5` | `9.0.9` | | [flatted](https://github.com/WebReflection/flatted) | `3.3.3` | `3.4.2` | | [picomatch](https://github.com/micromatch/picomatch) | `2.3.1` | `4.0.4` | | [rollup](https://github.com/rollup/rollup) | `4.56.0` | `4.60.4` | Updates `dompurify` from 3.3.1 to 3.4.0 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.3.1...3.4.0) Updates `svelte` from 5.48.0 to 5.55.7 - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.55.7/packages/svelte) Updates `vite` from 6.4.1 to 6.4.2 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite) Updates `minimatch` from 3.1.2 to 3.1.5 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) Updates `minimatch` from 9.0.5 to 9.0.9 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) Updates `devalue` from 5.6.2 to 5.8.1 - [Release notes](https://github.com/sveltejs/devalue/releases) - [Changelog](https://github.com/sveltejs/devalue/blob/main/CHANGELOG.md) - [Commits](sveltejs/devalue@v5.6.2...v5.8.1) Updates `flatted` from 3.3.3 to 3.4.2 - [Commits](WebReflection/flatted@v3.3.3...v3.4.2) Updates `picomatch` from 2.3.1 to 4.0.4 - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](micromatch/picomatch@2.3.1...4.0.4) Updates `rollup` from 4.56.0 to 4.60.4 - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](rollup/rollup@v4.56.0...v4.60.4) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.0 dependency-type: direct:production dependency-group: npm_and_yarn - dependency-name: svelte dependency-version: 5.55.7 dependency-type: direct:development dependency-group: npm_and_yarn - dependency-name: vite dependency-version: 6.4.2 dependency-type: direct:development dependency-group: npm_and_yarn - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: minimatch dependency-version: 9.0.9 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: devalue dependency-version: 5.8.1 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: flatted dependency-version: 3.4.2 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: picomatch dependency-version: 4.0.4 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: rollup dependency-version: 4.60.4 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com>
Scope completion truncation to active provider
…ween chats Live chat turns paid serverless cold starts on the remote embedding provider: 53.5s observed in production, 23.7s cold vs 1.0s warm reproduced against the same provider. A scheduled probe every 4 minutes (below common scale-to-zero windows) plus a startup warmup keeps the model resident; probes exceeding 5s log WARN as evidence the model went cold, and probe failures are logged without affecting request handling.
…-scoped fix The no-arg truncatePromptForCompletion lost its only production caller in #49 and silently assumed the OPENAI provider; the provider-arg overload was reachable only from tests. Both removed per [AB1d]; the regression tests now exercise buildCompletionRequest, covering the full provider-to-model-id truncation path.
…PT-5's 7K limit
The 7K truncation cap exists for GPT-5's 8K input tier on GitHub
Models; o-series reasoning models accept >=128K input tokens, so the
canonicalModelName startsWith("o") clause silently truncated prompts
that fit. The limit now follows the GPT-5 family check alone; the
reasoning-model distinction still governs temperature omission in
buildResponseParams.📝 WalkthroughWalkthroughAdds a scheduled EmbeddingModelKeepAlive component to probe embedding providers and moves completion prompt truncation into OpenAiRequestFactory so truncation uses the resolved model’s token limits; also updates a few frontend dependency versions. ChangesEmbedding Model Keep-Alive
Provider-Aware Prompt Truncation
Frontend dependency updates
Sequence Diagram(s)sequenceDiagram
participant SpringScheduler as Spring Scheduler
participant KeepAlive as EmbeddingModelKeepAlive
participant EmbedClient as EmbeddingClient
SpringScheduler->>KeepAlive: keepEmbeddingModelWarm() (scheduled)
KeepAlive->>KeepAlive: record probe start time
KeepAlive->>EmbedClient: embed(probe text)
alt Provider Available
EmbedClient-->>KeepAlive: embedding result
KeepAlive->>KeepAlive: compare latency to cold-start threshold
KeepAlive->>KeepAlive: log WARN (cold) or DEBUG (warm)
else Provider Unavailable
EmbedClient-->>KeepAlive: EmbeddingServiceUnavailableException
KeepAlive->>KeepAlive: log WARN, suppress exception
KeepAlive-->>SpringScheduler: no exception propagated
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:b6878a450f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
This PR fixes two LLM-path latency/correctness issues: it scopes completion prompt truncation to the active provider/model (so high-context models don’t get capped by an unused provider’s GPT‑5 defaults), and it adds an embedding keep-alive scheduler to prevent chat turns from stalling on embedding cold starts.
Changes:
- Move completion truncation into
OpenAiRequestFactory.buildCompletionRequest(...), keyed to the resolved model ID for each provider attempt (including fallback). - Adjust truncation rules so GPT‑5-family models use the 7K safe input budget while o‑series models no longer inherit that cap.
- Add
EmbeddingModelKeepAlive+ tests to probe the embedding provider on a cadence and avoid cold-start latency on user requests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/com/williamcallahan/javachat/service/OpenAIStreamingService.java | Stops pre-truncating before provider selection; passes raw prompt so each provider attempt can truncate to its own model limit. |
| src/main/java/com/williamcallahan/javachat/service/OpenAiRequestFactory.java | Applies completion prompt truncation inside buildCompletionRequest using the resolved model ID; adjusts GPT‑5 vs non‑GPT‑5 token budgeting. |
| src/main/java/com/williamcallahan/javachat/service/EmbeddingModelKeepAlive.java | Adds scheduled embedding probe to keep serverless/self-hosted embedding models warm and avoid cold-start latency. |
| src/test/java/com/williamcallahan/javachat/service/OpenAiRequestFactoryTest.java | Adds regression tests ensuring provider/model-specific truncation behavior (OpenAI vs GitHub Models; GPT‑5 vs o‑series). |
| src/test/java/com/williamcallahan/javachat/service/EmbeddingModelKeepAliveTest.java | Adds unit tests verifying probing behavior and that provider unavailability is not propagated. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…nd/npm_and_yarn-8019164dec build(deps): bump the npm_and_yarn group across 1 directory with 8 updates
ErrorDocumentationController served every error page with ResponseEntity.ok(), overwriting the status CustomErrorController set on the forward. A missing hashed asset therefore answered 200 text/html, which browsers surface as a strict-MIME module error during deploy windows instead of a clean 404. The controller now preserves the error-dispatch status (ERROR_STATUS_CODE survives the forward) while direct documentation visits stay 200; the swallowed IOException in serveHtmlFile now logs per [RC1e].
handlePageError forwarded to the error page without setting the ModelAndView status, leaving the forward's default 200 in place. Sets the resolved status at the dispatch source so the error contract does not depend solely on the forwarded controller.
…e variant Chrome deprecated honoring apple-mobile-web-app-capable alone and logs a console warning; the standard tag is added first while the apple tag stays for older iOS Safari.
… of the whole GPT-5 family Both prepareStreamingRequest and buildCompletionRequest applied the 7,000-token input cap to every GPT-5-family model, so gpt-5.4 served via OpenAI direct or the LLM gateway (400K context) was truncated to 7K on every chat turn and completion. The cap models GitHub Models' 8K input tier and now applies only when that provider serves a GPT-5 model; everything else uses the 100K default. The truncation notice follows the same flag so a 100K truncation no longer claims an 8K limit. Constant renamed to MAX_TOKENS_GITHUB_MODELS_GPT5_INPUT to name its real owner.
Unknown /api paths matched the dot-less SPA view-controller patterns and answered 200 with the index.html shell. The (?!api$) segment guard keeps them out of the fallback so they reach CustomErrorController's JSON branch as real 404s; SPA deep links keep forwarding to index.html.
The JSON-vs-HTML branch matched only /api/ prefixes, so a request to exactly /api received the HTML error page; it now gets the JSON error contract like the rest of the namespace.
…s with one guarded reload A rolling deploy can 404 lazily-loaded chunks referenced by the new index.html while the old container drains. The vite:preloadError listener reloads once per session to pick up the fresh build instead of leaving a dead page; the sessionStorage guard prevents reload loops if the failure persists.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Latency and correctness fixes across the LLM path and web layer: prompts now truncate to the limits of the model actually serving each request (previously every GPT-5-family chat — including gateway
gpt-5.4with 400K context — was silently cut to 7K tokens), live chat turns no longer pay 20–60s serverless embedding cold starts, error and API responses report real status codes, the SPA survives rolling deploys with a guarded auto-reload, and the frontend picks up the grouped Dependabot security bumps.Changes
Bug Fixes
gpt-5.4chat turn to 7K tokens; the cap now applies only when GitHub Models serves a GPT-5 model, and the truncation notice no longer claims an 8K limit for a 100K truncation (OpenAiRequestFactory.prepareStreamingRequest/buildCompletionRequest)buildCompletionRequest, keyed to the active provider's model; fallback re-truncates with the fallback model's own limit (OpenAIStreamingService.complete; fixes[Detail Bug] Prompt truncation uses limits from unused provider, forcing 7K cap on high-context models #40, contributed in Scope completion truncation to active provider #49 by @pntech20)o1/o3/o3-miniuse the default 100K cap instead of inheriting the GPT-5 tier cap (OpenAiRequestFactory)EmbeddingModelKeepAlive)ErrorDocumentationController,CustomErrorController)/apipaths return JSON 404 instead of the SPA shell with 200: a(?!api$)guard keeps the API namespace out of the SPA fallback patterns, and bare/apijoins the JSON error branch (WebMvcConfig,CustomErrorController)vite:preloadErrorlistener reloads once per session (sessionStorage-guarded) to pick up the fresh build instead of leaving a dead page (frontend/src/main.ts)mobile-web-app-capableadded alongside the apple variant (frontend/index.html)Dependencies
dompurify3.3.1→3.4.0 (markdown sanitizer; verified itsFORBID_TAGS/ADD_ATTRchanges don't intersect with our single static config),svelte5.48→5.55,vite6.4.1→6.4.2, plus transitive security patches; validated with clean install, lint, svelte-check, production build, and 80/80 tests (frontend/package.json, lockfile)Refactoring
buildCompletionRequest, covering the full provider-to-model-id truncation path (OpenAiRequestFactory,OpenAiRequestFactoryTest)Breaking Changes
None
Related Issues
Closes#40