"""율봇 API 클라이언트 — youlbot REST API(Phase 22)를 httpx로 호출.""" import json import os from typing import AsyncIterator import httpx from dotenv import load_dotenv load_dotenv() _API_URL = os.getenv("YOULBOT_API_URL", "http://localhost:8000").rstrip("/") _API_TOKEN = os.getenv("YOULBOT_API_TOKEN", "") def _headers() -> dict: if _API_TOKEN: return {"Authorization": f"Bearer {_API_TOKEN}"} return {} async def chat( message: str, user_id: str = "default", show_thinking: bool = False, ) -> AsyncIterator[tuple[str, str | None]]: """SSE 스트림을 읽어 (token, run_id) 튜플을 yield. - 일반 토큰: (token_str, None) - 스트림 종료: ("", run_id_or_None) ← __done 이벤트 """ async with httpx.AsyncClient(timeout=180) as client: async with client.stream( "POST", f"{_API_URL}/chat", json={"message": message, "user_id": user_id, "show_thinking": show_thinking}, headers=_headers(), ) as response: response.raise_for_status() async for line in response.aiter_lines(): if not line.startswith("data: "): continue raw = line[6:] try: payload = json.loads(raw) except json.JSONDecodeError: yield raw, None continue if isinstance(payload, dict) and payload.get("__done"): yield "", payload.get("run_id") return yield payload, None async def reset(user_id: str = "default") -> None: async with httpx.AsyncClient(timeout=30) as client: r = await client.post( f"{_API_URL}/reset", params={"user_id": user_id}, headers=_headers(), ) r.raise_for_status() async def ingest(file_path: str) -> dict: async with httpx.AsyncClient(timeout=300) as client: with open(file_path, "rb") as f: filename = os.path.basename(file_path) r = await client.post( f"{_API_URL}/ingest", files={"file": (filename, f, "application/octet-stream")}, headers=_headers(), ) r.raise_for_status() return r.json() async def list_documents() -> list[str]: async with httpx.AsyncClient(timeout=30) as client: r = await client.get(f"{_API_URL}/documents", headers=_headers()) r.raise_for_status() return r.json().get("documents", []) async def delete_document(source: str) -> None: async with httpx.AsyncClient(timeout=30) as client: r = await client.delete( f"{_API_URL}/documents/{source}", headers=_headers(), ) r.raise_for_status() async def save_feedback( user_id: str, user_msg: str, asst_msg: str, rating: int, run_id: str | None = None, ) -> None: async with httpx.AsyncClient(timeout=30) as client: r = await client.post( f"{_API_URL}/feedback", json={ "user_id": user_id, "user_msg": user_msg, "asst_msg": asst_msg, "rating": rating, "run_id": run_id, }, headers=_headers(), ) r.raise_for_status()