feat(interviews): LLM stub mode for deterministic CI tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
29be754ff4
commit
eb3c3629c1
|
|
@ -32,6 +32,31 @@ class LLMClient:
|
|||
base_url=self.base_url
|
||||
)
|
||||
|
||||
def _stub_key(self, messages: list[dict]) -> str:
|
||||
user_msg = next((m["content"] for m in reversed(messages) if m.get("role") == "user"), "")
|
||||
sys_msg = next((m["content"] for m in messages if m.get("role") == "system"), "")
|
||||
# Allow callers to embed an explicit stub_key=... token
|
||||
for chunk in user_msg.split():
|
||||
if chunk.startswith("stub_key="):
|
||||
return chunk[len("stub_key="):]
|
||||
import hashlib
|
||||
return hashlib.sha256((sys_msg + "|" + user_msg).encode("utf-8")).hexdigest()[:12]
|
||||
|
||||
def _stub_response(self, messages: list[dict]) -> str:
|
||||
import json as _json
|
||||
return _json.dumps(self._stub_response_json(messages), ensure_ascii=False)
|
||||
|
||||
def _stub_response_json(self, messages: list[dict]) -> dict:
|
||||
key = self._stub_key(messages)
|
||||
# Deterministic centered Likert + plausible open text
|
||||
digit = sum(ord(c) for c in key) % 5 + 1
|
||||
return {
|
||||
"stub_key": key,
|
||||
"responses": {"item_001": digit, "item_002": digit, "item_003": (digit % 5) + 1},
|
||||
"confidence": {"item_001": 0.7, "item_002": 0.7, "item_003": 0.6},
|
||||
"open_comment": f"stub:{key}",
|
||||
}
|
||||
|
||||
def chat(
|
||||
self,
|
||||
messages: List[Dict[str, str]],
|
||||
|
|
@ -51,6 +76,10 @@ class LLMClient:
|
|||
Returns:
|
||||
模型响应文本
|
||||
"""
|
||||
from app.config import Config
|
||||
if getattr(Config, "LLM_STUB_MODE", False):
|
||||
return self._stub_response(messages)
|
||||
|
||||
kwargs = {
|
||||
"model": self.model,
|
||||
"messages": messages,
|
||||
|
|
@ -84,6 +113,10 @@ class LLMClient:
|
|||
Returns:
|
||||
解析后的JSON对象
|
||||
"""
|
||||
from app.config import Config
|
||||
if getattr(Config, "LLM_STUB_MODE", False):
|
||||
return self._stub_response_json(messages)
|
||||
|
||||
response = self.chat(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import json
|
||||
from app.utils.llm_client import LLMClient
|
||||
|
||||
|
||||
def test_stub_mode_returns_deterministic_canned_json(monkeypatch):
|
||||
monkeypatch.setenv("LLM_STUB_MODE", "true")
|
||||
from app.config import Config
|
||||
Config.LLM_STUB_MODE = True
|
||||
client = LLMClient(api_key="x", base_url="x", model="x")
|
||||
messages = [
|
||||
{"role": "system", "content": "You are persona_42. Return JSON."},
|
||||
{"role": "user", "content": "stub_key=longitudinal:item_001"},
|
||||
]
|
||||
out1 = client.chat_json(messages=messages, temperature=0.0)
|
||||
out2 = client.chat_json(messages=messages, temperature=0.0)
|
||||
assert out1 == out2
|
||||
assert isinstance(out1, dict)
|
||||
Loading…
Reference in New Issue