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__)
|
||||
|
||||
# Gateway 설정 (clawdbot.json에서 읽기)
|
||||
# Gateway 설정 (openclaw.json 또는 clawdbot.json에서 읽기)
|
||||
OPENCLAW_CONFIG_PATH = Path.home() / '.openclaw' / 'openclaw.json'
|
||||
CLAWDBOT_CONFIG_PATH = Path.home() / '.clawdbot' / 'clawdbot.json'
|
||||
|
||||
|
||||
def _load_gateway_config():
|
||||
"""clawdbot.json에서 Gateway 설정 로드"""
|
||||
try:
|
||||
with open(CLAWDBOT_CONFIG_PATH, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
gw = config.get('gateway', {})
|
||||
return {
|
||||
'port': gw.get('port', 18789),
|
||||
'token': gw.get('auth', {}).get('token', ''),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"[Clawdbot] 설정 파일 로드 실패: {e}")
|
||||
return {'port': 18789, 'token': ''}
|
||||
"""OpenClaw/Clawdbot Gateway 설정 로드"""
|
||||
for config_path in [OPENCLAW_CONFIG_PATH, CLAWDBOT_CONFIG_PATH]:
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
gw = config.get('gateway', {})
|
||||
token = gw.get('auth', {}).get('token', '')
|
||||
if token:
|
||||
logger.info(f"[Gateway] 설정 로드: {config_path.name}")
|
||||
return {
|
||||
'port': gw.get('port', 18789),
|
||||
'token': token,
|
||||
}
|
||||
except Exception:
|
||||
continue
|
||||
logger.warning("[Gateway] 설정 파일 로드 실패")
|
||||
return {'port': 18789, 'token': ''}
|
||||
|
||||
|
||||
async def _ask_gateway(message, session_id='pharmacy-upsell',
|
||||
@@ -85,10 +91,10 @@ async def _ask_gateway(message, session_id='pharmacy-upsell',
|
||||
'maxProtocol': 3,
|
||||
'client': {
|
||||
'id': 'gateway-client',
|
||||
'displayName': 'Pharmacy Upsell',
|
||||
'displayName': 'Pharmacy PAAI',
|
||||
'version': '1.0.0',
|
||||
'platform': 'win32',
|
||||
'mode': 'backend',
|
||||
'mode': 'cli',
|
||||
'instanceId': str(uuid.uuid4()),
|
||||
},
|
||||
'caps': [],
|
||||
@@ -96,7 +102,7 @@ async def _ask_gateway(message, session_id='pharmacy-upsell',
|
||||
'token': token,
|
||||
},
|
||||
'role': 'operator',
|
||||
'scopes': ['operator.admin'],
|
||||
'scopes': ['operator.admin', 'operator.write', 'operator.read'],
|
||||
}
|
||||
}
|
||||
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',
|
||||
system_prompt=None, timeout=60, model=None):
|
||||
"""
|
||||
동기 래퍼: Flask에서 직접 호출 가능
|
||||
|
||||
OpenClaw CLI를 통한 AI 호출 (WebSocket 대신)
|
||||
|
||||
Args:
|
||||
message: 사용자 메시지
|
||||
session_id: 세션 ID (대화 구분용)
|
||||
system_prompt: 추가 시스템 프롬프트
|
||||
system_prompt: 추가 시스템 프롬프트 (현재 미사용)
|
||||
timeout: 타임아웃 (초)
|
||||
model: 모델 오버라이드 (예: 'anthropic/claude-sonnet-4-5')
|
||||
model: 모델 오버라이드 (현재 미사용 - CLI가 기본 모델 사용)
|
||||
|
||||
Returns:
|
||||
str: AI 응답 텍스트 (실패 시 None)
|
||||
"""
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
loop = asyncio.new_event_loop()
|
||||
result = loop.run_until_complete(
|
||||
_ask_gateway(message, session_id, system_prompt, timeout, model=model)
|
||||
# Node.js로 OpenClaw 직접 호출 (shell 없이, 특수문자 안전)
|
||||
node_path = r'C:\Program Files\nodejs\node.exe'
|
||||
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:
|
||||
logger.warning(f"[Clawdbot] 호출 실패: {e}")
|
||||
logger.warning(f"[OpenClaw] 호출 실패: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -356,13 +400,13 @@ async def _get_gateway_status():
|
||||
'displayName': 'Pharmacy Status',
|
||||
'version': '1.0.0',
|
||||
'platform': 'win32',
|
||||
'mode': 'backend',
|
||||
'mode': 'cli',
|
||||
'instanceId': str(uuid.uuid4()),
|
||||
},
|
||||
'caps': [],
|
||||
'auth': {'token': token},
|
||||
'role': 'operator',
|
||||
'scopes': ['operator.read'],
|
||||
'scopes': ['operator.admin', 'operator.write', 'operator.read'],
|
||||
}
|
||||
}
|
||||
await ws.send(json.dumps(connect_frame))
|
||||
|
||||
Reference in New Issue
Block a user