Added a configurable LLM timeout
This commit is contained in:
@@ -18,6 +18,7 @@ def _config(**overrides):
|
||||
data = {
|
||||
"api_key": "test-key",
|
||||
"llm_concurrency": base.llm_concurrency,
|
||||
"llm_timeout_seconds": base.llm_timeout_seconds,
|
||||
"module_keys": base.module_keys,
|
||||
"model": base.model,
|
||||
"base_url": base.base_url,
|
||||
@@ -51,8 +52,8 @@ def _install_fake_llm_modules(monkeypatch):
|
||||
return {"ok": True}
|
||||
|
||||
class FakeOpenAI:
|
||||
def __init__(self, *, api_key, base_url):
|
||||
openai_inits.append({"api_key": api_key, "base_url": base_url})
|
||||
def __init__(self, *, api_key, base_url, timeout):
|
||||
openai_inits.append({"api_key": api_key, "base_url": base_url, "timeout": timeout})
|
||||
|
||||
fake_instructor = types.SimpleNamespace(
|
||||
Mode=types.SimpleNamespace(TOOLS="TOOLS"),
|
||||
@@ -159,8 +160,33 @@ def test_client_cache_identity_uses_api_key_and_base_url(monkeypatch):
|
||||
)
|
||||
|
||||
assert openai_inits == [
|
||||
{"api_key": "key-1", "base_url": "http://localhost:8000/v1"},
|
||||
{"api_key": "key-1", "base_url": "https://api.openai.com/v1"},
|
||||
{"api_key": "key-1", "base_url": "http://localhost:8000/v1", "timeout": 600},
|
||||
{"api_key": "key-1", "base_url": "https://api.openai.com/v1", "timeout": 600},
|
||||
]
|
||||
|
||||
|
||||
def test_client_cache_identity_uses_timeout(monkeypatch):
|
||||
_, openai_inits = _install_fake_llm_modules(monkeypatch)
|
||||
client = OpenAICompatibleStructuredLLMClient()
|
||||
first = _config(api_key="key-1", base_url="http://localhost:8000/v1", llm_timeout_seconds=600)
|
||||
second = _config(api_key="key-1", base_url="http://localhost:8000/v1", llm_timeout_seconds=1200)
|
||||
|
||||
client.run_structured(
|
||||
stage_name="one",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
response_model=DummyResponseModel,
|
||||
config=first,
|
||||
)
|
||||
client.run_structured(
|
||||
stage_name="two",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
response_model=DummyResponseModel,
|
||||
config=second,
|
||||
)
|
||||
|
||||
assert openai_inits == [
|
||||
{"api_key": "key-1", "base_url": "http://localhost:8000/v1", "timeout": 600},
|
||||
{"api_key": "key-1", "base_url": "http://localhost:8000/v1", "timeout": 1200},
|
||||
]
|
||||
|
||||
|
||||
@@ -194,4 +220,4 @@ def test_client_initialization_is_safe_under_concurrent_calls(monkeypatch):
|
||||
)
|
||||
)
|
||||
|
||||
assert openai_inits == [{"api_key": "key-1", "base_url": "http://localhost:8000/v1"}]
|
||||
assert openai_inits == [{"api_key": "key-1", "base_url": "http://localhost:8000/v1", "timeout": 600}]
|
||||
|
||||
@@ -22,6 +22,7 @@ def test_process_help_exposes_framework_flags(capsys):
|
||||
assert "--report-json" in output
|
||||
assert "--llm-api-key" in output
|
||||
assert "--llm-concurrency" in output
|
||||
assert "--llm-timeout-seconds" in output
|
||||
assert "--modules" in output
|
||||
assert "--model" in output
|
||||
assert "--base-url" in output
|
||||
@@ -252,6 +253,60 @@ def test_cli_process_passes_llm_concurrency_override_to_config(monkeypatch, tmp_
|
||||
assert captured["llm_concurrency"] == 3
|
||||
|
||||
|
||||
def test_cli_process_passes_llm_timeout_seconds_override_to_config(monkeypatch, tmp_path):
|
||||
captured = {}
|
||||
transcript = parse_transcript_json(
|
||||
"""
|
||||
[
|
||||
{"id": 1, "speaker": "Eric", "start": 0.0, "end": 1.0, "text": "Fixed."}
|
||||
]
|
||||
"""
|
||||
)
|
||||
report = RunReport(
|
||||
status="success",
|
||||
config={"model": "m", "base_url": "b"},
|
||||
normalization={"source_segment_count": 1, "normalized_segment_count": 1, "merge_count": 0},
|
||||
pipeline=["grammar"],
|
||||
modules=[],
|
||||
applied_changes=[],
|
||||
skipped_corrections=[],
|
||||
totals={"output_segment_count": 1, "applied_change_count": 0, "skipped_correction_count": 0},
|
||||
work_dir_retention="auto",
|
||||
work_dir_retained=False,
|
||||
work_dir=None,
|
||||
error=None,
|
||||
)
|
||||
result = ProcessResult(
|
||||
transcript=transcript,
|
||||
report=report,
|
||||
run_dir=tmp_path / "run",
|
||||
work_dir_retained=False,
|
||||
)
|
||||
|
||||
def _fake_from_sources(*, overrides=None):
|
||||
captured["llm_timeout_seconds"] = overrides.llm_timeout_seconds
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr("audita.cli.AuditaConfig.from_sources", _fake_from_sources)
|
||||
monkeypatch.setattr("audita.cli.load_transcript", lambda path: [])
|
||||
monkeypatch.setattr("audita.cli.load_glossary", lambda path: object())
|
||||
monkeypatch.setattr("audita.cli.process_transcript_result", lambda *args, **kwargs: result)
|
||||
|
||||
exit_code = main(
|
||||
[
|
||||
"process",
|
||||
"transcript.json",
|
||||
"--glossary",
|
||||
"glossary.yaml",
|
||||
"--llm-timeout-seconds",
|
||||
"900",
|
||||
]
|
||||
)
|
||||
|
||||
assert exit_code == 0
|
||||
assert captured["llm_timeout_seconds"] == 900.0
|
||||
|
||||
|
||||
def test_cli_process_passes_min_section_tokens_override_to_config(monkeypatch, tmp_path):
|
||||
captured = {}
|
||||
transcript = parse_transcript_json(
|
||||
|
||||
@@ -7,6 +7,7 @@ from audita.core.config import (
|
||||
DEFAULT_GRAMMAR_CONFIDENCE_THRESHOLD,
|
||||
DEFAULT_HOMOPHONES_CONFIDENCE_THRESHOLD,
|
||||
DEFAULT_LLM_CONCURRENCY,
|
||||
DEFAULT_LLM_TIMEOUT_SECONDS,
|
||||
DEFAULT_MAX_SECTION_TOKENS,
|
||||
DEFAULT_MIN_SECTION_TOKENS,
|
||||
DEFAULT_NORMALIZE_MAX_SEGMENT_GAP,
|
||||
@@ -22,6 +23,7 @@ def test_default_config_allows_missing_api_key():
|
||||
|
||||
assert config.api_key is None
|
||||
assert config.llm_concurrency == DEFAULT_LLM_CONCURRENCY
|
||||
assert config.llm_timeout_seconds == DEFAULT_LLM_TIMEOUT_SECONDS
|
||||
assert config.max_section_tokens == DEFAULT_MAX_SECTION_TOKENS
|
||||
assert config.min_section_tokens == DEFAULT_MIN_SECTION_TOKENS
|
||||
assert config.module_keys == DEFAULT_MODULE_KEYS
|
||||
@@ -66,6 +68,21 @@ def test_llm_concurrency_env_is_parsed():
|
||||
assert config.llm_concurrency == 3
|
||||
|
||||
|
||||
def test_llm_timeout_seconds_cli_override_takes_precedence():
|
||||
config = AuditaConfig.from_sources(
|
||||
env={"AUDITA_LLM_TIMEOUT_SECONDS": "120"},
|
||||
overrides=ConfigOverrides(llm_timeout_seconds=900.0),
|
||||
)
|
||||
|
||||
assert config.llm_timeout_seconds == 900.0
|
||||
|
||||
|
||||
def test_llm_timeout_seconds_env_is_parsed():
|
||||
config = AuditaConfig.from_sources(env={"AUDITA_LLM_TIMEOUT_SECONDS": "120.5"})
|
||||
|
||||
assert config.llm_timeout_seconds == 120.5
|
||||
|
||||
|
||||
def test_min_section_tokens_env_is_parsed():
|
||||
config = AuditaConfig.from_sources(env={"AUDITA_MIN_SECTION_TOKENS": "3000"})
|
||||
|
||||
@@ -130,6 +147,12 @@ def test_invalid_work_dir_retention_is_rejected():
|
||||
AuditaConfig.from_sources(env={"AUDITA_WORK_DIR_RETENTION": "sometimes"})
|
||||
|
||||
|
||||
def test_report_dict_includes_llm_timeout_seconds():
|
||||
config = AuditaConfig.from_sources(env={"AUDITA_LLM_TIMEOUT_SECONDS": "321"})
|
||||
|
||||
assert config.to_report_dict()["llm_timeout_seconds"] == 321.0
|
||||
|
||||
|
||||
def test_threshold_overrides_take_precedence():
|
||||
config = AuditaConfig.from_sources(
|
||||
env={
|
||||
@@ -185,6 +208,12 @@ def test_invalid_llm_concurrency_is_rejected(value):
|
||||
AuditaConfig.from_sources(env={"AUDITA_LLM_CONCURRENCY": value})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["0", "-1", "many"])
|
||||
def test_invalid_llm_timeout_seconds_is_rejected(value):
|
||||
with pytest.raises(AuditaConfigError, match="AUDITA_LLM_TIMEOUT_SECONDS"):
|
||||
AuditaConfig.from_sources(env={"AUDITA_LLM_TIMEOUT_SECONDS": value})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["0", "-1", "many"])
|
||||
def test_invalid_min_section_tokens_is_rejected(value):
|
||||
with pytest.raises(AuditaConfigError, match="AUDITA_MIN_SECTION_TOKENS"):
|
||||
|
||||
Reference in New Issue
Block a user