fix(wizard): 503 on LLM error, sanitize history content, typed HistoryMessage model
This commit is contained in:
parent
6d1edff1b9
commit
e9943908c6
2 changed files with 20 additions and 16 deletions
28
dev-api.py
28
dev-api.py
|
|
@ -4541,8 +4541,13 @@ You must ALWAYS respond with valid JSON in this exact format:
|
||||||
Only include fields in extracted_fields that you are confident about from the conversation. Do not include fields the user hasn't mentioned. Infer complete=true when all required fields (name, email, career_summary) are gathered or when user explicitly says done."""
|
Only include fields in extracted_fields that you are confident about from the conversation. Do not include fields the user hasn't mentioned. Infer complete=true when all required fields (name, email, career_summary) are gathered or when user explicitly says done."""
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryMessage(BaseModel):
|
||||||
|
role: str # "user" or "assistant"
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
class WizardInterviewRequest(BaseModel):
|
class WizardInterviewRequest(BaseModel):
|
||||||
history: list[dict] # [{"role": "user"|"assistant", "content": "..."}]
|
history: list[HistoryMessage] = []
|
||||||
profile_so_far: dict = {}
|
profile_so_far: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4574,8 +4579,8 @@ def wizard_ai_interview(request: WizardInterviewRequest):
|
||||||
# Build conversation prompt from history
|
# Build conversation prompt from history
|
||||||
conversation_lines = []
|
conversation_lines = []
|
||||||
for msg in request.history:
|
for msg in request.history:
|
||||||
role = msg.get("role", "user")
|
role = msg.role
|
||||||
content = msg.get("content", "")
|
content = msg.content.replace("\n", " ").replace("\r", "")
|
||||||
if role == "user":
|
if role == "user":
|
||||||
conversation_lines.append(f"User: {content}")
|
conversation_lines.append(f"User: {content}")
|
||||||
else:
|
else:
|
||||||
|
|
@ -4600,7 +4605,7 @@ def wizard_ai_interview(request: WizardInterviewRequest):
|
||||||
from scripts.llm_router import LLMRouter
|
from scripts.llm_router import LLMRouter
|
||||||
response_text = LLMRouter().complete(prompt, system=_AI_WIZARD_SYSTEM_PROMPT)
|
response_text = LLMRouter().complete(prompt, system=_AI_WIZARD_SYSTEM_PROMPT)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(500, detail={"error": "llm_error", "message": str(exc)})
|
raise HTTPException(503, detail={"error": "llm_error", "message": str(exc)})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(response_text)
|
parsed = json.loads(response_text)
|
||||||
|
|
@ -4617,15 +4622,14 @@ def wizard_ai_interview(request: WizardInterviewRequest):
|
||||||
def wizard_ai_finalize(request: WizardFinalizeRequest):
|
def wizard_ai_finalize(request: WizardFinalizeRequest):
|
||||||
"""Merge AI-collected wizard fields into user.yaml. Only allowed fields are written."""
|
"""Merge AI-collected wizard fields into user.yaml. Only allowed fields are written."""
|
||||||
yaml_path = _user_yaml_path()
|
yaml_path = _user_yaml_path()
|
||||||
|
try:
|
||||||
current = load_user_profile(yaml_path)
|
current = load_user_profile(yaml_path)
|
||||||
|
updates = {k: v for k, v in request.profile.items() if k in _WIZARD_ALLOWED_FIELDS}
|
||||||
merged_keys = []
|
merged = {**current, **updates}
|
||||||
for key, value in request.profile.items():
|
save_user_profile(yaml_path, merged)
|
||||||
if key in _WIZARD_ALLOWED_FIELDS:
|
except Exception as exc:
|
||||||
current[key] = value
|
raise HTTPException(500, detail={"error": "write_error", "message": str(exc)})
|
||||||
merged_keys.append(key)
|
merged_keys = list(updates.keys())
|
||||||
|
|
||||||
save_user_profile(yaml_path, current)
|
|
||||||
return {"saved": True, "fields": merged_keys}
|
return {"saved": True, "fields": merged_keys}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -226,8 +226,8 @@ class TestWizardAIInterviewLLM:
|
||||||
assert "Alex Rivera" in prompt
|
assert "Alex Rivera" in prompt
|
||||||
assert "alex@example.com" in prompt
|
assert "alex@example.com" in prompt
|
||||||
|
|
||||||
def test_llm_error_returns_500(self, client):
|
def test_llm_error_returns_503(self, client):
|
||||||
"""If LLM raises, the endpoint returns 500."""
|
"""If LLM raises, the endpoint returns 503."""
|
||||||
with patch("dev_api._get_effective_tier", return_value="paid"):
|
with patch("dev_api._get_effective_tier", return_value="paid"):
|
||||||
with patch("app.wizard.tiers.has_configured_llm", return_value=True):
|
with patch("app.wizard.tiers.has_configured_llm", return_value=True):
|
||||||
with patch("scripts.llm_router.LLMRouter") as mock_cls:
|
with patch("scripts.llm_router.LLMRouter") as mock_cls:
|
||||||
|
|
@ -236,7 +236,7 @@ class TestWizardAIInterviewLLM:
|
||||||
"/api/wizard/ai/interview",
|
"/api/wizard/ai/interview",
|
||||||
json={"history": [{"role": "user", "content": "hi"}]},
|
json={"history": [{"role": "user", "content": "hi"}]},
|
||||||
)
|
)
|
||||||
assert r.status_code == 500
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
|
||||||
# ── POST /api/wizard/ai/finalize ──────────────────────────────────────────────
|
# ── POST /api/wizard/ai/finalize ──────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue