# [Specification] 메신저 연동 & sLLM 통합 명세 --- ## 1. 아키텍처 개요 ``` [메신저 (슬랙/잔디/자체앱)] │ Webhook (HTTPS POST) ▼ [FastAPI Webhook Server :80] │ Nginx → proxy_pass → :3000 (Node.js/NestJS) 또는 :8000 (FastAPI) ▼ [sLLM Parser :11434 (Ollama) or :8000 (vLLM)] │ JSON 정형 데이터 ▼ [CMDB 조회 → 권한 검증 → 승인 라우팅] │ ▼ [SSH/SFTP 배포 엔진] ``` --- ## 2. sLLM 서버 구성 ### 2.1. 모델 선택 ``` 권장 1순위: Solar-10.7B-Instruct (한국어 특화) 권장 2순위: Llama-3-8B-Instruct (경량) 실행 엔진: Ollama (빠른 구축) 또는 vLLM (고성능) 양자화: 4-bit AWQ 또는 GGUF (GPU VRAM 절감) ``` ### 2.2. Ollama 기반 로컬 실행 ```bash # 설치 및 모델 다운로드 ollama pull llama3:8b-instruct-q4_K_M # API 호출 (OpenAI 호환) curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model": "llama3:8b-instruct-q4_K_M", "messages": [...]}' ``` ### 2.3. Python 클라이언트 모듈 (`src/llm/parser.py`) ```python import openai client = openai.OpenAI( base_url="http://localhost:11434/v1", # 온프레미스 api_key="local" # 더미 키 ) SYSTEM_PROMPT = """ 당신은 인프라 자동화 시스템의 핵심 파서입니다. 1. 사용자의 자연어를 분석하여 JSON 출력물만 생성합니다. 2. 부연 설명, 인삿말은 절대 포함하지 않습니다. 3. CMDB에 없는 기관/서버는 "UNKNOWN"으로 처리합니다. 4. 배포 명령 시 requires_approval(boolean)을 반드시 판단합니다. 출력 포맷: {"intent_type": "...", "institution": "...", "system_name": "...", "infrastructure_layer": "WAS|WEB|DB", "target_nodes": [], "action_sequence": [], "deploy_artifacts": [], "requires_approval": false} """ def parse_natural_language(user_text: str) -> dict: response = client.chat.completions.create( model="llama3:8b-instruct-q4_K_M", response_format={"type": "json_object"}, temperature=0.1, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_text} ] ) return json.loads(response.choices[0].message.content) ``` --- ## 3. FastAPI Webhook 서버 ### 3.1. 메신저 수신 엔드포인트 (`src/api/webhook.py`) ```python from fastapi import FastAPI, WebSocket, WebSocketDisconnect from typing import Dict, List import json app = FastAPI() # 연결된 세션 관리 room_channels: Dict[str, List[WebSocket]] = {} connected_agents: Dict[str, WebSocket] = {} @app.post("/messenger/webhook") async def receive_message(payload: dict): """메신저 Webhook 수신 — 메시지 파싱 & SR 생성""" user_id = payload.get("user_id") text = payload.get("text", "") files = payload.get("files", []) room_id = payload.get("room_id") # 파일 스테이징 staged_files = await stage_files(files) # sLLM 파싱 parsed = parse_natural_language(text) # CMDB 조회 → 서버 정보 매핑 server_list = db.query_servers(parsed["institution"], parsed["system_name"]) if not server_list: return {"status": "ERROR", "message": "CMDB에 해당 서버 정보 없음"} # SR 생성 sr_id = create_ops_task(parsed, user_id, staged_files) # 승인 라우팅 if parsed.get("requires_approval"): initiate_approval_process(sr_id) return {"status": "PENDING_APPROVAL", "sr_id": sr_id} # 즉시 실행 trigger_execution_engine(sr_id) return {"status": "IN_PROGRESS", "sr_id": sr_id} ``` ### 3.2. WebSocket 실시간 중계 (`src/api/ws_relay.py`) ```python @app.websocket("/ws/chat/{room_id}/{client_id}/{client_type}") async def chat_endpoint(ws: WebSocket, room_id: str, client_id: str, client_type: str): await ws.accept() if client_type == "HUMAN": room_channels.setdefault(room_id, []).append(ws) elif client_type == "AGENT": connected_agents[client_id] = ws try: while True: data = json.loads(await ws.receive_text()) if client_type == "HUMAN" and data.get("is_command"): # AI 봇 멘션 → 파싱 → 역방향 에이전트로 라우팅 target_agent = data.get("target_agent_id", "pc-01") if target_agent in connected_agents: await connected_agents[target_agent].send_text( json.dumps({"task_id": str(uuid4()), "action": data["command_code"]}) ) elif client_type == "AGENT" and data.get("event") == "TASK_FINISHED": # 에이전트 결과 → 방 전체에 브로드캐스트 for ws in room_channels.get(room_id, []): await ws.send_text(json.dumps({"sender": "BOT", "result": data["payload"]})) except WebSocketDisconnect: if client_type == "HUMAN": room_channels.get(room_id, []).remove(ws) else: connected_agents.pop(client_id, None) ``` --- ## 4. Nginx 웹소켓 프록시 설정 ```nginx upstream was_backend { server 127.0.0.1:8000; keepalive 32; } server { listen 80; server_name localhost; location /ws { proxy_pass http://was_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400s; proxy_send_timeout 86400s; proxy_buffering off; client_max_body_size 100M; # 배포 파일 업로드 } location / { proxy_pass http://was_backend; proxy_http_version 1.1; proxy_set_header Host $host; } } ``` --- ## 5. Proactive AI 챗봇 (대화 맥락 분석) ```python CONTEXT_RULES = { "db_delay": { "keywords": ["DB", "디비", "쿼리", "느리", "타임아웃", "락"], "action": "FETCH_DB_LOCKS", "message": "DB 지연 징후가 감지되었습니다. 대기 쿼리를 조회할까요?" }, "disk_full": { "keywords": ["용량", "디스크", "로그", "쌓였"], "action": "CHECK_DISK_SPACE", "message": "디스크 공간 관련 문제가 감지되었습니다. 용량을 확인할까요?" } } async def analyze_context(room_id: str, sender: str, text: str): """대화 맥락 분석 → 선제적 조언 Push""" if any(kw in text for kw in ["@bot", "AI", "조회", "확인"]): return # 이미 명시적 명령 for rule_name, rule in CONTEXT_RULES.items(): if any(kw in text for kw in rule["keywords"]): await asyncio.sleep(1) await broadcast_to_room(room_id, { "sender": "GUARDiA-Bot", "sender_type": "BOT", "message_content": f"🤖 {rule['message']}", "interactive_action": { "type": "BUTTON", "label": "즉시 확인", "command_code": rule["action"] } }) break # 첫 매칭만 ``` --- ## 6. 메시지 프로토콜 스키마 (ITSM 확장 호환) ```json { "message_id": "MSG-20260523-0001", "timestamp": "2026-05-23T19:00:00Z", "sender": "ENGINEER_01", "sender_type": "HUMAN", "msg_type": "CHAT", "content": "기재부 예산시스템 WAS 재기동해줘", "is_widget": false, "itsm_metadata": { "itsm_ticket_id": null, "asset_code": "MOF-WAS-01", "severity": "MEDIUM" } } ```