You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
extract_edges hard-codes a 16,384 output-token cap and passes it as an explicit max_tokens=, which overrides the model's real output budget. On a model graphiti itself records as supporting 65,536 output tokens, edge extraction is therefore capped at a quarter of capacity. When a document produces more edge JSON than that, the reply is truncated mid-string, json.loads fails, and — on the Gemini path — the error surfaces as a bare Exception with no message.
Related but not the same: #811 asks for the constant to be hoisted. This issue is about the cap causing silent, hard-to-diagnose ingest failure, and about the messageless exception that hides it. #763 is the mirror-image problem (config max_tokens ignored) on the OpenAI clients.
llm_response=awaitllm_client.generate_response(
prompt_library.extract_edges.edge(context),
response_model=ExtractedEdges,
max_tokens=extract_edges_max_tokens, # <-- explicit, so it wins
...
)
GeminiClient._resolve_max_tokens (graphiti_core/llm_client/gemini_client.py:175-201) documents its own precedence, and an explicit parameter is rule 1:
1. Explicit max_tokens parameter passed to generate_response()
2. Instance max_tokens set during client initialization
3. ...
So the per-model table three dozen lines above it is never consulted for this call — even though it says (gemini_client.py:51-57):
There is no configuration knob: setting LLMConfig(max_tokens=...) does not help, because the call site passes its own value.
2. What that looks like at runtime
Ingesting a corpus of 81 markdown documents (~1 MB total), a single larger document blows the cap:
ERROR 🦀 LLM generation failed parsing as JSON, will try to salvage.
ERROR Raw output (truncated): {"edges": [{"source_entity_name": "ORCHESTRATOR", "target_entity_name": ...
ERROR Error in generating LLM response: Failed to parse structured response:
Unterminated string starting at: line 1 column 43665 (char 43664)
ERROR 🦀 LLM generation failed and retries are exhausted.
ERROR Max retries (2) exceeded. Last error:
43,665 characters of dense JSON is almost exactly 16,384 tokens — the reply is being cut at the cap, not by the model. salvage_json is attempted and returns None for these. Retrying twice cannot help: the same prompt regenerates the same over-long answer.
Note thinking_budget=0 is already set, so this is not#1868 (thinking tokens consuming the output budget) — this is the raw cap.
3. The error arrives with no message
graphiti_core/llm_client/gemini_client.py:361:
exceptExceptionase:
...
logger.error(f'Error in generating LLM response: {e}')
raiseExceptionfrome
raise Exception from e constructs an argument-less Exception. Anything that catches and formats it — a bulk runner, an MCP server, a user's except Exception as exc: log(exc) — gets an empty string:
[13-24/81] FAIL 209.9s Exception:
The cause survives only because logger.error on the line above happens to print it. raise (bare, preserving the original) or raise Exception(str(e)) from e would cost nothing. The same pattern appears at :344 where the message is preserved (raise Exception(f'Failed to parse structured response: {e}') from e), so this looks like an oversight rather than intent.
4. Blast radius in add_episode_bulk
One oversized document fails the entire batch. In the run above, chunk [13-24/81] — twelve documents — was lost to a single file that overflowed, because add_episode_bulk (graphiti.py:1290) has no per-episode error isolation. With the recommended chunking from its own docstring (graphiti.py:1345-1348), larger chunks mean a bigger blast radius from one bad document.
Suggested fixes
Make the edge-extraction cap respect the model. Either drop the explicit max_tokens= at edge_operations.py:205 and let _resolve_max_tokens consult GEMINI_MODEL_MAX_TOKENS, or make the constant a floor rather than a ceiling: max_tokens=max(extract_edges_max_tokens, model_max). Hoisting it (refactor: hoist EXTRACT_EDGES_MAX_TOKENS constant #811) would also make it overridable.
gemini_client.py:361 — preserve the message: bare raise, or raise Exception(str(e)) from e.
Optional, higher value: isolate per-episode failures in add_episode_bulk so one unparseable document does not discard its whole chunk.
Happy to open a PR for (1) and (2) if that's useful.
Summary
extract_edgeshard-codes a 16,384 output-token cap and passes it as an explicitmax_tokens=, which overrides the model's real output budget. On a model graphiti itself records as supporting 65,536 output tokens, edge extraction is therefore capped at a quarter of capacity. When a document produces more edge JSON than that, the reply is truncated mid-string,json.loadsfails, and — on the Gemini path — the error surfaces as a bareExceptionwith no message.Related but not the same: #811 asks for the constant to be hoisted. This issue is about the cap causing silent, hard-to-diagnose ingest failure, and about the messageless exception that hides it. #763 is the mirror-image problem (config
max_tokensignored) on the OpenAI clients.Version: graphiti-core 0.30.2 (
eaa4128), Python 3.12, Windows 10, Kuzu store,GeminiClient(model='gemini-2.5-flash', thinking_config=ThinkingConfig(thinking_budget=0)).1. The cap overrides the model
graphiti_core/utils/maintenance/edge_operations.py:141passed at
edge_operations.py:205:GeminiClient._resolve_max_tokens(graphiti_core/llm_client/gemini_client.py:175-201) documents its own precedence, and an explicit parameter is rule 1:So the per-model table three dozen lines above it is never consulted for this call — even though it says (
gemini_client.py:51-57):There is no configuration knob: setting
LLMConfig(max_tokens=...)does not help, because the call site passes its own value.2. What that looks like at runtime
Ingesting a corpus of 81 markdown documents (~1 MB total), a single larger document blows the cap:
43,665 characters of dense JSON is almost exactly 16,384 tokens — the reply is being cut at the cap, not by the model.
salvage_jsonis attempted and returnsNonefor these. Retrying twice cannot help: the same prompt regenerates the same over-long answer.Note
thinking_budget=0is already set, so this is not #1868 (thinking tokens consuming the output budget) — this is the raw cap.3. The error arrives with no message
graphiti_core/llm_client/gemini_client.py:361:raise Exception from econstructs an argument-lessException. Anything that catches and formats it — a bulk runner, an MCP server, a user'sexcept Exception as exc: log(exc)— gets an empty string:The cause survives only because
logger.erroron the line above happens to print it.raise(bare, preserving the original) orraise Exception(str(e)) from ewould cost nothing. The same pattern appears at:344where the message is preserved (raise Exception(f'Failed to parse structured response: {e}') from e), so this looks like an oversight rather than intent.4. Blast radius in
add_episode_bulkOne oversized document fails the entire batch. In the run above, chunk
[13-24/81]— twelve documents — was lost to a single file that overflowed, becauseadd_episode_bulk(graphiti.py:1290) has no per-episode error isolation. With the recommended chunking from its own docstring (graphiti.py:1345-1348), larger chunks mean a bigger blast radius from one bad document.Suggested fixes
max_tokens=atedge_operations.py:205and let_resolve_max_tokensconsultGEMINI_MODEL_MAX_TOKENS, or make the constant a floor rather than a ceiling:max_tokens=max(extract_edges_max_tokens, model_max). Hoisting it (refactor: hoist EXTRACT_EDGES_MAX_TOKENS constant #811) would also make it overridable.gemini_client.py:361— preserve the message: bareraise, orraise Exception(str(e)) from e.add_episode_bulkso one unparseable document does not discard its whole chunk.Happy to open a PR for (1) and (2) if that's useful.