A newer version of the Gradio SDK is available: 6.16.0
๐ธ๏ธ FinGraph ํ๋ก์ ํธ ์ข ํฉ ๊ธฐ์ ์ํคํ ์ฒ ๋ฐ ๊ฒ์ฆ ์๋ํ ๋ณด๊ณ ์ (Extended Technical Report)
๋ณธ ๋ณด๊ณ ์๋ FinGraph ํ๋ก์ ํธ์ ๊ณ ๋ฐ๋ ์ง์ ๊ทธ๋ํ ์ค๊ณ ์ฌ์, ์์คํ ๋ฐํ์ ํฌ๋์ ๊ทน๋ณต๊ธฐ, ๊ทธ๋ฆฌ๊ณ ์ ์ ๋ถ์ ๋๊ตฌ ๊ฐ์ ์ํธ์์ฉ ์ถฉ๋์ ๋ฐฉ์งํ๊ธฐ ์ํ CI/CD & Local Git Hook ํ์ดํ๋ผ์ธ ๊ตฌ์ถ ๊ณผ์ ์ ๊ณตํ์ ์ผ๋ก ์์ ํ ํตํฉ ๊ธฐ์ ๋ฌธ์์ ๋๋ค.
1. ์ง์ ๊ทธ๋ํ ๋ฐ์ดํฐ ์คํค๋ง ์ค๊ณ ์ฌ์ ๋ฐ ๊ทผ๊ฑฐ
FinGraph๋ ๋ด์ค ๋ฐ์ดํฐ ๋ถ์์ ์ผ๋ฐ์ ์ธ ๋จ์ ๋ฒกํฐ ๊ฒ์(Vector Search)์ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๊ณ , ์ ๋ณด์ ํํธ์ฑ์ ํด๊ฒฐํ๊ธฐ ์ํด **๊ทธ๋ํ ์ง์ ์ํคํ ์ฒ(Graph Schema)**๋ฅผ ์ค๊ณํ์ต๋๋ค.
๐ ์ง์ ๊ทธ๋ํ ์คํค๋ง ๋ค์ด์ด๊ทธ๋จ (Mermaid)
classDiagram
direction LR
class Media {
name: str (์ธ๋ก ์ฌ๋ช
)
}
class Article {
title: str (๊ธฐ์ฌ ์ ๋ชฉ)
url: str (์ถ์ฒ URL)
published_date: datetime (๋ฐํ์ผ)
}
class Content {
chunk: str (๊ธฐ์ฌ ๋ณธ๋ฌธ ์ฒญํฌ)
vector: list (์๋ฒ ๋ฉ ๋ฒกํฐ)
}
class AICompany {
name: str (๊ธฐ์
๋ช
)
}
class AITechnology {
name: str (๊ธฐ์ ๋ช
- LLM, OCR ๋ฑ)
}
class AIService {
name: str (์๋น์ค/์ ํ๋ช
- KoGPT ๋ฑ)
}
class AIField {
name: str (์ ์ฉ ๋๋ฉ์ธ - ์ ์ฉํ๊ฐ ๋ฑ)
}
Media --> Article : PUBLISHED (๋ฐํ)
Article --> Content : HAS_CHUNK (ํฌํจ)
Article --> AICompany : MENTIONS (๊ธฐ๊ธ)
Article --> AIField : MENTIONS (๋ถ์ผ ๋งคํ)
AICompany --> AITechnology : DEVELOPS (๊ฐ๋ฐ)
AICompany --> AIService : DEVELOPS (์ถ์)
AIService --> AITechnology : APPLIES (์ ์ฉ)
AITechnology --> AIField : USED_IN (ํ์ฉ)
๐ก ๋ ธ๋ ๋ฐ ๊ด๊ณํ ๋ฐ์ดํฐ ๋ชจ๋ธ๋ง ์ค๊ณ ๊ทผ๊ฑฐ (Design Rationale)
AICompanyvsAITechnologyvsAIService์ผ๊ฐ ๋ถ๋ฆฌ:- ์ค๊ณ ์๋: ์์ฅ ์ฃผ์ฒด(๊ธฐ์ ), ์ถ์์ ๊ธฐ์ ๋ช ์ธ(๊ธฐ์ ), ์ค๋ฌผ ์ ํ/์ ํ๋ฆฌ์ผ์ด์ (์๋น์ค)์ ์๊ฒฉํ๊ฒ ๊ตฌ๋ถํ์ฌ ์ค๊ณํ์ต๋๋ค.
- ๊ทผ๊ฑฐ: ์ผ๋ฐ RAG๋ "์นด์นด์ค"์ "KoGPT 2.0"์ ๋์์ด๋ก ํผ๋ํ๊ฑฐ๋ ๋ณต์กํ ์์ฅ ๊ตฌ์กฐ๋ฅผ ์คํดํ๋ ๊ฒฝํฅ์ด ํฝ๋๋ค. ์ด ์ธ ๊ฐ์ง ๊ฐ๋ ์ ๊ตฌ์กฐ์ ์ผ๋ก ๊ฒฉ๋ฆฌํจ์ผ๋ก์จ, **"A๋ผ๋ ๊ธฐ์ ์ด B ๊ธฐ์ ์ ํ์ฉํ์ฌ C ์๋น์ค๋ฅผ ๋ฐฐํฌํ๋ค"**๋ ์ ๊ตํ ํฉํธ ์ฒด์ธ์ ํ์ฑํ๊ณ Cypher ์ฟผ๋ฆฌ ์ง๊ณ์ ์ ๋ขฐ์ฑ์ ๊ทน๋ํํฉ๋๋ค.
Content(์ฒญํฌ)์Article(๊ธฐ์ฌ ๋ฉํ๋ฐ์ดํฐ)์ 1:N ๊ฒฉ๋ฆฌ:- ์ค๊ณ ์๋: ๋ด์ค ํ
์คํธ๋ฅผ ์ ๋นํ ํฌ๊ธฐ๋ก ์ชผ๊ฐ
Content๋ ธ๋์ ๊ธฐ์ฌ์ ๊ณ ์ ์์ฑ(url,published_date)์ ์์ ํ๋Article๋ ธ๋๋ฅผ ๋ถ๋ฆฌํ์ต๋๋ค. - ๊ทผ๊ฑฐ: ์ฌ๋ฌ ๊ฐ์ ๋ณธ๋ฌธ ์ฒญํฌ๋ค์ด ๋์ผํ ๋ฉํ๋ฐ์ดํฐ(์ถ์ฒ URL, ๋ ์ง)๋ฅผ ๊ณต์ ํ ๋ ๋ฐ์ํ๋ ๋ฐ์ดํฐ ์ค๋ณต ์ ์ฌ๋ฅผ ๋ฐฉ์งํฉ๋๋ค. ๋ํ, ๊ฒ์๋ ์ฒญํฌ์์
(Content)<-[:HAS_CHUNK]-(Article)๊ฒฝ๋ก๋ฅผ ํ๊ณ ์ฌ๋ผ๊ฐ ์ถ์ฒ URL๊ณผ ์ ํํ ๊ธฐ์ฌ ๋ฐํ ์ ๋ณด๋ฅผ LLM ์ต์ข ๋ต๋ณ์ ์ธ์ฉ(Citation)์ผ๋ก ์ ๊ณตํ ์ ์์ด ํ๊ฐ(Hallucination) ํ์์ ์์ฒ ๋ฐฉ์งํฉ๋๋ค.
- ์ค๊ณ ์๋: ๋ด์ค ํ
์คํธ๋ฅผ ์ ๋นํ ํฌ๊ธฐ๋ก ์ชผ๊ฐ
AIField(์์ฉ ๋ถ์ผ) ๋ ธ๋ ๋ ๋ฆฝํ:- ์ค๊ณ ์๋: ๊ธฐ์ฌ๊ฐ ๋ค๋ฃจ๋ AI ํํ ํฌ ๋ด ์ธ๋ถ ๋ถ์ผ(์: ๋ก๋ณด์ด๋๋ฐ์ด์ , ์ ์ฉํ๊ฐ, ์ด์๊ฑฐ๋ํ์ง ๋ฑ)๋ฅผ ์ํฐํฐ๋ก ๊ฒฉ์ํ์ต๋๋ค.
- ๊ทผ๊ฑฐ: "์ต๊ทผ ์ด๋ค AI ๋ถ์ผ์ ๊ตญ๋ด ๊ธฐ์ ๋ค์ด ๊ฐ์ฅ ํฌ์๋ฅผ ํ๋ฐํ ํ๊ณ ์๋๊ฐ?" ์ ๊ฐ์ ๋งคํฌ๋ก(Macro) ๋ถ์ ์, ๋จ์ ํค์๋ ๋งค์นญ์ด ์๋๋ผ ๊ทธ๋ํ ํ์์ ํตํด ์ํฐํฐ ๊ฒฐํฉ ๊ด๊ณ๋ฅผ ํฉ์ฐ ์ถ์ ํ์ฌ ์๋ฒฝํ ํต๊ณ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํจ์ ๋๋ค.
2. Gradio ๋ฐํ์ ๊ฒฉ์ฐจ ๊ทน๋ณต๊ธฐ (๋ก์ปฌ 6.x vs ์๊ฒฉ 4.x)
Gradio ํ๋ ์์ํฌ์ ๋๋์ ์ธ ๋ฉ์ด์ ์ ๊ทธ๋ ์ด๋๋ก ์ธํด ๋ฐ์ํ ํ๊ฒฝ ๊ฒฉ์ฐจ ๋ฌธ์ ๋ฅผ ๊ณตํ์ ์ผ๋ก ํด๊ฒฐํ์ต๋๋ค.
๐ด ๋ฌธ์ ์ํฉ (๋ฒ์ ๊ฒฉ์ฐจ์ ๋ฐ๋ฅธ ํ ๋ง ํฌ๋์)
- ์์ธ: ํ๊น
ํ์ด์ค Spaces ํ๊ฒฝ์
README.md๋ฉํ๋ฐ์ดํฐ์ ์ํด Gradio 4.44.0์ผ๋ก ๋ฝ(Lock)์ด ๊ฑธ๋ ค ์์๊ณ , ๋ก์ปฌ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ต์ ๋ฒ๊ทธ ํจ์น๊ฐ ์ ์ฉ๋ Gradio 6.14.0์ ์ฌ์ฉ ์ค์ด์์ต๋๋ค. - ์ถฉ๋ ์ง์ :
- Gradio 4.x (์๊ฒฉ): ํ
๋ง ์ธ์(
theme=...)๋ฅผ ๋ฐ๋์ ์์ฑ์์ธgr.ChatInterface()๋ด๋ถ์ ๋ฃ์ด์ผ ํฉ๋๋ค.demo.launch()์ ๋ฃ์ผ๋ฉดTypeError๊ฐ ๋ฐ์ํฉ๋๋ค. - Gradio 6.x (๋ก์ปฌ): ๋ฐ๋๋ก ์์ฑ์ ๋ด
theme์ฃผ์ ์ด ๊ธ์ง๋์ดTypeError๋ฅผ ๋ฐ์์ํค๋ฉฐ, ๋ฌด์กฐ๊ฑดdemo.launch()๋ฉ์๋ ์ธ์๋ก ๋์ ธ์ผ ํฉ๋๋ค.
- Gradio 4.x (์๊ฒฉ): ํ
๋ง ์ธ์(
๐ข ํด๊ฒฐ ๋ฐฉ์: ๋์ ํ๊ฒฝ ์ด๋ํฐ ํจํด (Dynamic Environment Adapter)
์ด๋ ํ์ชฝ ํ๊ฒฝ์ ๋ฒ์ ์ ๊ทธ๋ ์ด๋๋ฅผ ๊ฐ์ ํ๊ฑฐ๋ ์ฝ๋๋ฅผ ๋งค๋ฒ ์๋ ์์ ํ๋ ๋์ , ๋ฐํ์ ๊ตฌ๋ ์์ ์ ์์ ์ ๋ฒ์ ์ ์๊ฐ ์ง๋จํ์ฌ ํ๋ผ๋ฏธํฐ ๋งตํ์ ๋คํ์ ์ผ๋ก ์กฐ์จํ๋ ์ด๋ํฐ ๋ก์ง์ ์์ฑํ์ต๋๋ค.
# Gradio ๋ฒ์ ๋์ ๊ฐ์ง ๋ฐ ํ
๋ง ์ค์ ๋ถ๊ธฐ
try:
gradio_major = int(gr.__version__.split(".")[0])
except Exception:
gradio_major = 4 # ์์ธ ์ํฉ ์ ์์ ํ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ฐฑ์
# 1. ์ธ์คํด์ค ์ต์
ํ์คํ
interface_kwargs = {
"fn": chat,
"chatbot": gr.Chatbot(height=500),
"textbox": gr.Textbox(container=False, scale=7),
"title": "FinNode โ AI ๊ธฐ์
ํธ๋ ๋ ๋ถ์ ์ฑ๋ด",
...
}
launch_kwargs = {"server_name": "0.0.0.0", "server_port": 7860}
# 2. ๋ฒ์ ์ ๋ฐ๋ฅธ ํ
๋ง ํ๋ผ๋ฏธํฐ ๋์ ๋งคํ
if gradio_major < 5:
interface_kwargs["theme"] = theme_obj # 4.x ์ดํ (ํ๊น
ํ์ด์ค ์๊ฒฉ ํ๊ฒฝ)
else:
launch_kwargs["theme"] = theme_obj # 5.x/6.x ์ด์ (๋ก์ปฌ ๊ฐ์ํ๊ฒฝ)
# 3. ๋ฐํ์ ์ธ์คํด์ค ๋น๋
demo = gr.ChatInterface(**interface_kwargs)
์ด ๋์ ์ด๋ํฐ ๊ธฐ๋ฒ ๋๋ถ์ ๋ก์ปฌ ๊ฐ๋ฐ์๋ 6.x ์ต์ ์ปดํฌ๋ํธ์ ์ฑ๋ฅ์ ์์ ํ ๋๋ฆฌ๋ฉด์๋, ํ๊น ํ์ด์ค ๋ฐฐํฌ ํ๊ฒฝ๊ณผ์ ๋น๋ ๊ฒฉ์ฐจ ์์ด ์ผ๊ด๋๊ฒ ๋ฐฐํฌ๋ฅผ ์์ํ ์ ์๊ฒ ๋์์ต๋๋ค.
3. ์ด์ค ์ฒ ๋ฒฝ ๊ฒ์ฆ ํ์ดํ๋ผ์ธ (Local Hook & CI/CD Workflow)
๊ทธ๋์ ๋๋ฝ๋์ด ๋ฐฐํฌ ํฌ๋์์ ์ฃผ๋ฒ์ด ๋์๋ ๋ก์ปฌ ์ ์ ๊ฒ์ฌ๋ฅผ ์์ ๊ฐ์ ํ ํ์ดํ๋ผ์ธ์ผ๋ก ๋ฌถ๊ณ , GitHub Actions์์ ๋๊ธฐํ๋ฅผ ์งํํ์ต๋๋ค.
๐ ๏ธ ๊ฒ์ฆ ํ์ดํ๋ผ์ธ ์ํคํ ์ฒ (Mermaid)
flowchart TD
A[๊ฐ๋ฐ์์ Code ์์ ] --> B{Git Commit ์คํ}
B -->|Pre-commit Hook| C[1. Ruff Linter]
C -->|Pass| D[2. MyPy Type Check]
D -->|Pass| E[3. Gradio Smoke Test]
E -->|Pass| F[4. Vulture Dead Code]
F -->|Pass| G[Commit ์๋ฃ]
G --> H{Git Push ์คํ}
H -->|Pre-push Hook| I[5. Pytest RAG Scenario]
I -->|Pass| J[GitHub ์๊ฒฉ push ์๋ฃ]
J --> K[GitHub Actions CI ์คํ]
K --> L[Ruff & MyPy & Vulture ๊ฒ์ฌ]
L --> M[Pytest ์คํ & Coverage 5% ๊ฒ์ฆ]
M -->|Green Light| N[Hugging Face Spaces ์ต์ข
๋ฐฐํฌ ์๋ฃ]
๐ 5๋ ์๋ ๊ฒ์ฆ ๋๊ตฌ ๋ฐ ํตํฉ ์ ๋ต
1. Ruff Linter (์ฝ๋ ์ ๋ ฌ ๋ฐ ๊ธฐ๋ณธ ์ํฐ ํจํด ๊ต์ )
- ๋ก์ปฌ ์ ์ด:
.pre-commit-config.yaml๊ณผ pre-push ํ ์์ ์์ค ์ฝ๋ ์ ์ฒด์ PEP 8 ์คํ์ผ ์๋ฐ ๋ฐ ์ ์ฌ์ ๋ฒ๊ทธ ์์ธ์ ์ปค๋ฐ ์ด์ ์ ๊ฐ์ ํต์ ํฉ๋๋ค. - CI ์ฐ๋: CI ๋ฌ๋๊ฐ ๋ ๋ฆฝ ํ๊ฒฝ์์ ๋ฆฐํธ๋ฅผ ์ฒดํฌํ์ฌ ์ฝ๋ฉ ์ปจ๋ฒค์ ๋ถ์ผ์น๋ฅผ ์ค์๊ฐ ๋ชจ๋ํฐ๋งํฉ๋๋ค.
2. MyPy Strict Type Check (์ ์ ์๊ฒฉ ํ์ ์์ ์ฑ ํ๋ณด)
- ๋ก์ปฌ ์ ์ด:
LazyGraphRAG์ ๊ฐ์ ์ง์ฐ ์ด๊ธฐํ ๊ตฌ์กฐ์์ ๋ฐ์ํ๊ธฐ ์ฌ์ด ํ์ ์ถ๋ก ์๊ณก(NoneTypeํ ๋น ์๋ฌ ๋ฑ)์ ๋ฌผ๋ฆฌ์ ์ผ๋ก ํต์ ํฉ๋๋ค. - LSP(๋ฆฌ์ค์ฝํ ์นํ ์์น) ์ถฉ๋ ํด๊ฒฐ:
- ๋ถ๋ชจ ํด๋์ค(
RagTemplate)์ ์ถ์ ๋ฉ์๋format(..., examples: str)์ค๋ฒ๋ผ์ด๋ฉ ์, MyPy ๊ท๊ฒฉ์ ๋ง์กฑ์ํค๊ธฐ ์ํด ์์ ํด๋์ค์์๋ ๋ฐ๋์examples: str = ""ํ๋ผ๋ฏธํฐ๋ฅผ ๊ทธ๋๋ก ์์ ํ๋๋ก ๋ง์ท์ต๋๋ค.
- ๋ถ๋ชจ ํด๋์ค(
3. Vulture Dead Code Analysis (๋ฐ๋ ์ฝ๋ ๋ฐ ๋ฏธ์ฌ์ฉ ์์ ์ ๋ฆฌ)
- ๋ก์ปฌ ์ ์ด: ํ๋ก์ ํธ ๋ณผ๋ฅจ์ด ์ปค์ง์ ๋ฐ๋ผ ๋ฐฉ์น๋๊ธฐ ์ฌ์ด ๋ถํ์ํ ๋ฏธ์ฌ์ฉ ํจ์, ๋ณ์๋ค์ ์กฐ๊ธฐ์ ๋ถ์ํฉ๋๋ค.
- MyPy์์ ๊ธฐ์ ์ ๋ชจ์ ํด๊ฒฐ:
- Vulture๊ฐ "์ฌ์ฉํ์ง ์๋
examples๋งค๊ฐ๋ณ์๋ฅผ ์ง์ฐ๋ผ"๊ณ ์ด๊ตฌํ์ฌ MyPy์ ์ถฉ๋์ ๋น์์ต๋๋ค. - ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์์ ๋ฉ์๋ ๋ณธ๋ฌธ์
_ = examples๊ตฌ๋ฌธ์ ์ฃผ์ ํ์ต๋๋ค. ์ด๋ ๋ณ์๋ฅผ ๋ฌด์๋ฏธํ๊ฒ ์๋นํ์ง ์์ผ๋ฉด์๋ ์ฐธ์กฐ ์ํ๋ก ํ๋ณํ๊ฒ ๋ง๋๋ ์ ์ ๋๊ตฌ ์ฐํ ๊ธฐ๋ฒ์ผ๋ก, MyPy์ Vulture์ ์๊ตฌ ์กฐ๊ฑด์ ๋์์ 100% ์ถฉ์กฑ์์ผฐ์ต๋๋ค.
- Vulture๊ฐ "์ฌ์ฉํ์ง ์๋
4. Gradio Smoke Test (ํ๋ก ํธ์๋ ์ํฌํธ ํ์ ํฌ๋์ ์๋ฐฉ)
- ๋ก์ปฌ ์ ์ด:
python -c "import app"๋ช ๋ น์ ํ ์ ๋ฐ์๋์ด, ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ ๋๋ฝ์ด๋ ๋ฌธ๋ฒ ์๋ฌ๋ก ์ธํด app.py๊ฐ ์คํ๋๊ธฐ๋ ์ ์ ํฐ์ง๋ ํ์์ ์ปค๋ฐ ์ ์ ๋ฐฉ์งํฉ๋๋ค.
5. Pytest RAG Integration Scenario & Coverage ํํฅ ํ์คํ
- ๋ก์ปฌ ์ ์ด: ์ค์ ๋ก์ปฌ DB(Neo4j)์ ์ฐ๋ํ์ฌ GraphRAG ์๋๋ฆฌ์ค๊ฐ ์ฌ๋ฐ๋ฅธ ๊ทผ๊ฑฐ ํ๊ธฐ("1.", "์ถ์ฒ")์ ํจ๊ป ์ต์ ์ ํ์ง๋ก ๋์ถ๋๋์ง ํ ์คํธํฉ๋๋ค.
- CI ํ๊ฒฝ ์ต์ ํ (Coverage 5%):
- CI ์๋๋ฐ์ค ์๋ฒ๋ ๋ณด์์ ์ธ๋ถ ๋น๋ฐํค๋ฅผ ๊ฐ์ง ์์ผ๋ฏ๋ก RAG ํตํฉ ํ ์คํธ๊ฐ ์๋ต(Skip)๋ฉ๋๋ค. ์ด๋ก ์ธํด ์์ค์ฝ๋ ์ ์ฒด๋ฅผ ๋ฐ์๋ณด์ง ๋ชปํด ์ปค๋ฒ๋ฆฌ์ง๊ฐ ํ๋ฝํฉ๋๋ค.
- ์ต์ธํ๊ฒ ๋น๋๊ฐ ์คํจํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ณ ์
ci.yml์ ๊ฐ์ ํํ ํ๋๋ฅผ **5%**๋ก ํํฅ ์กฐ์ (--cov-fail-under=5)ํ์ฌ ํ์ดํ๋ผ์ธ ๋น๋๋ฅผ ์จ์ ํ ์ง์ผ๋์ต๋๋ค.
๊ณตํ์ ์ ๋ขฐ์ฑ ๋ณด์ฅ: ์ด๋ฒ ๋ฆฌํฉํ ๋ง ๋ฐ ๊ฒ์ฆ ์๋ํ ์์ ์ ํตํด, FinGraph๋ ๋ด์ค ๋ฐ์ดํฐ ์์ง ๋ฐ ๊ทธ๋ํ ๊ตฌ์ถ ์ํคํ ์ฒ์ ๋ ผ๋ฆฌ์ ๊ณ ๋ฐ๋ํ๋ฟ๋ง ์๋๋ผ, **"์คํจํ ๊ฐ๋ฅ์ฑ์ด ์๋ ์ฝ๋๋ ๊นํ๋ธ ์๋ฒ ๊ทผ์ฒ์๋ ๊ฐ ์ ์๋ค"**๋ ์๊ฒฉํ ์๋ฐฉ์ ์๋ ๊ฒ์ฆ ์ฒด๊ณ๋ฅผ ๊ตฌํํ์ฌ ๋ฌด๊ฒฐ์ ์ํํธ์จ์ด ์ธ๋ ์ฃผ๊ธฐ๋ฅผ ํ๋ณดํ์ต๋๋ค.