fix: PAAI OpenClaw 호출 방식 변경 (WebSocket -> CLI)
- OpenClaw 업데이트로 device identity 필수화됨 - WebSocket 대신 Node.js 직접 호출로 변경 - 특수문자/줄바꿈 문제 해결 (shell=True 제거) - subprocess array 방식으로 안전한 인자 전달
This commit is contained in:
@@ -25,23 +25,29 @@ import websockets
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Gateway 설정 (clawdbot.json에서 읽기)
|
# Gateway 설정 (openclaw.json 또는 clawdbot.json에서 읽기)
|
||||||
|
OPENCLAW_CONFIG_PATH = Path.home() / '.openclaw' / 'openclaw.json'
|
||||||
CLAWDBOT_CONFIG_PATH = Path.home() / '.clawdbot' / 'clawdbot.json'
|
CLAWDBOT_CONFIG_PATH = Path.home() / '.clawdbot' / 'clawdbot.json'
|
||||||
|
|
||||||
|
|
||||||
def _load_gateway_config():
|
def _load_gateway_config():
|
||||||
"""clawdbot.json에서 Gateway 설정 로드"""
|
"""OpenClaw/Clawdbot Gateway 설정 로드"""
|
||||||
try:
|
for config_path in [OPENCLAW_CONFIG_PATH, CLAWDBOT_CONFIG_PATH]:
|
||||||
with open(CLAWDBOT_CONFIG_PATH, 'r', encoding='utf-8') as f:
|
try:
|
||||||
config = json.load(f)
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
gw = config.get('gateway', {})
|
config = json.load(f)
|
||||||
return {
|
gw = config.get('gateway', {})
|
||||||
'port': gw.get('port', 18789),
|
token = gw.get('auth', {}).get('token', '')
|
||||||
'token': gw.get('auth', {}).get('token', ''),
|
if token:
|
||||||
}
|
logger.info(f"[Gateway] 설정 로드: {config_path.name}")
|
||||||
except Exception as e:
|
return {
|
||||||
logger.warning(f"[Clawdbot] 설정 파일 로드 실패: {e}")
|
'port': gw.get('port', 18789),
|
||||||
return {'port': 18789, 'token': ''}
|
'token': token,
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
logger.warning("[Gateway] 설정 파일 로드 실패")
|
||||||
|
return {'port': 18789, 'token': ''}
|
||||||
|
|
||||||
|
|
||||||
async def _ask_gateway(message, session_id='pharmacy-upsell',
|
async def _ask_gateway(message, session_id='pharmacy-upsell',
|
||||||
@@ -85,10 +91,10 @@ async def _ask_gateway(message, session_id='pharmacy-upsell',
|
|||||||
'maxProtocol': 3,
|
'maxProtocol': 3,
|
||||||
'client': {
|
'client': {
|
||||||
'id': 'gateway-client',
|
'id': 'gateway-client',
|
||||||
'displayName': 'Pharmacy Upsell',
|
'displayName': 'Pharmacy PAAI',
|
||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
'platform': 'win32',
|
'platform': 'win32',
|
||||||
'mode': 'backend',
|
'mode': 'cli',
|
||||||
'instanceId': str(uuid.uuid4()),
|
'instanceId': str(uuid.uuid4()),
|
||||||
},
|
},
|
||||||
'caps': [],
|
'caps': [],
|
||||||
@@ -96,7 +102,7 @@ async def _ask_gateway(message, session_id='pharmacy-upsell',
|
|||||||
'token': token,
|
'token': token,
|
||||||
},
|
},
|
||||||
'role': 'operator',
|
'role': 'operator',
|
||||||
'scopes': ['operator.admin'],
|
'scopes': ['operator.admin', 'operator.write', 'operator.read'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await ws.send(json.dumps(connect_frame))
|
await ws.send(json.dumps(connect_frame))
|
||||||
@@ -198,27 +204,65 @@ async def _ask_gateway(message, session_id='pharmacy-upsell',
|
|||||||
def ask_clawdbot(message, session_id='pharmacy-upsell',
|
def ask_clawdbot(message, session_id='pharmacy-upsell',
|
||||||
system_prompt=None, timeout=60, model=None):
|
system_prompt=None, timeout=60, model=None):
|
||||||
"""
|
"""
|
||||||
동기 래퍼: Flask에서 직접 호출 가능
|
OpenClaw CLI를 통한 AI 호출 (WebSocket 대신)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message: 사용자 메시지
|
message: 사용자 메시지
|
||||||
session_id: 세션 ID (대화 구분용)
|
session_id: 세션 ID (대화 구분용)
|
||||||
system_prompt: 추가 시스템 프롬프트
|
system_prompt: 추가 시스템 프롬프트 (현재 미사용)
|
||||||
timeout: 타임아웃 (초)
|
timeout: 타임아웃 (초)
|
||||||
model: 모델 오버라이드 (예: 'anthropic/claude-sonnet-4-5')
|
model: 모델 오버라이드 (현재 미사용 - CLI가 기본 모델 사용)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: AI 응답 텍스트 (실패 시 None)
|
str: AI 응답 텍스트 (실패 시 None)
|
||||||
"""
|
"""
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop = asyncio.new_event_loop()
|
# Node.js로 OpenClaw 직접 호출 (shell 없이, 특수문자 안전)
|
||||||
result = loop.run_until_complete(
|
node_path = r'C:\Program Files\nodejs\node.exe'
|
||||||
_ask_gateway(message, session_id, system_prompt, timeout, model=model)
|
openclaw_path = str(Path.home() / 'AppData/Roaming/npm/node_modules/openclaw/openclaw.mjs')
|
||||||
|
|
||||||
|
cmd = [node_path, openclaw_path, 'agent', '-m', message, '--session-id', session_id, '--json']
|
||||||
|
logger.info(f"[OpenClaw] session={session_id}, msg_len={len(message)}")
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=timeout + 30,
|
||||||
|
encoding='utf-8',
|
||||||
|
env={**os.environ, 'PYTHONIOENCODING': 'utf-8'}
|
||||||
)
|
)
|
||||||
loop.close()
|
|
||||||
return result
|
if result.returncode != 0:
|
||||||
|
logger.warning(f"[OpenClaw] CLI 에러: {result.stderr}")
|
||||||
|
# 에러가 있어도 stdout에 결과가 있을 수 있음
|
||||||
|
if not result.stdout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# JSON 파싱
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
if data.get('status') == 'ok':
|
||||||
|
payloads = data.get('result', {}).get('payloads', [])
|
||||||
|
if payloads:
|
||||||
|
text = payloads[0].get('text', '')
|
||||||
|
logger.info(f"[OpenClaw] 응답 수신: {len(text)}자")
|
||||||
|
return text
|
||||||
|
|
||||||
|
logger.warning(f"[OpenClaw] 응답 없음: {data}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.warning(f"[OpenClaw] 타임아웃 ({timeout}초)")
|
||||||
|
return None
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.warning(f"[OpenClaw] JSON 파싱 실패: {e}")
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[Clawdbot] 호출 실패: {e}")
|
logger.warning(f"[OpenClaw] 호출 실패: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -356,13 +400,13 @@ async def _get_gateway_status():
|
|||||||
'displayName': 'Pharmacy Status',
|
'displayName': 'Pharmacy Status',
|
||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
'platform': 'win32',
|
'platform': 'win32',
|
||||||
'mode': 'backend',
|
'mode': 'cli',
|
||||||
'instanceId': str(uuid.uuid4()),
|
'instanceId': str(uuid.uuid4()),
|
||||||
},
|
},
|
||||||
'caps': [],
|
'caps': [],
|
||||||
'auth': {'token': token},
|
'auth': {'token': token},
|
||||||
'role': 'operator',
|
'role': 'operator',
|
||||||
'scopes': ['operator.read'],
|
'scopes': ['operator.admin', 'operator.write', 'operator.read'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await ws.send(json.dumps(connect_frame))
|
await ws.send(json.dumps(connect_frame))
|
||||||
|
|||||||
Reference in New Issue
Block a user