Skip to content

fix(llm): pin claude-cli output to a JSON schema when supported (#2076)#2095

Open
Yyunozor wants to merge 1 commit into
Graphify-Labs:v8from
Yyunozor:fix/claude-cli-2076
Open

fix(llm): pin claude-cli output to a JSON schema when supported (#2076)#2095
Yyunozor wants to merge 1 commit into
Graphify-Labs:v8from
Yyunozor:fix/claude-cli-2076

Conversation

@Yyunozor

Copy link
Copy Markdown
Contributor

Summary

The claude-cli backend puts the extraction schema in the user turn and trusts the model to emit raw JSON (the llm.py:1376 workaround, "verified against Claude Code 2.1.197"). On newer Claude Code the model treats that prompt as an agentic task and reports the result in prose instead — Knowledge graph extracted — 21 nodes, 20 edges…. That parses to zero nodes, _response_is_hollow reads truncation, and adaptive-retry bisects the chunk without ever converging, so the graph comes out empty (#2076).

Why this is version-robust, and how it was checked

We run Claude Code as this backend's harness, so the failing component is directly testable here. On 2.1.207 the current user-turn workaround still returns JSON — the empty-graph break does not reproduce there, so the regression is version-gated, consistent with the 2.1.216 report. But the failure mode is easy to force and to fix on a live CLI: a prompt that invites prose makes claude -p --output-format json put a conversational summary in result, while adding --json-schema makes the same call return the object regardless of framing (also surfaced in a structured_output envelope field). Structured output is a structural guarantee independent of the model's discretion, so it survives the prompt-behaviour drift that broke the prompt-shape workaround.

Fix

Pass --json-schema — a schema pinning the top-level {nodes, edges} shape graphify consumes, item internals left loose so valid extractions are never rejected — when the CLI advertises the flag, probed once via claude --help and cached. Older CLIs that predate the flag keep the user-turn prompt as a fallback, unchanged. The result envelope still carries the JSON string, so the _parse_llm_json path is untouched.

The issue's second suggestion — routing a successful-sounding prose response away from the bisect path — is complementary and left out of scope; with structured output the prose case no longer arises on supported CLIs.

Validation

tests/test_claude_cli_backend.py: 28 passed, 5 new (flag present when supported, absent when not, the --help capability probe, safe fallback on probe error). Full suite: 3317 passed on the branch vs 3312 on the parent e32c9f4, same environment — delta is exactly these 5 tests. (13 failures in unrelated suites — terraform/ollama/manifest/install-references — are pre-existing, identical on the parent, from optional deps absent locally.) ruff check clean.

…hify-Labs#2076)

The claude-cli backend delivers the extraction schema in the user turn and
trusts the model to emit raw JSON. Newer Claude Code releases treat that
prompt as an agentic task and report the result in prose instead ("Knowledge
graph extracted — 21 nodes, 20 edges…"), so the graph parses empty, reads as
truncation, and adaptive-retry bisects without ever converging.

Pass --json-schema (structured output) when the CLI advertises it — probed
once via `claude --help` and cached — so the object shape is constrained
regardless of prompt framing. Older CLIs that predate the flag keep the
user-turn prompt as a fallback. The `result` envelope still carries the JSON
string, so the parse path is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant