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:
@@ -283,6 +283,8 @@ model intake_batches {
|
|||||||
status String
|
status String
|
||||||
transaction_id Int?
|
transaction_id Int?
|
||||||
capture_id Int?
|
capture_id Int?
|
||||||
|
// The undo baseline, written once per batch (see 0034 and line-items.ts).
|
||||||
|
previous_line_items Json?
|
||||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||||
resolved_at DateTime? @db.Timestamptz(6)
|
resolved_at DateTime? @db.Timestamptz(6)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,11 +185,18 @@ export function liteLLMExtractor(opts: {
|
|||||||
});
|
});
|
||||||
if (!response.ok) throw new ExtractionError(`LiteLLM returned ${response.status}`);
|
if (!response.ok) throw new ExtractionError(`LiteLLM returned ${response.status}`);
|
||||||
const raw = (await response.json()) as { choices?: { message?: { content?: string }; finish_reason?: string }[] };
|
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
|
// The rule is: a finish_reason that is PRESENT must be `stop`. An absent
|
||||||
// still parse once the JSON is closed, which quietly drops the tail of a
|
// one is accepted, deliberately — providers differ on whether they send the
|
||||||
// long receipt; a `content_filter` or `tool_calls` stop is a different reply
|
// field, and failing every call on a provider that omits it would be a
|
||||||
// shape entirely. Accepting anything but `stop` accepts a reply the schema
|
// certain outage in exchange for a case that also requires the model to
|
||||||
// never constrained — losing lines silently is worse than failing.
|
// 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;
|
const finish = raw.choices?.[0]?.finish_reason;
|
||||||
if (finish && finish !== "stop") {
|
if (finish && finish !== "stop") {
|
||||||
throw new ExtractionError(
|
throw new ExtractionError(
|
||||||
|
|||||||
Reference in New Issue
Block a user