feat: implement automatic payload candidate retry mechanism in AIPlanner for improved LM Studio compatibility

This commit is contained in:
21in7
2026-03-27 20:07:32 +09:00
parent 2cf072d38c
commit d054f9aee1
4 changed files with 94 additions and 12 deletions

View File

@@ -196,14 +196,31 @@ class AIPlanner:
spinner.start()
try:
payload = self._build_chat_payload(user_message)
resp = httpx.post(
f"{OLLAMA_HOST}/api/v1/chat",
json=payload,
timeout=600.0,
)
resp.raise_for_status()
data = resp.json()
data = None
last_http_error: Exception | None = None
candidates = self._build_payload_candidates(user_message)
for i, payload in enumerate(candidates):
resp = httpx.post(
f"{OLLAMA_HOST}/api/v1/chat",
json=payload,
timeout=600.0,
)
if resp.status_code < 400:
data = resp.json()
break
if resp.status_code == 400 and i < len(candidates) - 1:
err_msg = self._extract_http_error_message(resp)
print(f"[AI] payload 호환 재시도 {i + 1}: {err_msg}")
continue
try:
resp.raise_for_status()
except Exception as e:
last_http_error = e
break
if data is None:
if last_http_error is not None:
raise last_http_error
raise RuntimeError("Ollama/LM Studio 응답을 받지 못했습니다.")
finally:
stop_event.set()
spinner.join()
@@ -232,6 +249,40 @@ class AIPlanner:
"options": {"temperature": 0.3, "num_ctx": 8192},
}
@staticmethod
def _build_input_only_payload(user_message: str) -> dict:
merged = f"{SYSTEM_PROMPT}\n\n{user_message}"
return {
"model": OLLAMA_MODEL,
"input": merged,
"stream": False,
}
@staticmethod
def _build_payload_candidates(user_message: str) -> list[dict]:
# 서버 구현체마다 스키마가 달라 자동 호환을 위해 후보를 순차 시도한다.
return [
AIPlanner._build_chat_payload(user_message),
AIPlanner._build_input_only_payload(user_message),
]
@staticmethod
def _extract_http_error_message(resp: httpx.Response) -> str:
try:
data = resp.json()
if isinstance(data, dict):
err = data.get("error")
if isinstance(err, dict):
msg = err.get("message")
if isinstance(msg, str) and msg.strip():
return msg
except Exception:
pass
text = resp.text.strip()
if text:
return text[:200]
return f"HTTP {resp.status_code}"
@staticmethod
def _extract_response_content(data: dict) -> str:
message = data.get("message")