mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-20 20:46:15 +00:00
fix(llm): enforce API timeout in the secondary LLM dispatch path (#1442)
_call_llm (used by the dedup LLM tiebreaker) built its Anthropic and OpenAI-compatible clients with max_retries but no timeout, so requests on this path silently ignored GRAPHIFY_API_TIMEOUT — unlike the primary extraction paths (_call_openai_compat / _call_claude) which already pass both. Add timeout=_resolve_api_timeout() to both constructors. The PR branch self-neutralized: a v8 merge resolved the conflict in favor of the max_retries-bearing line and dropped the original one-line fix, so it is re-applied here on top of current v8 with max_retries preserved. Adds regression coverage for both _call_llm branches, which were previously untested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
86ecb769b6
commit
4e4935a64a
+2
-2
@@ -1905,7 +1905,7 @@ def _call_llm(
|
||||
import anthropic
|
||||
except ImportError as exc:
|
||||
raise ImportError(_backend_pkg_hint("anthropic", "anthropic")) from exc
|
||||
client = anthropic.Anthropic(api_key=key, base_url=cfg["base_url"], max_retries=_resolve_max_retries())
|
||||
client = anthropic.Anthropic(api_key=key, base_url=cfg["base_url"], timeout=_resolve_api_timeout(), max_retries=_resolve_max_retries())
|
||||
resp = client.messages.create(
|
||||
model=mdl,
|
||||
max_tokens=max_tokens,
|
||||
@@ -1988,7 +1988,7 @@ def _call_llm(
|
||||
from openai import OpenAI
|
||||
except ImportError as exc:
|
||||
raise ImportError(_backend_pkg_hint("openai", "openai")) from exc
|
||||
client = OpenAI(api_key=key, base_url=cfg["base_url"], max_retries=_resolve_max_retries())
|
||||
client = OpenAI(api_key=key, base_url=cfg["base_url"], timeout=_resolve_api_timeout(), max_retries=_resolve_max_retries())
|
||||
kwargs: dict = {
|
||||
"model": mdl,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
|
||||
@@ -1028,3 +1028,62 @@ def test_openai_compat_client_built_with_retries(monkeypatch):
|
||||
"user msg", temperature=0, max_completion_tokens=4096, backend="kimi",
|
||||
)
|
||||
assert ctor_kwargs.get("max_retries", 0) >= 5, ctor_kwargs
|
||||
|
||||
|
||||
def test_call_llm_claude_client_built_with_timeout_and_retries(monkeypatch):
|
||||
"""The secondary dispatch path (_call_llm, used by the dedup tiebreaker)
|
||||
must build its Anthropic client with both timeout and max_retries, matching
|
||||
the primary extraction path — #1442. Previously _call_llm passed neither
|
||||
(then only max_retries), so GRAPHIFY_API_TIMEOUT was silently ignored here."""
|
||||
import sys
|
||||
import types
|
||||
|
||||
ctor_kwargs = {}
|
||||
|
||||
class _FakeMessages:
|
||||
def create(self, **_):
|
||||
return types.SimpleNamespace(content=[types.SimpleNamespace(text="ok")])
|
||||
|
||||
class _FakeAnthropic:
|
||||
def __init__(self, *_, **kwargs):
|
||||
ctor_kwargs.update(kwargs)
|
||||
self.messages = _FakeMessages()
|
||||
|
||||
fake_module = types.ModuleType("anthropic")
|
||||
fake_module.Anthropic = _FakeAnthropic
|
||||
monkeypatch.setitem(sys.modules, "anthropic", fake_module)
|
||||
monkeypatch.setattr(llm, "_get_backend_api_key", lambda _b: "fake-key")
|
||||
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "1")
|
||||
monkeypatch.delenv("GRAPHIFY_MAX_RETRIES", raising=False)
|
||||
|
||||
assert llm._call_llm("hi", backend="claude") == "ok"
|
||||
assert ctor_kwargs.get("timeout") == 1.0, ctor_kwargs
|
||||
assert ctor_kwargs.get("max_retries", 0) >= 5, ctor_kwargs
|
||||
|
||||
|
||||
def test_call_llm_openai_compat_client_built_with_timeout_and_retries(monkeypatch):
|
||||
"""Same #1442 fix for the OpenAI-compatible branch of _call_llm."""
|
||||
import sys
|
||||
import types
|
||||
|
||||
ctor_kwargs = {}
|
||||
|
||||
class _FakeOpenAI:
|
||||
def __init__(self, *_, **kwargs):
|
||||
ctor_kwargs.update(kwargs)
|
||||
self.chat = self
|
||||
self.completions = self
|
||||
|
||||
def create(self, **_):
|
||||
return _fake_openai_response("ok", finish_reason="stop", completion_tokens=1)
|
||||
|
||||
fake_module = types.ModuleType("openai")
|
||||
fake_module.OpenAI = _FakeOpenAI
|
||||
monkeypatch.setitem(sys.modules, "openai", fake_module)
|
||||
monkeypatch.setattr(llm, "_get_backend_api_key", lambda _b: "fake-key")
|
||||
monkeypatch.setenv("GRAPHIFY_API_TIMEOUT", "1")
|
||||
monkeypatch.delenv("GRAPHIFY_MAX_RETRIES", raising=False)
|
||||
|
||||
llm._call_llm("hi", backend="kimi")
|
||||
assert ctor_kwargs.get("timeout") == 1.0, ctor_kwargs
|
||||
assert ctor_kwargs.get("max_retries", 0) >= 5, ctor_kwargs
|
||||
|
||||
Reference in New Issue
Block a user