honour GRAPHIFY_API_TIMEOUT in claude-cli and Anthropic SDK backends (#1112)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-06-05 10:45:01 +01:00
co-authored by Claude Sonnet 4.6
parent 830f0396d8
commit 75c4de7759
3 changed files with 53 additions and 14 deletions
+1 -1
View File
@@ -366,7 +366,7 @@ These are only needed for **headless / CI extraction** (`graphify extract`). Whe
| `AWS_*` / `~/.aws/credentials` | AWS Bedrock — standard credential chain | `--backend bedrock` (no API key, uses IAM) |
| `GRAPHIFY_MAX_WORKERS` | AST parallelism thread count | optional — also `--max-workers` flag |
| `GRAPHIFY_MAX_OUTPUT_TOKENS` | Raise output cap for dense corpora | optional — e.g. `32768` for large files |
| `GRAPHIFY_API_TIMEOUT` | HTTP timeout in seconds (default: 600) | optional — also `--api-timeout` flag |
| `GRAPHIFY_API_TIMEOUT` | Per-call timeout in seconds for HTTP, claude-cli, and Anthropic SDK backends (default: 600) | optional — also `--api-timeout` flag |
| `GRAPHIFY_FORCE` | Force graph rebuild even with fewer nodes | optional — also `--force` flag |
| `GRAPHIFY_GOOGLE_WORKSPACE` | Auto-enable Google Workspace export | optional — set to `1` |
| `GRAPHIFY_TRIAGE_BACKEND` | Backend for `graphify prs --triage` | optional — auto-detected from available keys |
+17 -13
View File
@@ -214,6 +214,19 @@ def _resolve_max_tokens(default: int) -> int:
pass
return default
def _resolve_api_timeout(default: float = 600.0) -> float:
"""Honour GRAPHIFY_API_TIMEOUT env var override, else use default (seconds)."""
raw = os.environ.get("GRAPHIFY_API_TIMEOUT", "").strip()
if raw:
try:
v = float(raw)
if v > 0:
return v
except ValueError:
pass
return default
_EXTRACTION_SYSTEM = """\
You are a graphify semantic extraction agent. Extract a knowledge graph fragment from the files provided.
Output ONLY valid JSON no explanation, no markdown fences, no preamble.
@@ -434,16 +447,7 @@ def _call_openai_compat(
# default. Honour GRAPHIFY_API_TIMEOUT (seconds) for explicit override;
# default to 600s, which is long enough for a 31B model on a 16k chunk
# but still bounds runaway connections (issue #792 addendum).
timeout_raw = os.environ.get("GRAPHIFY_API_TIMEOUT", "").strip()
timeout_s: float = 600.0
if timeout_raw:
try:
v = float(timeout_raw)
if v > 0:
timeout_s = v
except ValueError:
pass
client = OpenAI(api_key=api_key, base_url=base_url, timeout=timeout_s)
client = OpenAI(api_key=api_key, base_url=base_url, timeout=_resolve_api_timeout())
kwargs: dict = {
"model": model,
"messages": [
@@ -550,7 +554,7 @@ def _call_claude(api_key: str, model: str, user_message: str, max_tokens: int =
except ImportError as exc:
raise ImportError(_backend_pkg_hint("anthropic", "anthropic")) from exc
client = anthropic.Anthropic(api_key=api_key)
client = anthropic.Anthropic(api_key=api_key, timeout=_resolve_api_timeout())
resp = client.messages.create(
model=model,
max_tokens=max_tokens,
@@ -637,7 +641,7 @@ def _call_claude_cli(user_message: str, max_tokens: int = 8192, *, deep_mode: bo
capture_output=True,
text=True,
encoding="utf-8", # Force UTF-8 — prevents UnicodeEncodeError on Windows cp1252
timeout=600,
timeout=_resolve_api_timeout(),
check=False,
)
if proc.returncode != 0:
@@ -1200,7 +1204,7 @@ def _call_llm(prompt: str, *, backend: str, max_tokens: int = 200) -> str:
capture_output=True,
text=True,
encoding="utf-8", # Force UTF-8 — prevents UnicodeEncodeError on Windows cp1252
timeout=600,
timeout=_resolve_api_timeout(),
check=False,
)
if proc.returncode != 0:
+35
View File
@@ -193,3 +193,38 @@ def test_non_windows_uses_bare_claude(monkeypatch):
argv = run.call_args.args[0]
assert argv[0] == "claude"
# ---------- GRAPHIFY_API_TIMEOUT honoured by all backends ----------
def test_resolve_api_timeout_default(monkeypatch):
monkeypatch.delenv("GRAPHIFY_API_TIMEOUT", raising=False)
assert llm._resolve_api_timeout() == 600.0
def test_resolve_api_timeout_env_override(monkeypatch):
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "45")
assert llm._resolve_api_timeout() == 45.0
def test_resolve_api_timeout_ignores_invalid(monkeypatch):
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "not-a-number")
assert llm._resolve_api_timeout() == 600.0
def test_resolve_api_timeout_ignores_nonpositive(monkeypatch):
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "0")
assert llm._resolve_api_timeout() == 600.0
def test_claude_cli_extraction_honours_timeout(monkeypatch, fake_claude):
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "30")
llm._call_claude_cli("dummy", max_tokens=8192)
assert fake_claude.call_args.kwargs["timeout"] == 30.0
def test_call_llm_claude_cli_branch_honours_timeout(monkeypatch, fake_claude):
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "30")
llm._call_llm(prompt="x", backend="claude-cli", max_tokens=10)
assert fake_claude.call_args.kwargs["timeout"] == 30.0