From 64cd08297bc2a49f52e30e00a2ad226deedd206e Mon Sep 17 00:00:00 2001 From: kswdev0 Date: Thu, 26 Mar 2026 12:56:56 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20AIPlanner=20JSON-only=20repair=20?= =?UTF-8?q?=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8=20=EB=B0=8F=20max=5Ftokens?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JSON-only repair 프롬프트의 내용을 간소화하여 명확한 요구사항을 전달하도록 수정 - 재시도 시 max_tokens를 250으로 줄이고, temperature를 0.0으로 설정하여 JSON 포맷 준수율을 높임 - README.md에 변경 사항 반영 --- README.md | 2 +- ai_planner.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f75e439..650aebb 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ planner.set_goal( - `agent_log.jsonl`에 모든 행동과 타임스탬프가 기록됩니다 - `ai_planner.py`는 GLM 응답이 잘리거나(중괄호/대괄호 불일치) 마크다운이 섞여도 JSON 파싱을 복구하도록 `{} / []` 균형 추적과 보정 로직을 사용합니다. 또한 최상위에서 JSON 배열(`[...]`)로 답하는 경우도 `actions`로 래핑해 처리합니다. (추가) `finish_reason=length` 등으로 `message.content`가 비거나(또는 JSON 형태가 아니면) `message.reasoning_content`를 우선 사용합니다. 그리고 응답 안에 여러 개의 `{...}`가 섞여 있어도 그중 `actions`를 포함한 계획 객체를 우선 선택합니다. 또한 JSON 파싱 실패가 감지되면 다음 재시도에는 `JSON-only repair` 프롬프트를 덧붙여 모델이 스키마를 다시 따르도록 유도합니다. - 재시도(attempt>0)에서는 `max_tokens`를 줄여(기본 `900`) 분석 텍스트가 길어져 JSON이 잘리는 패턴을 줄입니다. -- `JSON-only repair` 프롬프트가 붙는 경우에는 `max_tokens`를 더 낮추고(기본 `600`) `temperature`도 더 낮춰(JSON 포맷 준수율을 올리려) 요청합니다. +- `JSON-only repair` 프롬프트가 붙는 경우에는 `max_tokens`를 더 낮추고(기본 `250`) `temperature`도 더 낮춰(기본 `0.0`) JSON 포맷 준수율을 올리려 요청합니다. - `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)로 바꿀 수 있습니다. diff --git a/ai_planner.py b/ai_planner.py index bab23f2..7d2f942 100644 --- a/ai_planner.py +++ b/ai_planner.py @@ -226,17 +226,10 @@ class AIPlanner: or "actions 스키마" in str(e) ): repair_suffix = ( - "\n\n[중요] 이전 응답은 JSON 요구사항을 위반했습니다.\n" - "지금은 아래 스키마의 JSON 객체만 '그대로' 반환하세요.\n" - "마크다운/설명 금지.\n" - "응답의 첫 비공백 문자는 반드시 `{` 입니다.\n" - "키 이름과 구문은 동일해야 합니다.\n" - "{" - "\"thinking\":\"\"," - "\"current_goal\":\"\"," - "\"actions\":[{\"action\":\"explore\",\"params\":{},\"reason\":\"\"}]," - "\"after_this\":\"\"" - "}" + "\n\n[중요] JSON-only. 분석/설명/마크다운 금지.\n" + "응답은 반드시 첫 비공백 문자가 `{` 이고, 마지막도 `}` 입니다.\n" + "아래 JSON 스키마를 그대로(키/구조 동일) 반환하세요:\n" + "{\"thinking\":\"\",\"current_goal\":\"\",\"actions\":[{\"action\":\"wait\",\"params\":{\"seconds\":1},\"reason\":\"repair\"}],\"after_this\":\"재시도\"}" ) if _glm_debug_enabled(): print("[GLM][디버그] JSON-only repair 프롬프트 적용") @@ -359,7 +352,7 @@ class AIPlanner: base_max_tokens = int(os.environ.get("GLM_MAX_TOKENS", "2000")) retry_max_tokens = int(os.environ.get("GLM_RETRY_MAX_TOKENS", "900")) - repair_max_tokens = int(os.environ.get("GLM_REPAIR_MAX_TOKENS", "600")) + repair_max_tokens = int(os.environ.get("GLM_REPAIR_MAX_TOKENS", "250")) if repair_mode: max_tokens = repair_max_tokens @@ -369,7 +362,7 @@ class AIPlanner: temperature = 0.3 if repair_mode: # JSON 포맷 준수율을 높이기 위해 변동성을 낮춘다. - temperature = float(os.environ.get("GLM_REPAIR_TEMPERATURE", "0.1")) + temperature = float(os.environ.get("GLM_REPAIR_TEMPERATURE", "0.0")) payload = json.dumps({ "model": GLM_MODEL,