schema: previous_line_items on the model; say what the finish_reason rule is
Migration 0034 has the column and the Prisma model did not, so a regenerated client and the database disagreed. The extraction comment claimed only a finished reply is accepted while the code accepts an absent finish_reason. The code is right — failing every call on a provider that omits an optional field would be a certain outage in exchange for a case that also requires syntactically complete JSON, and the harm a truncated reply does is closed on the write side. The prose now states the rule as it is: a reason that is PRESENT must be `stop`.
This commit is contained in:
@@ -185,11 +185,18 @@ export function liteLLMExtractor(opts: {
|
||||
});
|
||||
if (!response.ok) throw new ExtractionError(`LiteLLM returned ${response.status}`);
|
||||
const raw = (await response.json()) as { choices?: { message?: { content?: string }; finish_reason?: string }[] };
|
||||
// Only `stop` means the strict schema was satisfied. A truncated reply can
|
||||
// still parse once the JSON is closed, which quietly drops the tail of a
|
||||
// long receipt; a `content_filter` or `tool_calls` stop is a different reply
|
||||
// shape entirely. Accepting anything but `stop` accepts a reply the schema
|
||||
// never constrained — losing lines silently is worse than failing.
|
||||
// The rule is: a finish_reason that is PRESENT must be `stop`. An absent
|
||||
// one is accepted, deliberately — providers differ on whether they send the
|
||||
// field, and failing every call on a provider that omits it would be a
|
||||
// certain outage in exchange for a case that also requires the model to
|
||||
// emit syntactically complete JSON. The harm a truncated reply does — a
|
||||
// partial item list replacing a good one — is closed on the write side
|
||||
// instead, by the guard in `routeFood`/`createFromBatch`.
|
||||
//
|
||||
// `length` is the common one: a truncated reply still parses once the JSON
|
||||
// happens to close, quietly dropping the tail of a long receipt. Any other
|
||||
// reason (`content_filter`, `tool_calls`) is a different reply shape that
|
||||
// the strict schema never constrained.
|
||||
const finish = raw.choices?.[0]?.finish_reason;
|
||||
if (finish && finish !== "stop") {
|
||||
throw new ExtractionError(
|
||||
|
||||
Reference in New Issue
Block a user