fix: AIPlanner에서 finish_reason 처리 및 휴리스틱 폴백 로직 개선
- 응답의 finish_reason이 'length'이고 비JSON 텍스트가 포함된 경우, 즉시 상태 기반 휴리스틱 폴백으로 전환하는 로직 추가 - AIPlanner 클래스에 _last_glm_finish_reason 속성을 추가하여 이전 finish_reason을 추적 - README.md에 변경 사항 반영
This commit is contained in:
@@ -148,6 +148,7 @@ planner.set_goal(
|
||||
- 재시도(attempt>0)에서는 `max_tokens`를 줄여(기본 `900`) 분석 텍스트가 길어져 JSON이 잘리는 패턴을 줄입니다.
|
||||
- `JSON-only repair` 프롬프트가 붙는 경우에는 `max_tokens`를 더 낮추고(기본 `250`) `temperature`도 더 낮춰(기본 `0.0`) JSON 포맷 준수율을 올리려 요청합니다.
|
||||
- `JSON-only repair`를 한 번 적용했는데도 비JSON 텍스트가 계속 오면, 불필요한 API 재시도 반복 대신 즉시 상태 기반 휴리스틱 폴백으로 전환합니다. 또한 LLM이 `actions=[]`를 반환하면 같은 이유로 휴리스틱 플랜으로 즉시 대체합니다.
|
||||
- `finish_reason=length`이면서 응답이 비JSON 텍스트(`{' 없음`)로 시작하면, repair 재시도를 기다리지 않고 즉시 휴리스틱 폴백으로 전환해 턴 지연을 줄입니다.
|
||||
- `ai_planner.py`의 `AIPlanner.decide()`는 `TimeoutError`/`ConnectionError`/`urllib.error.URLError` 같은 GLM HTTP 지연/연결 오류도 3회 재시도한 뒤, **상태 요약에 나온 광맥(앵커) 좌표가 있으면 `mine_resource`(먼 경우 `move` 후 채굴)로 폴백**하고, 광맥 정보가 없을 때만 `explore` 방향을 순환하며 탐색합니다(동일 방향 탐색 루프 완화).
|
||||
- GLM이 `HTTP 429 (Rate limit)`에 걸리면 `Retry-After`가 있으면 그만큼 기다렸다가(없으면 기본 백오프) 재시도하도록 처리합니다.
|
||||
- GLM HTTP 읽기 제한 시간은 기본 120초이며, `GLM_HTTP_TIMEOUT_SECONDS`로 조정할 수 있습니다. 광맥은 플레이어와 200타일 이상 떨어진 경우에만 폴백에서 `move`를 끼우며, 임계값은 `GLM_FALLBACK_MOVE_THRESHOLD`(기본 200)로 바꿀 수 있습니다.
|
||||
|
||||
@@ -177,6 +177,7 @@ class AIPlanner:
|
||||
|
||||
self.step = 0
|
||||
self.feedback_log: list[dict] = []
|
||||
self._last_glm_finish_reason: str | None = None
|
||||
# GLM 전부 실패 시 explore 방향 순환 (동일 방향 탐색 루프 완화)
|
||||
self._fallback_explore_turn = 0
|
||||
self.long_term_goal = (
|
||||
@@ -222,6 +223,17 @@ class AIPlanner:
|
||||
detail = describe_glm_exception(e)
|
||||
parse_no_brace = isinstance(e, ValueError) and "JSON 파싱 실패 ('{' 없음)" in str(e)
|
||||
|
||||
# 첫 시도에서 이미 finish_reason=length + 비JSON 텍스트면,
|
||||
# repair 재시도도 같은 패턴으로 반복되는 경우가 많아 즉시 폴백한다.
|
||||
if parse_no_brace and self._last_glm_finish_reason == "length":
|
||||
if _glm_debug_enabled():
|
||||
print("[GLM][디버그] finish_reason=length + 비JSON -> 즉시 휴리스틱 폴백")
|
||||
plan = self._fallback_plan_from_summary(
|
||||
state_summary,
|
||||
last_error=detail,
|
||||
)
|
||||
break
|
||||
|
||||
# JSON-only repair를 이미 한 번 적용했는데도 여전히 비JSON 텍스트만 오는 경우:
|
||||
# 추가 API 호출을 반복하지 말고 즉시 상태 기반 휴리스틱으로 진행한다.
|
||||
if parse_no_brace and repair_applied_once:
|
||||
@@ -437,6 +449,8 @@ class AIPlanner:
|
||||
if _glm_debug_enabled():
|
||||
finish_reason = data.get("choices", [{}])[0].get("finish_reason")
|
||||
print(f"[GLM][디버그] finish_reason={finish_reason!r}")
|
||||
finish_reason = data.get("choices", [{}])[0].get("finish_reason")
|
||||
self._last_glm_finish_reason = str(finish_reason) if finish_reason is not None else None
|
||||
content = self._extract_glm_assistant_text(data).strip()
|
||||
if not content and _glm_debug_enabled():
|
||||
# content가 비어있으면 아래 파서에서 원인 추적이 어려워지므로 raw 일부를 남긴다.
|
||||
@@ -466,6 +480,7 @@ class AIPlanner:
|
||||
)
|
||||
return content
|
||||
except urllib.error.HTTPError as e:
|
||||
self._last_glm_finish_reason = None
|
||||
body = ""
|
||||
try:
|
||||
body = e.read().decode("utf-8", errors="replace")
|
||||
|
||||
Reference in New Issue
Block a user