generated from coulomb/repo-seed
WP-0001 — Foundation & GAAF Baseline - SCOPE.md, ARCHITECTURE-LAYERS.md, contracts/ tree - .claude/rules/ stubs filled (architecture, stack, boundary) - 57 tests (pytest), pyproject.toml with ruff+mypy, CI workflow WP-0002 — Core Extensions (FR-4 + FR-3) - FR-4: BudgetTracker (thread-safe) + LLMBudgetExceededError + optional RunConfig.budget_tracker + enforcement in all adapters - FR-3: async_execute_prompt on LLMAdapter ABC (asyncio.to_thread fallback) + native asyncio.create_subprocess_exec in ClaudeCodeAdapter 81 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.9 KiB
Python
87 lines
2.9 KiB
Python
"""
|
|
Tests for RunConfig and LLMResponse (Core models).
|
|
"""
|
|
|
|
import pytest
|
|
from llm_connect.models import RunConfig, LLMResponse
|
|
|
|
|
|
class TestRunConfig:
|
|
def test_defaults(self):
|
|
cfg = RunConfig()
|
|
assert cfg.model_name == "gpt-4"
|
|
assert cfg.temperature == 0.7
|
|
assert cfg.max_tokens == 2000
|
|
assert cfg.model_params == {}
|
|
assert cfg.max_depth == 3
|
|
assert cfg.skip_if_exists is True
|
|
assert cfg.timeout_seconds == 300
|
|
|
|
def test_custom_values(self):
|
|
cfg = RunConfig(model_name="gemini-2.5-flash", temperature=0.1, max_tokens=500)
|
|
assert cfg.model_name == "gemini-2.5-flash"
|
|
assert cfg.temperature == 0.1
|
|
assert cfg.max_tokens == 500
|
|
|
|
def test_to_dict_roundtrip(self):
|
|
cfg = RunConfig(model_name="gpt-4o", temperature=0.3, max_tokens=1000)
|
|
d = cfg.to_dict()
|
|
assert d["model_name"] == "gpt-4o"
|
|
assert d["temperature"] == 0.3
|
|
assert d["max_tokens"] == 1000
|
|
|
|
def test_from_dict_roundtrip(self):
|
|
original = RunConfig(model_name="claude-3", temperature=0.5, max_tokens=800)
|
|
restored = RunConfig.from_dict(original.to_dict())
|
|
assert restored.model_name == original.model_name
|
|
assert restored.temperature == original.temperature
|
|
assert restored.max_tokens == original.max_tokens
|
|
|
|
def test_from_dict_uses_defaults_for_missing_keys(self):
|
|
cfg = RunConfig.from_dict({})
|
|
assert cfg.model_name == "gpt-4"
|
|
assert cfg.temperature == 0.7
|
|
|
|
def test_model_params_default_is_independent(self):
|
|
a = RunConfig()
|
|
b = RunConfig()
|
|
a.model_params["x"] = 1
|
|
assert "x" not in b.model_params
|
|
|
|
|
|
class TestLLMResponse:
|
|
def test_minimal_construction(self):
|
|
r = LLMResponse(content="hello", model="test-model")
|
|
assert r.content == "hello"
|
|
assert r.model == "test-model"
|
|
assert r.usage == {}
|
|
assert r.finish_reason == "stop"
|
|
assert r.metadata == {}
|
|
|
|
def test_full_construction(self):
|
|
r = LLMResponse(
|
|
content="response text",
|
|
model="gpt-4",
|
|
usage={"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
|
|
finish_reason="length",
|
|
metadata={"provider": "openai", "latency_seconds": 1.2},
|
|
)
|
|
assert r.usage["total_tokens"] == 15
|
|
assert r.finish_reason == "length"
|
|
assert r.metadata["provider"] == "openai"
|
|
|
|
def test_to_dict(self):
|
|
r = LLMResponse(content="hi", model="m", finish_reason="stop")
|
|
d = r.to_dict()
|
|
assert d["content"] == "hi"
|
|
assert d["model"] == "m"
|
|
assert d["finish_reason"] == "stop"
|
|
assert "usage" in d
|
|
assert "metadata" in d
|
|
|
|
def test_metadata_default_is_independent(self):
|
|
a = LLMResponse(content="a", model="m")
|
|
b = LLMResponse(content="b", model="m")
|
|
a.metadata["x"] = 1
|
|
assert "x" not in b.metadata
|