commit e575ec8c806bbeb7cc7452e28bbd47816375a121 Author: kyy Date: Fri Jul 18 17:32:56 2025 +0900 Initial commit diff --git a/.env.chatbot b/.env.chatbot new file mode 100644 index 0000000..c2f0814 --- /dev/null +++ b/.env.chatbot @@ -0,0 +1,2 @@ +# Replace with your actual Google API key +GOOGLE_API_KEY="AIzaSyD37Fp00b_i2DIywwtQu39w0RhkGAJO4YM" \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b60060f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,32 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ece5652 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# Use an official Python runtime as a parent image +FROM python:3.9-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy the requirements file into the container at /app +COPY requirements.txt . + +# Install any needed packages specified in requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application's code into the container at /app +COPY . . + +# Make port 8501 available to the world outside this container +EXPOSE 8501 + +# Define environment variable +ENV GOOGLE_API_KEY="" + +# Run main.py when the container launches +CMD ["streamlit", "run", "main.py"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4af313f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Lectom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b58bd3 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# ๐Ÿค– OpenAPI ๋ช…์„ธ ์กฐํšŒ ์ฑ—๋ด‡ (LLM-Gateway Spec Chatbot) + +์ด ํ”„๋กœ์ ํŠธ๋Š” ํŠน์ • URL์—์„œ ์ œ๊ณตํ•˜๋Š” OpenAPI ๋ช…์„ธ(Specification) ํŒŒ์ผ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•˜๋Š” Streamlit ๊ธฐ๋ฐ˜์˜ ์ฑ—๋ด‡ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. Gemini AI ๋ชจ๋ธ์„ ํ™œ์šฉํ•˜์—ฌ ์ž์—ฐ์–ด ์งˆ๋ฌธ์„ ์ดํ•ดํ•˜๊ณ , API ๋ช…์„ธ์— ๊ทผ๊ฑฐํ•œ ์ •ํ™•ํ•œ ์ •๋ณด๋ฅผ ํ•œ๊ตญ์–ด๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Docker ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด ์žˆ์–ด, ๊ฐ„ํŽธํ•˜๊ฒŒ ์„ค์น˜ํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## โœจ ์ฃผ์š” ๊ธฐ๋Šฅ + +- **OpenAPI ๋ช…์„ธ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€**: ๋กœ์ปฌ์— ์ €์žฅ๋œ `api_spec.json` ํŒŒ์ผ์„ ๋ถ„์„ํ•˜์—ฌ API์˜ ์—”๋“œํฌ์ธํŠธ, ํŒŒ๋ผ๋ฏธํ„ฐ, ์š”์•ฝ ์ •๋ณด ๋“ฑ์— ๋Œ€ํ•ด ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค. +- **์ž์—ฐ์–ด ์งˆ์˜์‘๋‹ต**: Google์˜ Gemini ๋ชจ๋ธ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž์˜ ์ž์—ฐ์–ด ์งˆ๋ฌธ์„ ์ดํ•ดํ•˜๊ณ  ์ง€๋Šฅ์ ์ธ ๋‹ต๋ณ€์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +- **๋™์  ๋ช…์„ธ ์ƒˆ๋กœ๊ณ ์นจ**: UI์˜ ๋ฒ„ํŠผ ํด๋ฆญ ํ•œ ๋ฒˆ์œผ๋กœ ์ตœ์‹  API ๋ช…์„ธ๋ฅผ ์›๊ฒฉ URL์—์„œ ๋‹ค์‹œ ๊ฐ€์ ธ์™€ ์•ฑ์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- **ํ•œ๊ตญ์–ด ์ง€์›**: ๋ชจ๋“  ๋‹ต๋ณ€์€ ํ•œ๊ตญ์–ด๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. +- **์ง๊ด€์ ์ธ UI**: Streamlit์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ์›น ๊ธฐ๋ฐ˜ ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ๏ฟฝ๏ฟฝ๏ฟฝ๊ณ  ๋ฐ ์ปค์Šคํ…€ ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +- **์ปจํ…Œ์ด๋„ˆํ™”**: Docker ๋ฐ Docker Compose๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๊ณ  ์ผ๊ด€๋œ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ + +- **์–ธ์–ด**: Python 3.9 +- **ํ”„๋ ˆ์ž„์›Œํฌ**: Streamlit +- **AI ๋ชจ๋ธ**: Google Gemini 1.5 Flash +- **์ปจํ…Œ์ด๋„ˆ**: Docker, Docker Compose +- **์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ**: `google-generativeai`, `requests`, `pandas` + +## ๐Ÿ“‚ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ + +``` +chatbot_app/ +โ”œโ”€โ”€ static/ +โ”‚ โ”œโ”€โ”€ api_spec.json # API ๋ช…์„ธ ํŒŒ์ผ +โ”‚ โ””โ”€โ”€ logo.png # UI์— ํ‘œ์‹œ๋  ๋กœ๊ณ  ์ด๋ฏธ์ง€ +โ”œโ”€โ”€ .env.chatbot # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํŒŒ์ผ (API ํ‚ค ์ €์žฅ) +โ”œโ”€โ”€ docker-compose.yml # Docker Compose ์„ค์ • ํŒŒ์ผ +โ”œโ”€โ”€ Dockerfile # Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ ํŒŒ์ผ +โ”œโ”€โ”€ main.py # Streamlit ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฉ”์ธ ์ฝ”๋“œ +โ”œโ”€โ”€ README.md # ํ”„๋กœ์ ํŠธ ์„ค๋ช… ํŒŒ์ผ (ํ˜„์žฌ ํŒŒ์ผ) +โ””โ”€โ”€ requirements.txt # Python ์˜์กด์„ฑ ๋ชฉ๋ก +``` + +## ๐Ÿš€ ์„ค์น˜ ๋ฐ ์‹คํ–‰ ๋ฐฉ๋ฒ• + +### ์‚ฌ์ „ ์ค€๋น„ ์‚ฌํ•ญ + +- [Docker](https://www.docker.com/get-started)๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +- [Docker Compose](https://docs.docker.com/compose/install/)๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์ตœ์‹  ๋ฒ„์ „์˜ Docker Desktop์—๋Š” ๊ธฐ๋ณธ ํฌํ•จ) + +### ์„ค์น˜ ์ ˆ์ฐจ + +1. **ํ”„๋กœ์ ํŠธ ์ค€๋น„**: + ์ด `chatbot_app` ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. + +2. **์ •์  ํŒŒ์ผ ๋ฐฐ์น˜**: + `chatbot_app/static/` ๋””๋ ‰ํ† ๋ฆฌ ์•ˆ์— ๋‹ค์Œ ๋‘ ํŒŒ์ผ์„ ์œ„์น˜์‹œํ‚ต๋‹ˆ๋‹ค. + - `api_spec.json`: ์กฐํšŒํ•  ๋Œ€์ƒ์˜ OpenAPI ๋ช…์„ธ ํŒŒ์ผ + - `logo.png`: UI ์‚ฌ์ด๋“œ๋ฐ”์— ํ‘œ์‹œํ•  ๋กœ๊ณ  ์ด๋ฏธ์ง€ + +3. **ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •**: + `chatbot_app/.env.chatbot` ํŒŒ์ผ์„ ์—ด๊ณ , `YOUR_GEMINI_API_KEY` ๋ถ€๋ถ„์„ ์‹ค์ œ ๋ฐœ๊ธ‰๋ฐ›์€ Google Gemini API ํ‚ค๋กœ ๊ต์ฒดํ•ฉ๋‹ˆ๋‹ค. + ```env + # YOUR_GEMINI_API_KEY๋ฅผ ์‹ค์ œ ํ‚ค๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”. + GOOGLE_API_KEY="YOUR_GEMINI_API_KEY" + ``` + +4. **์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰**: + ํ„ฐ๋ฏธ๋„์—์„œ `chatbot_app` ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ํ•œ ํ›„, ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. + ```bash + docker-compose up --build -d + ``` + - `--build`: ์ด๋ฏธ์ง€๋ฅผ ์ƒˆ๋กœ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค. (์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ํ•„์š”) + - `-d`: ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. + +5. **์ฑ—๋ด‡ ์ ‘์†**: + ๋นŒ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๊ณ  ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹คํ–‰๋˜๋ฉด, ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์—ด๊ณ  ๋‹ค์Œ ์ฃผ์†Œ๋กœ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค. + - **URL**: `http://localhost:8501` + +## ๐Ÿ’ก ์‚ฌ์šฉ ๋ฐฉ๋ฒ• + +- **์งˆ๋ฌธํ•˜๊ธฐ**: ํ™”๋ฉด ํ•˜๋‹จ์˜ ์ž…๋ ฅ์ฐฝ์— API ๋ช…์„ธ์™€ ๊ด€๋ จ๋œ ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•˜๊ณ  Enter ํ‚ค๋ฅผ ๋ˆ„๋ฆ…๋‹ˆ๋‹ค. +- **API ๋ช…์„ธ ์ƒˆ๋กœ๊ณ ์นจ**: ์‚ฌ์ด๋“œ๋ฐ”์˜ '๐Ÿ”„ API ๋ช…์„ธ ์ƒˆ๋กœ๊ณ ์นจ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด `API_SPEC_URL`์— ์ง€์ •๋œ ์ฃผ์†Œ์—์„œ ์ตœ์‹  ๋ช…์„ธ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. +- **API ์ƒ์„ธ์ •๋ณด ํ™•์ธ**: ์‚ฌ์ด๋“œ๋ฐ”์˜ '๐Ÿ“˜ API ์ƒ์„ธ์ •๋ณด ๋ณด๊ธฐ'๋ฅผ ํŽผ์น˜๋ฉด API์˜ ์ œ๋ชฉ, ๋ฒ„์ „ ๋ฐ ์ „์ฒด ์—”๋“œํฌ์ธํŠธ ๋ชฉ๋ก์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f6618d6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + chatbot: + build: . + container_name: pgn_spec_chatbot + ports: + - "8501:8501" + env_file: + - .env.chatbot + volumes: + - .:/app diff --git a/main.py b/main.py new file mode 100644 index 0000000..a0cabbb --- /dev/null +++ b/main.py @@ -0,0 +1,197 @@ +import json +import os + +import google.generativeai as genai +import pandas as pd +import requests +import streamlit as st + +# --- ์„ค์ • --- +SPEC_FILE = "static/api_spec.json" +LOGO_FILE = "static/logo.png" +API_SPEC_URL = "http://172.16.10.176:8888/openapi.json" + + +# --- ๋„์šฐ๋ฏธ ํ•จ์ˆ˜ --- +def load_api_spec(): + """๋กœ์ปฌ ํŒŒ์ผ์—์„œ API ๋ช…์„ธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.""" + try: + with open(SPEC_FILE, "r", encoding="utf-8") as f: + return json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as e: + st.error(f"API ๋ช…์„ธ ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}") + return None + + +def fetch_and_save_spec(): + """URL์—์„œ ์ตœ์‹  API ๋ช…์„ธ๋ฅผ ๊ฐ€์ ธ์™€ ๋กœ์ปฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.""" + try: + response = requests.get(API_SPEC_URL) + response.raise_for_status() # ์ž˜๋ชป๋œ ์ƒํƒœ ์ฝ”๋“œ์— ๋Œ€ํ•ด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค + spec_data = response.json() + with open(SPEC_FILE, "w", encoding="utf-8") as f: + json.dump(spec_data, f, indent=2, ensure_ascii=False) + st.success(f"์„ฑ๊ณต์ ์œผ๋กœ API ๋ช…์„ธ๋ฅผ ๊ฐ€์ ธ์™€ ์—…๋ฐ์ดํŠธํ–ˆ์Šต๋‹ˆ๋‹ค: {API_SPEC_URL}") + return spec_data + except requests.exceptions.RequestException as e: + st.error(f"API ๋ช…์„ธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {e}") + return None + except json.JSONDecodeError: + st.error("์‘๋‹ต์—์„œ JSON์„ ํŒŒ์‹ฑํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.") + return None + + +# --- Gemini AI ์„ค์ • --- +try: + api_key = os.getenv("GOOGLE_API_KEY") + if not api_key: + st.error( + "GOOGLE_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. .env.chatbot ํŒŒ์ผ์—์„œ ์„ค์ •ํ•ด์ฃผ์„ธ์š”." + ) + st.stop() + genai.configure(api_key=api_key) + model = genai.GenerativeModel("gemini-1.5-flash") +except Exception as e: + st.error(f"Gemini AI ์„ค์ • ์‹คํŒจ: {e}") + st.stop() + +# --- Streamlit UI ์„ค์ • --- +st.set_page_config( + page_title="API Guide Chatbot", + page_icon="๐Ÿค–", + layout="wide", + initial_sidebar_state="expanded", +) + +# --- ์ปค์Šคํ…€ CSS --- +st.markdown( + """ + +""", + unsafe_allow_html=True, +) + + +# --- ๋ฉ”์ธ ์•ฑ ๋กœ์ง --- +# ์‹œ์ž‘ ์‹œ API ๋ช…์„ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ +api_spec = load_api_spec() + +# --- ์‚ฌ์ด๋“œ๋ฐ” --- +with st.sidebar: + if os.path.exists(LOGO_FILE): + st.image(LOGO_FILE, use_container_width=True) + st.header("โš™๏ธ Controls") + if st.button("๐Ÿ”„ API ๋ช…์„ธ ์ƒˆ๋กœ๊ณ ์นจ"): + with st.spinner("์ตœ์‹  API ๋ช…์„ธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘..."): + api_spec = fetch_and_save_spec() + st.session_state.messages = [] # ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ๋Œ€ํ™” ๊ธฐ๋ก ์‚ญ์ œ + st.rerun() + + if api_spec: + with st.expander("๐Ÿ“˜ API ์ƒ์„ธ์ •๋ณด ๋ณด๊ธฐ", expanded=True): + st.info(f"**Title:** {api_spec.get('info', {}).get('title', 'N/A')}") + st.text(f"Version: {api_spec.get('info', {}).get('version', 'N/A')}") + + paths = api_spec.get("paths", {}) + if paths: + endpoint_data = [] + for path, methods in paths.items(): + for method, details in methods.items(): + endpoint_data.append( + { + "Method": method.upper(), + "Endpoint": path, + "summary": details.get("summary", "์š”์•ฝ ์—†์Œ"), + } + ) + df = pd.DataFrame(endpoint_data) + st.dataframe(df, use_container_width=True) + else: + st.warning("API ๋ช…์„ธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.") + +# --- ๋ฉ”์ธ ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค --- +st.markdown( + "

๐Ÿ’ฌ LLM-Gateway Guide Chatbot

", + unsafe_allow_html=True, +) +st.caption(f"ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ API ๋ช…์„ธ: {API_SPEC_URL}") + + +# ๋Œ€ํ™” ๊ธฐ๋ก ์ดˆ๊ธฐํ™” +if "messages" not in st.session_state: + st.session_state.messages = [] + +# ์‹œ์ž‘ ๋ฉ”์‹œ์ง€ +if not st.session_state.messages: + st.info("์•ˆ๋…•ํ•˜์„ธ์š”! LLM-Gateway ์— ๋Œ€ํ•ด ๊ถ๊ธˆํ•œ ์ ์„ ๋ฌผ์–ด๋ณด์„ธ์š”.") + +# ์•ฑ ์žฌ์‹คํ–‰ ์‹œ ๊ธฐ๋ก์—์„œ ๋Œ€ํ™” ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ +for message in st.session_state.messages: + avatar = "๐Ÿง‘โ€๐Ÿ’ป" if message["role"] == "user" else "๐Ÿ‘ป" + with st.chat_message(message["role"], avatar=avatar): + st.markdown(message["content"]) + +# ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ˆ˜๋ฝ +if prompt := st.chat_input("LLM-Gateway ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•˜์„ธ์š”..."): + if not api_spec: + st.error("์งˆ๋ฌธ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: API ๋ช…์„ธ๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.") + else: + # ๋Œ€ํ™” ๊ธฐ๋ก์— ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€ + st.session_state.messages.append({"role": "user", "content": prompt}) + # ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ปจํ…Œ์ด๋„ˆ์— ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ + with st.chat_message("user", avatar="๐Ÿง‘โ€๐Ÿ’ป"): + st.markdown(prompt) + + # ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ปจํ…Œ์ด๋„ˆ์— ์–ด์‹œ์Šคํ„ดํŠธ ์‘๋‹ต ํ‘œ์‹œ + with st.chat_message("assistant", avatar="๐Ÿ‘ป"): + message_placeholder = st.empty() + with st.spinner("๋‹ต๋ณ€์„ ์ƒ์„ฑํ•˜๋Š” ์ค‘..."): + try: + # Gemini๋ฅผ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ ์ค€๋น„ + full_prompt = f""" + ๋‹น์‹ ์€ ๋‹ค์Œ LLM-Gateway API ๋ช…์„ธ์— ๋Œ€ํ•œ ์ „๋ฌธ๊ฐ€ ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค. + ๋‹น์‹ ์˜ ์ž„๋ฌด๋Š” ์ œ๊ณต๋œ JSON ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ๋งŒ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + ์ •๋ณด๋ฅผ ์ง€์–ด๋‚ด์ง€ ๋งˆ์„ธ์š”. ๋งŒ์•ฝ ๋ช…์„ธ์— ๋‹ต๋ณ€์ด ์—†๋‹ค๋ฉด ์—†๋‹ค๊ณ  ๋งํ•˜์„ธ์š”. + ๋ชจ๋“  ๋‹ต๋ณ€์€ ๋ฐ˜๋“œ์‹œ ํ•œ๊ตญ์–ด๋กœ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”. + + **LLM-Gateway API ๋ช…์„ธ (JSON):** + ```json + {json.dumps(api_spec, indent=2, ensure_ascii=False)} + ``` + + **์‚ฌ์šฉ์ž ์งˆ๋ฌธ:** + {prompt} + """ + response = model.generate_content(full_prompt) + + if response.parts: + response_text = response.text + else: + # ์‘๋‹ต์ด ์ฐจ๋‹จ๋  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค + response_text = ( + "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค, ํ•ด๋‹น ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ๋“œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." + ) + + message_placeholder.markdown(response_text) + st.session_state.messages.append( + {"role": "assistant", "content": response_text} + ) + + except Exception as e: + error_text = f"์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}" + message_placeholder.markdown(error_text) + st.session_state.messages.append( + {"role": "assistant", "content": error_text} + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6cc45da --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +line-length = 120 +indent-width = 4 + +[lint] +# ๊ธฐ๋ณธ์ ์œผ๋กœ Pyflakes('F')์™€ pycodestyle('E') ์ฝ”๋“œ์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# ํ™œ์„ฑํ™”๋œ ๋ชจ๋“  ๊ทœ์น™์— ๋Œ€ํ•œ ์ˆ˜์ • ํ—ˆ์šฉ. +fixable = ["ALL"] +unfixable = [] + +# ๋ฐ‘์ค„ ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ณ€์ˆ˜๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" +docstring-code-format = false +docstring-code-line-length = "dynamic" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1420878 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +streamlit +google-generativeai +requests +pandas +pyarrow diff --git a/static/api_spec.json b/static/api_spec.json new file mode 100644 index 0000000..baf2980 --- /dev/null +++ b/static/api_spec.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","info":{"title":"LLM GATEWAY","description":"LLM ๋ชจ๋ธ์ด ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ตฌ์กฐํ™”๋œ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” API ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.","version":"0.1.0"},"paths":{"/metrics":{"get":{"summary":"Metrics","description":"Endpoint that serves Prometheus metrics.","operationId":"metrics_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/schema_file_guide":{"get":{"tags":["Guide Book"],"summary":"schema ํŒŒ์ผ ์ž‘์„ฑ ๊ฐ€์ด๋“œ๋ถ HTML ๋ณด๊ธฐ","description":"๐Ÿ“„ ๋ณธ ๊ฐ€์ด๋“œ๋ถ์€ /general ๋ฐ /extract/structured ์—”๋“œํฌ์ธํŠธ์— ์ฒจ๋ถ€๋˜๋Š” schema_file ์ž‘์„ฑ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€์ด๋“œ๋ถ์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.","operationId":"schema_guide_schema_file_guide_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/general_guide":{"get":{"tags":["Guide Book"],"summary":"/general ๊ฐ€์ด๋“œ๋ถ HTML ๋ณด๊ธฐ","description":"๊ฐ€์ด๋“œ๋ถ์„ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.","operationId":"general_guide_general_guide_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/extract_guide":{"get":{"tags":["Guide Book"],"summary":"/extract ๊ฐ€์ด๋“œ๋ถ HTML ๋ณด๊ธฐ","description":"๊ฐ€์ด๋“œ๋ถ์„ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.","operationId":"extract_guide_extract_guide_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/info":{"get":{"tags":["Model Management"],"summary":"'/extract', '/general' ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ ๋ชฉ๋ก ํ™•์ธ","description":"โœ… 'inner(๋‚ด๋ถ€์šฉ)' ์™€ 'outer(์™ธ๋ถ€์šฉ)' ๋ชจ๋ธ์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชฉ๋ก์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
\n โœ… 'Try it out' โ†’ 'Execute' ์ˆœ์„œ๋กœ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
","operationId":"get_model_info_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/default_prompt":{"get":{"tags":["Model Management"],"summary":"๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ","operationId":"download_default_prompt_default_prompt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/structured_prompt":{"get":{"tags":["Model Management"],"summary":"๊ตฌ์กฐํ™” ํ”„๋กฌํ”„ํŠธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ","operationId":"download_structured_prompt_structured_prompt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/structured_schema":{"get":{"tags":["Model Management"],"summary":"๊ตฌ์กฐํ™” ํฌ๋งท ์ •์˜ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ","operationId":"download_structured_schema_structured_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/general/inner":{"post":{"tags":["General"],"summary":"๋‚ด๋ถ€ LLM ๊ธฐ๋ฐ˜ ๋ฒ”์šฉ ์ถ”๋ก  ์š”์ฒญ (๋น„๋™๊ธฐ)","description":"### **์š”์•ฝ**\n๋‚ด๋ถ€๋ง์— ๋ฐฐํฌ๋œ LLM(Ollama ๊ธฐ๋ฐ˜)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œ ๊ธฐ๋ฐ˜์˜ ๋ฒ”์šฉ ์ถ”๋ก ์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ์ด ์—”๋“œํฌ์ธํŠธ๋Š” ํŒŒ์ผ(PDF, ์ด๋ฏธ์ง€ ๋“ฑ)์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ ์šฉํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n1. **์š”์ฒญ ์ ‘์ˆ˜**: `input_file`, `prompt_file` ๋“ฑ์„ ๋ฐ›์•„ ๊ณ ์œ ํ•œ `request_id`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n2. **๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ**:\n - `input_file`์ด ๋ฌธ์„œ๋‚˜ ์ด๋ฏธ์ง€์ผ ๊ฒฝ์šฐ, **OCR API**๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.\n - ์ถ”์ถœ๋œ ํ…์ŠคํŠธ์™€ `prompt_file`์˜ ๋‚ด์šฉ์„ ์กฐํ•ฉํ•˜์—ฌ ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n - ๋‚ด๋ถ€ LLM(Ollama)์— ์ถ”๋ก ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n - `schema_file`์ด ์ œ๊ณต๋˜๋ฉด, LLM์ด ์Šคํ‚ค๋งˆ์— ๋งž๋Š” JSON์„ ์ƒ์„ฑํ•˜๋„๋ก ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n3. **์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ**: ๋ฐ˜ํ™˜๋œ `request_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `GET /general/progress/{request_id}` ์—”๋“œํฌ์ธํŠธ์—์„œ ์ž‘์—… ์ง„ํ–‰ ์ƒํƒœ์™€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ (multipart/form-data)**\n- `input_file` (**ํ•„์ˆ˜**): ์ถ”๋ก ์˜ ๊ธฐ๋ฐ˜์ด ๋  ๋ฌธ์„œ ํŒŒ์ผ.\n - ์ง€์› ํ˜•์‹: `.pdf`, `.docx`, `.jpg`, `.png`, `.jpeg` ๋“ฑ.\n - ๋‚ด๋ถ€์ ์œผ๋กœ OCR์„ ํ†ตํ•ด ํ…์ŠคํŠธ๊ฐ€ ์ž๋™ ์ถ”์ถœ๋ฉ๋‹ˆ๋‹ค.\n- `prompt_file` (**ํ•„์ˆ˜**): LLM์— ์ „๋‹ฌํ•  ๋ช…๋ น์–ด(ํ”„๋กฌํ”„ํŠธ)๊ฐ€ ํฌํ•จ๋œ `.txt` ํŒŒ์ผ.\n- `schema_file` (์„ ํƒ): ๊ฒฐ๊ณผ๋ฌผ์˜ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•˜๋Š” `.json` ์Šคํ‚ค๋งˆ ํŒŒ์ผ. ์ œ๊ณต ์‹œ, ์ถœ๋ ฅ์€ ์ด ์Šคํ‚ค๋งˆ๋ฅผ ๋”ฐ๋ฅด๋Š” JSON ํ˜•์‹์œผ๋กœ ๊ฐ•์ œ๋ฉ๋‹ˆ๋‹ค.\n- `model` (์„ ํƒ): ์‚ฌ์šฉํ•  ๋‚ด๋ถ€ LLM ๋ชจ๋ธ ์ด๋ฆ„. (๊ธฐ๋ณธ๊ฐ’: `gemma3:27b`)\n\n### **์ถœ๋ ฅ (application/json)**\n- **์ดˆ๊ธฐ ์‘๋‹ต**:\n ```json\n {\n \"message\": \"์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.\",\n \"request_id\": \"๊ณ ์œ ํ•œ ์š”์ฒญ ID\",\n \"status_check_url\": \"/general/progress/๊ณ ์œ ํ•œ ์š”์ฒญ ID\"\n }\n ```\n- **์ตœ์ข… ๊ฒฐ๊ณผ**: `GET /general/progress/{request_id}`๋ฅผ ํ†ตํ•ด ํ™•์ธ ๊ฐ€๋Šฅ.","operationId":"general_endpoint_general_inner_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_general_endpoint_general_inner_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/general/outer":{"post":{"tags":["General"],"summary":"์™ธ๋ถ€ LLM ๊ธฐ๋ฐ˜ ๋ฒ”์šฉ ์ถ”๋ก  ์š”์ฒญ (๋น„๋™๊ธฐ)","description":"### **์š”์•ฝ**\n์™ธ๋ถ€ ์ƒ์šฉ LLM(์˜ˆ: GPT, Gemini, Claude)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œ ๊ธฐ๋ฐ˜์˜ ๋ฒ”์šฉ ์ถ”๋ก ์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ๊ณผ ์ž‘๋™ ๋ฐฉ์‹์€ ๋‚ด๋ถ€ LLM์šฉ ์—”๋“œํฌ์ธํŠธ์™€ ๋™์ผํ•˜๋‚˜, ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n1. **์š”์ฒญ ์ ‘์ˆ˜**: `input_file`, `prompt_file` ๋“ฑ์„ ๋ฐ›์•„ ๊ณ ์œ ํ•œ `request_id`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n2. **๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ**:\n - `input_file`์—์„œ **OCR API**๋ฅผ ํ†ตํ•ด ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.\n - ์ถ”์ถœ๋œ ํ…์ŠคํŠธ์™€ `prompt_file`์˜ ๋‚ด์šฉ์„ ์กฐํ•ฉํ•˜์—ฌ ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n - ์™ธ๋ถ€ LLM API(OpenAI, Google, Anthropic ๋“ฑ)์— ์ถ”๋ก ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n - `schema_file`์ด ์ œ๊ณต๋˜๋ฉด, LLM์ด ์Šคํ‚ค๋งˆ์— ๋งž๋Š” JSON์„ ์ƒ์„ฑํ•˜๋„๋ก ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n3. **์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ**: ๋ฐ˜ํ™˜๋œ `request_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `GET /general/progress/{request_id}` ์—”๋“œํฌ์ธํŠธ์—์„œ ์ž‘์—… ์ง„ํ–‰ ์ƒํƒœ์™€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ (multipart/form-data)**\n- `input_file` (**ํ•„์ˆ˜**): ์ถ”๋ก ์˜ ๊ธฐ๋ฐ˜์ด ๋  ๋ฌธ์„œ ํŒŒ์ผ.\n - ์ง€์› ํ˜•์‹: `.pdf`, `.docx`, `.jpg`, `.png`, `.jpeg` ๋“ฑ.\n- `prompt_file` (**ํ•„์ˆ˜**): LLM์— ์ „๋‹ฌํ•  ํ”„๋กฌํ”„ํŠธ๊ฐ€ ํฌํ•จ๋œ `.txt` ํŒŒ์ผ.\n- `schema_file` (์„ ํƒ): ๊ฒฐ๊ณผ๋ฌผ์˜ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•˜๋Š” `.json` ์Šคํ‚ค๋งˆ ํŒŒ์ผ.\n- `model` (์„ ํƒ): ์‚ฌ์šฉํ•  ์™ธ๋ถ€ LLM ๋ชจ๋ธ ์ด๋ฆ„. (๊ธฐ๋ณธ๊ฐ’: `gemini-2.5-flash`)\n\n### **์ถœ๋ ฅ (application/json)**\n- **์ดˆ๊ธฐ ์‘๋‹ต**:\n ```json\n {\n \"message\": \"์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.\",\n \"request_id\": \"๊ณ ์œ ํ•œ ์š”์ฒญ ID\",\n \"status_check_url\": \"/general/progress/๊ณ ์œ ํ•œ ์š”์ฒญ ID\"\n }\n ```\n- **์ตœ์ข… ๊ฒฐ๊ณผ**: `GET /general/progress/{request_id}`๋ฅผ ํ†ตํ•ด ํ™•์ธ ๊ฐ€๋Šฅ.","operationId":"general_endpoint_general_outer_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_general_endpoint_general_outer_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/general/progress/{request_id}":{"get":{"tags":["General"],"summary":"๋ฒ”์šฉ ์ถ”๋ก  ์ž‘์—… ์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ์กฐํšŒ","description":"### **์š”์•ฝ**\n`POST /general/inner` ๋˜๋Š” `POST /general/outer` ์š”์ฒญ ์‹œ ๋ฐ˜ํ™˜๋œ `request_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ํ•ด๋‹น ์ž‘์—…์˜ ์ง„ํ–‰ ์ƒํƒœ์™€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n- `request_id`๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Redis์— ์ €์žฅ๋œ ์ž‘์—… ๋กœ๊ทธ์™€ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n- ์ž‘์—…์ด ์ง„ํ–‰ ์ค‘์ผ ๋•Œ๋Š” ํ˜„์žฌ๊นŒ์ง€์˜ ๋กœ๊ทธ๋ฅผ, ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ๋Š” ๋กœ๊ทธ์™€ ํ•จ๊ป˜ ์ตœ์ข… ๊ฒฐ๊ณผ(`final_result`)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ**\n- `request_id`: ์กฐํšŒํ•  ์ž‘์—…์˜ ๊ณ ์œ  ID.\n\n### **์ถœ๋ ฅ (application/json)**\n- **์„ฑ๊ณต ์‹œ**:\n ```json\n {\n \"request_id\": \"์š”์ฒญ ์‹œ ์‚ฌ์šฉ๋œ ID\",\n \"progress_logs\": [\n { \"timestamp\": \"...\", \"status\": \"OCR ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"์ž…๋ ฅ ๊ธธ์ด ๊ฒ€์‚ฌ ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"LLM ์ถ”๋ก  ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"LLM ์ถ”๋ก  ์™„๋ฃŒ ๋ฐ ํ›„์ฒ˜๋ฆฌ ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"ํ›„์ฒ˜๋ฆฌ ์™„๋ฃŒ ๋ฐ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜\"\", \"details\": \"...\" }\n ],\n \"final_result\": {\n \"filename\": \"์ž…๋ ฅ ํŒŒ์ผ\",\n \"processed\": \"LLM์˜ ์ตœ์ข… ์‘๋‹ต ๋‚ด์šฉ\"\n }\n }\n ```\n- **ID๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ (404 Not Found)**:\n ```json\n {\n \"message\": \"{request_id}์— ๋Œ€ํ•œ ์ƒํƒœ ๋กœ๊ทธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.\"\n }\n ```","operationId":"get_pipeline_status_general_progress__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/extract/inner":{"post":{"tags":["Extraction"],"summary":"๋‚ด๋ถ€ LLM ๊ธฐ๋ฐ˜ ๋ฌธ์„œ ์ •๋ณด ์ถ”์ถœ (๋น„๋™๊ธฐ)","description":"### **์š”์•ฝ**\n๋‚ด๋ถ€๋ง์— ๋ฐฐํฌ๋œ LLM(Ollama ๊ธฐ๋ฐ˜)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œ(PDF, ์ด๋ฏธ์ง€ ๋“ฑ)์—์„œ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜๊ณ  ์‘๋‹ต์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ์—”๋“œํฌ์ธํŠธ๋Š” ์‚ฌ์ „ ์ •์˜๋œ ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n1. **์š”์ฒญ ์ ‘์ˆ˜**: `input_file`์„ ๋ฐ›์•„ ๊ณ ์œ  `request_id`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n2. **๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ**:\n - `input_file`์— ๋Œ€ํ•ด **OCR API**๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.\n - ์‹œ์Šคํ…œ์— ๋‚ด์žฅ๋œ ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ์™€ ์ถ”์ถœ๋œ ํ…์ŠคํŠธ๋ฅผ ์กฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. (`prompt_file`์„ ์—…๋กœ๋“œํ•˜์—ฌ ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)\n - ๋‚ด๋ถ€ LLM(Ollama)์— ์ถ”๋ก ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n3. **์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ**: `GET /extract/progress/{request_id}`๋กœ ์ž‘์—… ์ƒํƒœ์™€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ (multipart/form-data)**\n- `input_file` (**ํ•„์ˆ˜**): ์ •๋ณด ์ถ”์ถœ์˜ ๋Œ€์ƒ์ด ๋  ๋ฌธ์„œ ํŒŒ์ผ.\n - ์ง€์› ํ˜•์‹: `.pdf`, `.docx`, `.jpg`, `.png`, `.jpeg` ๋“ฑ.\n- `prompt_file` (์„ ํƒ): ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ ๋Œ€์‹  ์‚ฌ์šฉํ•  ์‚ฌ์šฉ์ž ์ •์˜ `.txt` ํ”„๋กฌํ”„ํŠธ ํŒŒ์ผ.\n- `model` (์„ ํƒ): ์‚ฌ์šฉํ•  ๋‚ด๋ถ€ LLM ๋ชจ๋ธ ์ด๋ฆ„. (๊ธฐ๋ณธ๊ฐ’: `gemma3:27b`)\n\n### **์ถœ๋ ฅ (application/json)**\n- **์ดˆ๊ธฐ ์‘๋‹ต**:\n ```json\n {\n \"message\": \"์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.\",\n \"request_id\": \"๊ณ ์œ ํ•œ ์š”์ฒญ ID\",\n \"status_check_url\": \"/extract/progress/๊ณ ์œ ํ•œ ์š”์ฒญ ID\"\n }\n ```\n- **์ตœ์ข… ๊ฒฐ๊ณผ**: `GET /extract/progress/{request_id}`๋ฅผ ํ†ตํ•ด ํ™•์ธ ๊ฐ€๋Šฅ.","operationId":"extract_endpoint_extract_inner_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_endpoint_extract_inner_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/extract/outer":{"post":{"tags":["Extraction"],"summary":"์™ธ๋ถ€ LLM ๊ธฐ๋ฐ˜ ๋ฌธ์„œ ์ •๋ณด ์ถ”์ถœ (๋น„๋™๊ธฐ)","description":"### **์š”์•ฝ**\n์™ธ๋ถ€ ์ƒ์šฉ LLM(์˜ˆ: GPT, Gemini)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œ์—์„œ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜๊ณ  ์‘๋‹ต์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€ LLM ์—”๋“œํฌ์ธํŠธ์™€ ์ž‘๋™ ๋ฐฉ์‹์€ ๋™์ผํ•˜๋‚˜, ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n1. **์š”์ฒญ ์ ‘์ˆ˜**: `input_file`์„ ๋ฐ›์•„ `request_id`๋ฅผ ์ƒ์„ฑ ํ›„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n2. **๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ**:\n - `input_file`์—์„œ **OCR API**๋ฅผ ํ†ตํ•ด ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.\n - ๋‚ด์žฅ๋œ ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ(๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ `prompt_file`)์™€ ํ…์ŠคํŠธ๋ฅผ ์กฐํ•ฉํ•ฉ๋‹ˆ๋‹ค.\n - ์™ธ๋ถ€ LLM API(OpenAI, Google ๋“ฑ)์— ์ถ”๋ก ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n3. **์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ**: `GET /extract/progress/{request_id}`๋กœ ์ž‘์—… ์ƒํƒœ์™€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ (multipart/form-data)**\n- `input_file` (**ํ•„์ˆ˜**): ์ •๋ณด ์ถ”์ถœ ๋Œ€์ƒ ๋ฌธ์„œ ํŒŒ์ผ.\n- `prompt_file` (์„ ํƒ): ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ ๋Œ€์‹  ์‚ฌ์šฉํ•  `.txt` ํŒŒ์ผ.\n- `model` (์„ ํƒ): ์‚ฌ์šฉํ•  ์™ธ๋ถ€ LLM ๋ชจ๋ธ ์ด๋ฆ„. (๊ธฐ๋ณธ๊ฐ’: `gemini-2.5-flash`)\n\n### **์ถœ๋ ฅ (application/json)**\n- **์ดˆ๊ธฐ ์‘๋‹ต**:\n ```json\n {\n \"message\": \"์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.\",\n \"request_id\": \"๊ณ ์œ ํ•œ ์š”์ฒญ ID\",\n \"status_check_url\": \"/extract/progress/๊ณ ์œ ํ•œ ์š”์ฒญ ID\"\n }\n ```\n- **์ตœ์ข… ๊ฒฐ๊ณผ**: `GET /extract/progress/{request_id}`๋ฅผ ํ†ตํ•ด ํ™•์ธ ๊ฐ€๋Šฅ.","operationId":"extract_endpoint_extract_outer_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_endpoint_extract_outer_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/extract/progress/{request_id}":{"get":{"tags":["Extraction"],"summary":"์ •๋ณด ์ถ”์ถœ ์ž‘์—… ์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ์กฐํšŒ","description":"### **์š”์•ฝ**\n`POST /extract/*` ๊ณ„์—ด ์—”๋“œํฌ์ธํŠธ ์š”์ฒญ ์‹œ ๋ฐ˜ํ™˜๋œ `request_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ํ•ด๋‹น ์ •๋ณด ์ถ”์ถœ ์ž‘์—…์˜ ์ง„ํ–‰ ์ƒํƒœ์™€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n- `request_id`๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Redis์— ์ €์žฅ๋œ ์ž‘์—… ๋กœ๊ทธ์™€ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n- ์ž‘์—…์ด ์ง„ํ–‰ ์ค‘์ผ ๋•Œ๋Š” ํ˜„์žฌ๊นŒ์ง€์˜ ๋กœ๊ทธ๋ฅผ, ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ๋Š” ๋กœ๊ทธ์™€ ํ•จ๊ป˜ ์ตœ์ข… ๊ฒฐ๊ณผ(`final_result`)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ**\n- `request_id`: ์กฐํšŒํ•  ์ž‘์—…์˜ ๊ณ ์œ  ID.\n\n### **์ถœ๋ ฅ (application/json)**\n- **์„ฑ๊ณต ์‹œ**:\n ```json\n {\n \"request_id\": \"์š”์ฒญ ์‹œ ์‚ฌ์šฉ๋œ ID\",\n \"progress_logs\": [\n { \"timestamp\": \"...\", \"status\": \"OCR ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"์ž…๋ ฅ ๊ธธ์ด ๊ฒ€์‚ฌ ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"LLM ์ถ”๋ก  ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"LLM ์ถ”๋ก  ์™„๋ฃŒ ๋ฐ ํ›„์ฒ˜๋ฆฌ ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"ํ›„์ฒ˜๋ฆฌ ์™„๋ฃŒ ๋ฐ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜\"\", \"details\": \"...\" } \n ],\n \"final_result\": {\n \"filename\": \"์ž…๋ ฅ ํŒŒ์ผ\",\n \"processed\": \"LLM์˜ ์ตœ์ข… ์‘๋‹ต ๋‚ด์šฉ\"\n }\n }\n ```\n- **ID๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ (404 Not Found)**:\n ```json\n {\n \"message\": \"{request_id}์— ๋Œ€ํ•œ ์ƒํƒœ ๋กœ๊ทธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.\"\n }\n ```","operationId":"get_pipeline_status_extract_progress__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dummy/extract/outer":{"post":{"tags":["Dummy"],"summary":"๋”๋ฏธ ์‘๋‹ต ์ƒ์„ฑ","description":"### **์š”์•ฝ**\n์‹ค์ œ ๋ชจ๋ธ ์ถ”๋ก ์ด๋‚˜ ํŒŒ์ผ ์—…๋กœ๋“œ ์—†์ด, ์ง€์ •๋œ ๋ชจ๋ธ์˜ ์‘๋‹ต ํ˜•์‹์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ๋”๋ฏธ(dummy) ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n- ์š”์ฒญ ์‹œ, ์‹œ์Šคํ…œ์— ๋ฏธ๋ฆฌ ์ €์žฅ๋œ ๋”๋ฏธ ์‘๋‹ต(`dummy_response.json`)์„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n- ์‹ค์ œ OCR, LLM ์ถ”๋ก  ๋“ฑ ์–ด๋– ํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n- ๋„คํŠธ์›Œํฌ๋‚˜ ๋ชจ๋ธ ์„ฑ๋Šฅ์— ๊ด€๊ณ„์—†์ด API ์‘๋‹ต ๊ตฌ์กฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ (multipart/form-data)**\n- `model` (์„ ํƒ): ์‘๋‹ต ํ˜•์‹์˜ ๊ธฐ์ค€์ด ๋  ๋ชจ๋ธ ์ด๋ฆ„. (๊ธฐ๋ณธ๊ฐ’: `dummy`)\n - ์ด ๊ฐ’์€ ์‹ค์ œ ์ถ”๋ก ์— ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ, ํ˜•์‹ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ๋งŒ ๊ธฐ๋Šฅํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ถœ๋ ฅ (application/json)**\n- **์ฆ‰์‹œ ๋ฐ˜ํ™˜**:\n ```json\n {\n \"filename\": \"dummy_input.pdf\",\n \"dummy_model\": {\n \"ocr_model\": \"dummy\",\n \"llm_model\": \"dummy\",\n \"api_url\": \"dummy\"\n },\n \"time\": {\n \"duration_sec\": \"0.00\",\n \"started_at\": \"...\",\n \"ended_at\": \"...\"\n },\n \"fields\": {},\n \"parsed\": \"dummy\",\n \"generated\": \"dummy\",\n \"processed\": {\n \"dummy response\"\n }\n }\n ```","operationId":"extract_outer_dummy_extract_outer_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_extract_outer_dummy_extract_outer_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ocr":{"post":{"tags":["OCR"],"summary":"๋ฌธ์„œ OCR ์š”์ฒญ (๋น„๋™๊ธฐ)","description":"### **์š”์•ฝ**\n๋ฌธ์„œ ํŒŒ์ผ(PDF, ์ด๋ฏธ์ง€ ๋“ฑ)์„ ๋ฐ›์•„ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•˜๋Š” OCR(๊ด‘ํ•™ ๋ฌธ์ž ์ธ์‹) ์ž‘์—…์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n1. **์š”์ฒญ ์ ‘์ˆ˜**: `file`์„ ๋ฐ›์•„ ๊ณ ์œ  `request_id`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.\n2. **๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ**:\n - ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์„ ๋‚ด๋ถ€ ์ €์žฅ์†Œ(MinIO)์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.\n - ๋ณ„๋„์˜ OCR ์„œ๋ฒ„์— ํ…์ŠคํŠธ ์ถ”์ถœ ์ž‘์—…์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.\n3. **์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ**: ๋ฐ˜ํ™˜๋œ `request_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ `GET /ocr/progress/{request_id}`๋กœ ์ž‘์—… ์ƒํƒœ๋ฅผ, `GET /ocr/result/{request_id}`๋กœ ์ตœ์ข… ํ…์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ (multipart/form-data)**\n- `file` (**ํ•„์ˆ˜**): ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•  ๋ฌธ์„œ ํŒŒ์ผ.\n - ์ง€์› ํ˜•์‹: `.pdf`, `.jpg`, `.png`, `.jpeg` ๋“ฑ OCR ์„œ๋ฒ„๊ฐ€ ์ง€์›ํ•˜๋Š” ํ˜•์‹.\n\n### **์ถœ๋ ฅ (application/json)**\n- **์ดˆ๊ธฐ ์‘๋‹ต**:\n ```json\n [\n {\n \"request_id\": \"๊ณ ์œ ํ•œ ์š”์ฒญ ID\",\n \"status\": \"์ž‘์—… ์ ‘์ˆ˜\",\n \"message\": \"์•„๋ž˜ URL์„ ํ†ตํ•ด ์ž‘์—… ์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.\"\n }\n ]\n ```\n- **์ตœ์ข… ๊ฒฐ๊ณผ**: `GET /ocr/result/{request_id}`๋ฅผ ํ†ตํ•ด ํ™•์ธ ๊ฐ€๋Šฅ.","operationId":"ocr_only_ocr_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_ocr_only_ocr_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ocr/progress/{request_id}":{"get":{"tags":["OCR"],"summary":"OCR ์ž‘์—… ์ƒํƒœ ์กฐํšŒ","description":"### **์š”์•ฝ**\n`POST /ocr` ์š”์ฒญ ์‹œ ๋ฐ˜ํ™˜๋œ `request_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ OCR ์ž‘์—…์˜ ํ˜„์žฌ ์ง„ํ–‰ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.\n\n### **์ž‘๋™ ๋ฐฉ์‹**\n- `request_id`๋ฅผ OCR ์„œ๋ฒ„์— ์ „๋‹ฌํ•˜์—ฌ ํ•ด๋‹น ์ž‘์—…์˜ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.\n- ์ƒํƒœ๋Š” ๋ณดํ†ต 'PENDING', 'IN_PROGRESS', 'SUCCESS', 'FAILURE' ๋“ฑ์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.\n\n### **์ž…๋ ฅ**\n- `request_id`: ์กฐํšŒํ•  OCR ์ž‘์—…์˜ ๊ณ ์œ  ID.\n\n### **์ถœ๋ ฅ (application/json)**\n- **์„ฑ๊ณต ์‹œ**:\n ```json\n {\n \"request_id\": \"์š”์ฒญ ์‹œ ์‚ฌ์šฉ๋œ ID\",\n \"progress_logs\": [\n { \"timestamp\": \"...\", \"status\": \"OCR ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"์ž…๋ ฅ ๊ธธ์ด ๊ฒ€์‚ฌ ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"LLM ์ถ”๋ก  ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"LLM ์ถ”๋ก  ์™„๋ฃŒ ๋ฐ ํ›„์ฒ˜๋ฆฌ ์‹œ์ž‘\", \"details\": \"...\" },\n { \"timestamp\": \"...\", \"status\": \"ํ›„์ฒ˜๋ฆฌ ์™„๋ฃŒ ๋ฐ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜\"\", \"details\": \"...\" }\n ],\n \"final_result\": {\n \"filename\": \"์ž…๋ ฅ ํŒŒ์ผ\",\n \"parsed\": \"OCR ๊ฒฐ๊ณผ ๋‚ด์šฉ\"\n }\n }\n ```\n- **ID๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ (404 Not Found)**:\n ```json\n {\n \"detail\": \"Meeting ID {request_id} ์ž‘์—… ์—†์Œ\"\n }\n ```","operationId":"get_pipeline_status_ocr_progress__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audio":{"post":{"tags":["STT Gateway"],"summary":"Proxy Audio","operationId":"proxy_audio_audio_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_proxy_audio_audio_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/progress/{request_id}":{"get":{"tags":["STT Gateway"],"summary":"Proxy Progress","operationId":"proxy_progress_progress__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/summary":{"post":{"tags":["summary"],"summary":"Summarize","operationId":"summarize_summary_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ollama_summary":{"post":{"tags":["summary"],"summary":"Ollama Summary","operationId":"ollama_summary_ollama_summary_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/task_summary":{"post":{"tags":["summary"],"summary":"Task Summary","operationId":"task_summary_task_summary_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/task_summary/{task_id}":{"get":{"tags":["summary"],"summary":"Get Status","operationId":"get_status_task_summary__task_id__get","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health/API":{"get":{"summary":"Health Check","description":"์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ ํ™•์ธ","operationId":"health_check_health_API_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health/Redis":{"get":{"summary":"Redis Health Check","operationId":"redis_health_check_health_Redis_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health/MinIO":{"get":{"summary":"Minio Health Check","operationId":"minio_health_check_health_MinIO_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_extract_endpoint_extract_inner_post":{"properties":{"input_file":{"type":"string","format":"binary","title":"Input File"},"prompt_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Prompt File","description":"โš ๏ธ prompt_file ์—…๋กœ๋“œํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, **'Send empty value'** ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๋ฐ˜๋“œ์‹œ ํ•ด์ œํ•ด์ฃผ์„ธ์š”."},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","default":"gemma3:27b"}},"type":"object","required":["input_file"],"title":"Body_extract_endpoint_extract_inner_post"},"Body_extract_endpoint_extract_outer_post":{"properties":{"input_file":{"type":"string","format":"binary","title":"Input File"},"prompt_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Prompt File","description":"โš ๏ธ prompt_file ์—…๋กœ๋“œํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, **'Send empty value'** ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๋ฐ˜๋“œ์‹œ ํ•ด์ œํ•ด์ฃผ์„ธ์š”."},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","default":"gemini-2.5-flash"}},"type":"object","required":["input_file"],"title":"Body_extract_endpoint_extract_outer_post"},"Body_extract_outer_dummy_extract_outer_post":{"properties":{"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"์‹ค์ œ ์ถ”๋ก  ์—†์ด ํฌ๋งท ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.","default":"dummy"}},"type":"object","title":"Body_extract_outer_dummy_extract_outer_post"},"Body_general_endpoint_general_inner_post":{"properties":{"input_file":{"type":"string","format":"binary","title":"Input File"},"prompt_file":{"type":"string","format":"binary","title":"Prompt File"},"schema_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Schema File"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","default":"gemma3:27b"}},"type":"object","required":["input_file","prompt_file"],"title":"Body_general_endpoint_general_inner_post"},"Body_general_endpoint_general_outer_post":{"properties":{"input_file":{"type":"string","format":"binary","title":"Input File"},"prompt_file":{"type":"string","format":"binary","title":"Prompt File"},"schema_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Schema File"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","default":"gemini-2.5-flash"}},"type":"object","required":["input_file","prompt_file"],"title":"Body_general_endpoint_general_outer_post"},"Body_ocr_only_ocr_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_ocr_only_ocr_post"},"Body_proxy_audio_audio_post":{"properties":{"audio_file":{"type":"string","format":"binary","title":"Audio File"}},"type":"object","required":["audio_file"],"title":"Body_proxy_audio_audio_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"SummaryRequest":{"properties":{"text":{"type":"string","title":"Text"}},"type":"object","required":["text"],"title":"SummaryRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}} \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..46868be Binary files /dev/null and b/static/logo.png differ