feat: explore 액션을 시스템 프롬프트에 추가
- explore를 최우선 탐색 액션으로 안내 - move는 "좌표를 알 때만" 사용하도록 변경 - fallback 행동도 move→explore로 변경 - 방향별 순서 안내 (east→north→south→west)
This commit is contained in:
@@ -63,9 +63,16 @@ SYSTEM_PROMPT = """당신은 팩토리오 게임을 순수하게 플레이하는
|
||||
|
||||
## 전체 action 목록
|
||||
|
||||
### 이동 (실제 걷기 — 거리에 비례해 시간 소요!)
|
||||
### 탐색 (★ 자원 없을 때 최우선! 걸으면서 자원 스캔)
|
||||
- "explore" → {"direction": "east|west|north|south|...", "max_steps": 200}
|
||||
★ 자원이 보이지 않을 때 반드시 explore 사용! move 대신!
|
||||
★ 방향으로 걸으면서 반경 50타일 자원 스캔, 발견 즉시 멈춤
|
||||
★ 장애물 자동 감지. 막히면 다른 방향 시도
|
||||
★ 한 방향 실패 시 다음 방향 (east→north→south→west)
|
||||
|
||||
### 이동 (자원 좌표를 알 때만 사용)
|
||||
- "move" → {"x": int, "y": int}
|
||||
주의: 건설/채굴/삽입 전에 반드시 해당 위치 근처로 move
|
||||
주의: 자원/건물의 정확한 좌표를 알 때만 사용. 탐색에는 explore!
|
||||
|
||||
### 채굴 (자원 패치 근처에서만 작동)
|
||||
- "mine_resource" → {"ore": "iron-ore", "count": int}
|
||||
@@ -149,7 +156,7 @@ class AIPlanner:
|
||||
"thinking": "API 응답 파싱 실패로 기본 탐색 수행",
|
||||
"current_goal": "주변 탐색",
|
||||
"actions": [
|
||||
{"action": "move", "params": {"x": 20, "y": 0}, "reason": "탐색을 위해 이동"},
|
||||
{"action": "explore", "params": {"direction": "east", "max_steps": 200}, "reason": "자원 탐색"},
|
||||
],
|
||||
"after_this": "자원 발견 후 채굴 시작"
|
||||
}
|
||||
@@ -196,7 +203,7 @@ class AIPlanner:
|
||||
{"role": "user", "content": user_message},
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 2000, # 1500 → 2000으로 증가 (잘림 방지)
|
||||
"max_tokens": 2000,
|
||||
}).encode("utf-8")
|
||||
|
||||
req = urllib.request.Request(
|
||||
@@ -216,38 +223,25 @@ class AIPlanner:
|
||||
raise ConnectionError(f"GLM API 오류 {e.code}: {e.read().decode()}")
|
||||
|
||||
def _parse_json(self, raw: str) -> dict:
|
||||
"""GLM 응답에서 JSON을 안전하게 추출. 여러 형태 대응."""
|
||||
text = raw.strip()
|
||||
|
||||
# 1. <think> 태그 제거
|
||||
if "<think>" in text:
|
||||
text = text.split("</think>")[-1].strip()
|
||||
|
||||
# 2. 마크다운 코드블록 제거
|
||||
if text.startswith("```"):
|
||||
# ```json ... ``` 패턴
|
||||
text = "\n".join(
|
||||
l for l in text.splitlines()
|
||||
if not l.strip().startswith("```")
|
||||
).strip()
|
||||
|
||||
# 3. 순수 JSON 시도
|
||||
try:
|
||||
return json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# 4. { } 범위 추출 (중첩 괄호 고려)
|
||||
start = text.find("{")
|
||||
if start == -1:
|
||||
raise ValueError("JSON 파싱 실패 ('{' 없음):\n" + raw[:300])
|
||||
|
||||
# 중첩 괄호 카운팅으로 정확한 끝 위치 찾기
|
||||
depth = 0
|
||||
in_string = False
|
||||
escape = False
|
||||
end = start
|
||||
|
||||
for i in range(start, len(text)):
|
||||
c = text[i]
|
||||
if escape:
|
||||
@@ -268,50 +262,32 @@ class AIPlanner:
|
||||
if depth == 0:
|
||||
end = i + 1
|
||||
break
|
||||
|
||||
if depth != 0:
|
||||
# 괄호가 안 닫힘 — 잘린 응답. 복구 시도
|
||||
partial = text[start:]
|
||||
# 잘린 actions 배열 복구
|
||||
partial = self._repair_truncated_json(partial)
|
||||
try:
|
||||
return json.loads(partial)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError(f"JSON 파싱 실패 (잘린 응답 복구 불가):\n{raw[:400]}")
|
||||
|
||||
try:
|
||||
return json.loads(text[start:end])
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError(f"JSON 파싱 실패:\n{raw[:400]}")
|
||||
|
||||
def _repair_truncated_json(self, text: str) -> str:
|
||||
"""잘린 JSON 응답 복구 시도"""
|
||||
# 마지막 완전한 action 항목까지만 유지
|
||||
# actions 배열에서 마지막 완전한 } 를 찾아서 배열 닫기
|
||||
|
||||
# "actions" 키가 있는지 확인
|
||||
if '"actions"' not in text:
|
||||
# actions도 없으면 기본 구조로 대체
|
||||
return '{"thinking":"응답 잘림","current_goal":"탐색","actions":[],"after_this":"재시도"}'
|
||||
|
||||
# 마지막으로 완전한 action 객체의 } 위치 찾기
|
||||
last_complete = -1
|
||||
# "reason": "..." } 패턴의 마지막 위치
|
||||
for m in re.finditer(r'"reason"\s*:\s*"[^"]*"\s*\}', text):
|
||||
last_complete = m.end()
|
||||
|
||||
if last_complete > 0:
|
||||
# 그 지점까지 자르고 배열과 객체 닫기
|
||||
result = text[:last_complete]
|
||||
# 열린 괄호 세기
|
||||
open_brackets = result.count('[') - result.count(']')
|
||||
open_braces = result.count('{') - result.count('}')
|
||||
result += ']' * open_brackets
|
||||
# after_this 추가
|
||||
result += ',"after_this":"계속 진행"'
|
||||
result += '}' * open_braces
|
||||
return result
|
||||
|
||||
return '{"thinking":"응답 잘림","current_goal":"탐색","actions":[],"after_this":"재시도"}'
|
||||
|
||||
def set_goal(self, goal: str):
|
||||
|
||||
Reference in New Issue
Block a user