bot: classify-intent answers UNKNOWN for a bounds rejection because langchain wraps it in OutputParserException #84
Loading…
Add table
Add a link
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?
Raised by
Reviewer78r1in rounds 1–6 of PR #78 and deliberately carried out of it: the service bound holds, nothing is written, and the fix is a langchain error-handling decision rather than a numeric-bounds one.The defect
apps/management/lib/bot/nodes/classify-intent.ts:399-412catches a failed model invocation and routes it throughdescribeBotRejection, so an out-of-range number from the intent classifier should reach the operator as a named refusal.It does not. When the structured-output parser rejects the model's payload, langchain wraps the underlying
ZodErrorin anOutputParserException.describeBotRejectionmatchesZodErrorandServiceRejection, so it returnsnull, and the operator getsUNKNOWN— the bot appearing to ignore them, which is exactly the outcome the code was written to avoid.The comment ships either way, and it is wrong
This is the part worth fixing regardless of which direction the behaviour goes.
classify-intent.ts:401-403currently reads:The first sentence is true. The second describes behaviour the code does not produce: it answers
UNKNOWNwith no explanation, which is the thing the comment says it avoids. PR #78 spent six review rounds on comments claiming slightly more than their code does, and this is one of them still standing.Severity
Message quality only. The service bound holds —
adjustStock,createPurchaseandcreateOrderall reject before any write, measured in round 1 — so nothing is written and nothing is corrupted. The cost is an operator who cannot tell a refused number from a bot that did not understand them, and who therefore retries the same message.Why it needs a decision, not a patch
Unwrapping
OutputParserExceptionhas two viable shapes and they are not equivalent:llmOutput, re-parsing to recover the underlying issues. Precise, but couples this node to langchain's exception surface.ZodError, the wayapps/store/lib/catalog.tswalks.causefor a framework marker. Looser coupling, and there is already a bounded, cycle-safe implementation in this repo to copy from — including the reasons it is bounded (see #75).Whichever is chosen,
lib/bot/nodes/vision-llm.tscarries the same wrap. Its comment is correctly hedged, so it is not making a false claim, but a real fix touches both nodes — which is the main argument for this being its own change rather than a nit on a bounds PR.Suggested scope
classify-intent.tsandvision-llm.tstogether.classify-intent.ts:401-403to describe what the code does.ZodErrorreaches the operator as a named refusal, plus a negative control asserting a genuine model outage does not get dressed up as a bad number — the shape PR #78 settled on after four rounds of unit tests standing in for wiring.Related
describeBotRejectionand closed the equivalent wiring gap forexecute-purchase.tsandexecute-stock-adjust.ts, which call it with real, unwrapped errors and are covered bynode-wiring.test.ts.Closed as stale. In PR #113 (
1e94275), LangGraph and LangChain were completely replaced with a native OpenRouter runner, andlangchainwas removed from dependencies. Model structured output is parsed withIntentSchema.parse(), which throws a nativeZodErrorcaught directly bydescribeBotRejection.OutputParserExceptionno longer exists on this path.