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