pharmacy-pos-qr-system/docs/clawdbot-gateway-api.md
thug0bin 2a090c9704 feat: Clawdbot Gateway 모니터링 페이지 + API 클라이언트
- /admin/ai-gw: 토큰 사용량/비용 실시간 모니터링 대시보드
- clawdbot_client.py: Gateway HTTP API 클라이언트 (세션 상태, 사용량 조회)
- 세션별 토큰/비용 통계, 모델별 breakdown
- API 문서 추가 (docs/clawdbot-gateway-api.md)
2026-02-27 12:22:05 +09:00

9.1 KiB

Clawdbot Gateway WebSocket API 가이드

외부 애플리케이션에서 Clawdbot Gateway에 연결하여 AI 호출 또는 상태 조회하는 방법

개요

Clawdbot Gateway는 WebSocket API를 제공합니다. 이를 통해:

  • AI 호출 (agent 메서드) — Claude/GPT 등 모델에 질문 (토큰 소비)
  • 상태 조회 (sessions.list 등) — 세션 정보 조회 (토큰 무소비)
  • 세션 설정 (sessions.patch) — 모델 오버라이드 등

아키텍처

┌─────────────────┐     WebSocket      ┌─────────────────┐
│  Flask 서버     │ ◄─────────────────► │ Clawdbot Gateway│
│  (pharmacy-pos) │    Port 18789       │   (localhost)   │
└─────────────────┘                     └────────┬────────┘
                                                 │
                                        ┌────────▼────────┐
                                        │  Claude / GPT   │
                                        │   (Providers)   │
                                        └─────────────────┘

설정 파일 위치

Gateway 설정은 ~/.clawdbot/clawdbot.json에 있음:

{
  "gateway": {
    "port": 18789,
    "auth": {
      "mode": "token",
      "token": "your-gateway-token"
    }
  }
}

연결 프로토콜 (Python)

1. 기본 연결 흐름

import asyncio
import json
import uuid
import websockets

async def connect_to_gateway():
    config = load_gateway_config()  # ~/.clawdbot/clawdbot.json 읽기
    url = f"ws://127.0.0.1:{config['port']}"
    token = config['token']
    
    async with websockets.connect(url) as ws:
        # 1단계: challenge 수신
        challenge = json.loads(await ws.recv())
        # {'event': 'connect.challenge', 'payload': {'nonce': '...'}}
        
        # 2단계: connect 요청
        connect_frame = {
            'type': 'req',
            'id': str(uuid.uuid4()),
            'method': 'connect',
            'params': {
                'minProtocol': 3,
                'maxProtocol': 3,
                'client': {
                    'id': 'gateway-client',      # 고정값
                    'displayName': 'My App',
                    'version': '1.0.0',
                    'platform': 'win32',
                    'mode': 'backend',           # 고정값
                    'instanceId': str(uuid.uuid4()),
                },
                'caps': [],
                'auth': {'token': token},
                'role': 'operator',
                'scopes': ['operator.admin'],    # 또는 ['operator.read']
            }
        }
        await ws.send(json.dumps(connect_frame))
        
        # 3단계: connect 응답 대기
        while True:
            msg = json.loads(await ws.recv())
            if msg.get('id') == connect_frame['id']:
                if msg.get('ok'):
                    print("연결 성공!")
                    break
                else:
                    print(f"연결 실패: {msg.get('error')}")
                    return
        
        # 이제 다른 메서드 호출 가능
        # ...

2. 주의사항: client 파라미터

⚠️ 중요: client.idclient.mode는 Gateway 스키마에 정의된 값만 허용됨

필드 허용되는 값 설명
client.id 'gateway-client' 백엔드 클라이언트용
client.mode 'backend' 백엔드 모드
role 'operator' 제어 클라이언트
scopes ['operator.admin'] 또는 ['operator.read'] 권한 범위

잘못된 값 사용 시 에러:

invalid connect params: at /client/id: must be equal to constant

메서드 종류

토큰 소비 없는 메서드 (관리용)

메서드 용도 파라미터
sessions.list 세션 목록 조회 {limit: 10}
sessions.patch 세션 설정 변경 {key: '...', model: '...'}

토큰 소비하는 메서드 (AI 호출)

메서드 용도 파라미터
agent AI에게 질문 {message: '...', sessionId: '...'}

실제 구현 예제

예제 1: 상태 조회 (토큰 0)

# services/clawdbot_client.py 참고

async def _get_gateway_status():
    """세션 목록 조회 — 토큰 소비 없음"""
    # ... (연결 코드 생략)
    
    # sessions.list 요청
    list_frame = {
        'type': 'req',
        'id': str(uuid.uuid4()),
        'method': 'sessions.list',
        'params': {'limit': 10}
    }
    await ws.send(json.dumps(list_frame))
    
    # 응답 대기
    while True:
        msg = json.loads(await ws.recv())
        if msg.get('event'):  # 이벤트는 무시
            continue
        if msg.get('id') == list_frame['id']:
            return msg.get('payload', {})

응답 예시:

{
  "sessions": [
    {
      "key": "agent:main:main",
      "totalTokens": 30072,
      "contextTokens": 200000,
      "model": "claude-opus-4-5"
    }
  ],
  "defaults": {
    "model": "claude-opus-4-5",
    "contextTokens": 200000
  }
}

예제 2: AI 호출 (토큰 소비)

async def ask_ai(message, session_id='my-session', model=None):
    """AI에게 질문 — 토큰 소비함"""
    # ... (연결 코드)
    
    # 모델 오버라이드 (선택)
    if model:
        patch_frame = {
            'type': 'req',
            'id': str(uuid.uuid4()),
            'method': 'sessions.patch',
            'params': {'key': session_id, 'model': model}
        }
        await ws.send(json.dumps(patch_frame))
        # 응답 대기...
    
    # agent 요청
    agent_frame = {
        'type': 'req',
        'id': str(uuid.uuid4()),
        'method': 'agent',
        'params': {
            'message': message,
            'sessionId': session_id,
            'sessionKey': session_id,
            'timeout': 60,
        }
    }
    await ws.send(json.dumps(agent_frame))
    
    # 응답 대기 (accepted → final)
    while True:
        msg = json.loads(await ws.recv())
        if msg.get('event'):
            continue
        if msg.get('id') == agent_frame['id']:
            if msg.get('payload', {}).get('status') == 'accepted':
                continue  # 아직 처리 중
            # 최종 응답
            payloads = msg.get('payload', {}).get('result', {}).get('payloads', [])
            return '\n'.join(p.get('text', '') for p in payloads)

예제 3: 모델 오버라이드

비싼 Opus 대신 저렴한 Sonnet 사용:

UPSELL_MODEL = 'anthropic/claude-sonnet-4-5'

response = await ask_ai(
    message="추천 멘트 만들어줘",
    session_id='upsell-customer1',
    model=UPSELL_MODEL  # Sonnet으로 오버라이드
)

Flask API 엔드포인트 예제

# app.py

@app.route('/api/claude-status')
def api_claude_status():
    """토큰 차감 없이 상태 조회"""
    from services.clawdbot_client import get_claude_status
    
    status = get_claude_status()
    
    if not status.get('connected'):
        return jsonify({'ok': False, 'error': status.get('error')}), 503
    
    sessions = status.get('sessions', {})
    # ... 데이터 가공
    
    return jsonify({
        'ok': True,
        'context': {'used': 30000, 'max': 200000, 'percent': 15},
        'model': 'claude-opus-4-5'
    })

토큰 관리 전략

모델별 용도 분리

용도 모델 이유
메인 컨트롤러 Claude Opus 복잡한 추론, 도구 사용
단순 생성 (업셀링 등) Claude Sonnet 빠르고 저렴
코딩 작업 GPT-5 Codex 정식 지원, 안정적

세션 분리

# 용도별 세션 ID 분리
ask_ai("...", session_id='upsell-고객명')      # 업셀링 전용
ask_ai("...", session_id='analysis-daily')    # 분석 전용
ask_ai("...", session_id='chat-main')         # 일반 대화

트러블슈팅

1. "invalid connect params" 에러

at /client/id: must be equal to constant
at /client/mode: must be equal to constant

해결: client.id'gateway-client', client.mode'backend' 사용

2. Gateway 연결 실패

ConnectionRefusedError: [WinError 10061]

해결: Clawdbot Gateway가 실행 중인지 확인

clawdbot gateway status

3. CLI 명령어가 hang됨

Clawdbot 내부(agent 세션)에서 clawdbot status 같은 CLI 호출하면 충돌. → WebSocket API 직접 사용할 것


파일 위치

pharmacy-pos-qr-system/
└── backend/
    └── services/
        └── clawdbot_client.py   # Gateway 클라이언트 구현
    └── app.py                   # Flask API (/api/claude-status)

참고 자료

  • Clawdbot 문서: C:\Users\청춘약국\AppData\Roaming\npm\node_modules\clawdbot\docs\
  • Gateway 프로토콜: docs/gateway/protocol.md
  • 설정 예제: docs/gateway/configuration-examples.md

작성: 2026-02-27 | 용림 🐉