fix: a retried starter-question batch gets other snippets, and the pool's model is documented
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 6s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 16s

The same snippets sent DeepSeek into the same 26,000-character reasoning
spiral three times running, each ending in an empty reply at the token
ceiling. A retry now rotates the snippets. Production sets
clinical_assistant.prompt_model to gpt-4.1-mini, a model that does not
think about a list of questions; the setting is now documented with why.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
This commit is contained in:
Daniel 2026-09-13 06:46:41 +02:00
parent d517a3fdf6
commit 1c5e218382
2 changed files with 12 additions and 3 deletions

View file

@ -57,6 +57,11 @@ keys):
| `LITELLM_TTS_MODEL`, `LITELLM_TTS_VOICE` | LiteLLM-routed TTS model and default voice. |
| `LITELLM_TTS_VOICES` | The voices of `LITELLM_TTS_MODEL` only; other models use the built-in table in `src/utils/ttsProvider.js`. The roster itself is `tts.roster` in `app_settings`. |
### Starter questions
| Setting | Meaning |
|---|---|
| `clinical_assistant.prompt_model` | The model that writes the starter-question pool. Use a **non-reasoning** model (`openrouter-gpt-4.1-mini` in production): a reasoning model spends the whole completion budget thinking about a 20-question JSON list and returns nothing. Falls back to `clinical_assistant.chat_model`, then `models.default`. |
### Embeddings
| Variable | Purpose |

View file

@ -340,8 +340,11 @@ function createClinicalPromptPool(opts) {
if (!snippets.length) continue;
var categoryExamples = [];
var batches = Math.max(1, Math.min(6, Math.ceil(item.quota / 18)));
var retries = 0;
for (var i = 0; i < batches && categoryExamples.length < item.quota; i++) {
var batchSnippets = rotateExamples(snippets, i * 18).slice(0, 18);
// A retry gets different snippets: the same ones produced the same
// runaway reasoning and the same empty reply, three times in a row.
var batchSnippets = rotateExamples(snippets, i * 18 + retries * 7).slice(0, 18);
var sourceText = batchSnippets.map(function(s, idx) {
return '[' + (idx + 1) + '] ' + s.title + (s.page ? ', page ' + s.page : '') + '\n' + clip(s.excerpt, 700);
}).join('\n\n');
@ -364,8 +367,9 @@ function createClinicalPromptPool(opts) {
// The model answers a JSON task with an empty message now and then —
// two batches in five came back with nothing. One more try with the
// same snippets is cheap and usually enough.
console.warn('[clinical-assistant] prompt pool ' + item.category + ' batch ' + (i + 1) + ': nothing parseable in ' + String(ai.content || '').length + ' chars (finish ' + (ai.finishReason || '?') + '); retrying once');
i--; batches++; if (batches > 12) break;
console.warn('[clinical-assistant] prompt pool ' + item.category + ' batch ' + (i + 1) + ': nothing parseable in ' + String(ai.content || '').length + ' chars (finish ' + (ai.finishReason || '?') + '); retrying with other snippets');
if (++retries > 3) break;
i--;
continue;
}
else if (parsed.truncated) console.warn('[clinical-assistant] prompt pool ' + item.category + ' batch ' + (i + 1) + ': reply was cut off; ' + offered + ' questions salvaged');