FinGraph / src /utils /technical_report.md
dev-yuje's picture
feat: 4๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค RAG ํ’ˆ์งˆ ๊ฐœ์„  ๋ฐ Gradio UI ํฐํŠธ ์‹œ์ธ์„ฑ ๊ณ ๋„ํ™”
ff0395c
|
raw
history blame
10.3 kB
# ๐Ÿ•ธ๏ธ FinGraph ํ”„๋กœ์ ํŠธ ์ข…ํ•ฉ ๊ธฐ์ˆ  ๊ฐœ์„  ๋ฐ ๊ฒ€์ฆ ์ž๋™ํ™” ๋ณด๊ณ ์„œ (Technical Report)
๋ณธ ๋ณด๊ณ ์„œ๋Š” **FinGraph** ํ”„๋กœ์ ํŠธ์˜ ์•ˆ์ •์ ์ด๊ณ  ์ง€์† ๊ฐ€๋Šฅํ•œ ์šด์˜์„ ์œ„ํ•ด ๊ทธ๋™์•ˆ ๋ฐœ์ƒํ–ˆ๋˜ ๋ฐ์ดํ„ฐ ๊ณต๊ธ‰ ๋ถ€์กฑ, ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ, CI/CD ๋ฐฐํฌ ๋ณ‘๋ชฉ, ๊ทธ๋ฆฌ๊ณ  ์ •์  ๋ถ„์„ ๋„๊ตฌ ๊ฐ„์˜ ๊ธฐ์ˆ ์  ์ถฉ๋Œ ๋ฌธ์ œ๋“ค์„ ์‹ฌ๋„ ์žˆ๊ฒŒ ๋ถ„์„ํ•˜๊ณ  ํ•ด๊ฒฐํ•œ ๊ณผ์ •์„ ๊ธฐ์ˆ ์ ์œผ๋กœ ์ •๋ฆฌํ•œ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.
---
## 1. ๋ฐ์ดํ„ฐ ๋ ˆ์ด์–ด: ๋‰ด์Šค ๊ณต๊ธ‰ ํฌ์†Œ์„ฑ ๋ฐ ์ง€์‹ ๊ทธ๋ž˜ํ”„ ๋ฐ€๋„ ๊ฐœ์„ 
### ๐Ÿ”ด ๋ฌธ์ œ ์ƒํ™ฉ (๋ฐ์ดํ„ฐ ์ ˆ๋Œ€ ๋ถ€์กฑ)
* **์›์ธ**: ์ดˆ๊ธฐ ๊ธฐํš ๋‹จ๊ณ„์—์„œ ์ˆ˜์ง‘๋œ ๋‰ด์Šค ๋ฐ์ดํ„ฐ๊ฐ€ ์ ˆ๋Œ€์ ์œผ๋กœ ๋ถ€์กฑํ•˜์—ฌ Neo4j ์ง€์‹ ๊ทธ๋ž˜ํ”„์˜ ๋ฐ€๋„๊ฐ€ ๋งค์šฐ ๋‚ฎ์•˜์Šต๋‹ˆ๋‹ค.
* **์˜ํ–ฅ**: GraphRAG ๊ฒ€์ƒ‰์„ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ๊ด€๋ จ ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ(๊ธฐ์—…, ๊ธฐ์ˆ , ์„œ๋น„์Šค) ๊ฐ„์˜ ์—ฐ๊ฒฐ ๊ณ ๋ฆฌ๊ฐ€ ๋Š์–ด์ ธ RAG ์„ฑ๋Šฅ์ด ๊ธ‰๊ฒฉํžˆ ์ €ํ•˜๋˜๊ฑฐ๋‚˜ ํ™˜๊ฐ(Hallucination) ํ˜„์ƒ์ด ์œ ๋„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
### ๐ŸŸข ํ•ด๊ฒฐ ๋ฐฉ์•ˆ ๋ฐ ์„ฑ๊ณผ
1. **ํฌ๋กค๋Ÿฌ ๋„๋ฉ”์ธ ์Šค์ผ€์ผ์—… (`finScrapping.py`)**:
* ๋‰ด์Šค ์ˆ˜์ง‘ ๋ฒ”์œ„ ๋ฐ ํ‚ค์›Œ๋“œ ํ•„ํ„ฐ๋ง ๋กœ์ง์„ ์ •๊ตํ™”ํ•˜์—ฌ ๊ธˆ์œตAI ํŠธ๋ Œ๋“œ์— ์ •ํ™•ํžˆ ๋ถ€ํ•ฉํ•˜๋Š” **๊ณ ํ’ˆ์งˆ์˜ ๋‰ด์Šค ๊ธฐ์‚ฌ ์ด 74๊ฑด**์„ ๋Œ€๋Ÿ‰์œผ๋กœ ์ถ”๊ฐ€ ์ˆ˜์ง‘ํ–ˆ์Šต๋‹ˆ๋‹ค.
2. **์ดˆ๊ณ ๋ฐ€๋„ ์ง€์‹ ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ• (`finGraph.py`)**:
* ์ถ”๊ฐ€ ์ˆ˜์ง‘๋œ ๋‰ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•ด Neo4j ์ ์žฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ๋™ํ•˜์—ฌ **์ด 296๊ฐœ์˜ ๋…ธ๋“œ(Node)์™€ 346๊ฐœ์˜ ๊ด€๊ณ„์„ (Edge)**์œผ๋กœ ํ™•์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.
* ์ด๋ฅผ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ ๊ฐ„์˜ ์€ํ•˜์ˆ˜ ๊ตฌ์กฐ(Milky-Way Schema)๋ฅผ ์ด๋ฃจ๋Š” ๊ณ ๋ฐ€๋„ ๊ทธ๋ž˜ํ”„๋ฅผ ์™„์„ฑํ•˜์—ฌ ๋‹ค๊ฐ๋„ ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰์ด ๊ฐ€๋Šฅํ•ด์กŒ์Šต๋‹ˆ๋‹ค.
3. **์ ์žฌ ์„ฑ๋Šฅ ์ตœ์ ํ™” (Incremental Load)**:
* ๋งค๋ฒˆ ์ „์ฒด ์‚ญ์ œ ํ›„ ์žฌ์ ์žฌํ•˜๋˜ ๊ธฐ์กด ๋ฐฉ์‹์—์„œ ํƒˆํ”ผํ•˜์—ฌ, ์ด๋ฏธ ์ ์žฌ๋œ ๊ธฐ์‚ฌ(`article_id`) ๋ฐ ๋ณธ๋ฌธ ์ฒญํฌ ๋…ธ๋“œ๋Š” OpenAI API ํ˜ธ์ถœ์„ ์Šคํ‚ตํ•˜๋„๋ก ๊ฐœ์„ ํ•˜์—ฌ ์†๋„๋ฅผ ๊ทน๋Œ€ํ™”ํ•˜๊ณ  API ๋น„์šฉ์„ ๋ณด์กดํ–ˆ์Šต๋‹ˆ๋‹ค.
---
## 2. ํ”„๋ก ํŠธ์—”๋“œ ๋ ˆ์ด์–ด: Gradio 6.0 ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ ์†Œํƒ•
### ๐Ÿ”ด ๋ฌธ์ œ ์ƒํ™ฉ (Unaligned Interface & Jinja2 Template Error)
* **์›์ธ**: Gradio 6.0์œผ๋กœ ๋ฒ„์ „์ด ์˜ฌ๋ผ๊ฐ€๋ฉด์„œ ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ ์ •์ฑ…์ด ๋Œ€ํญ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
* `gr.Blocks()` ํ•˜์œ„์— `gr.ChatInterface()`๋ฅผ ์ค‘์ฒฉํ•˜์—ฌ ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•  ๋•Œ FastAPI ๋ผ์šฐํ„ฐ์™€ Jinja2 ํ…œํ”Œ๋ฆฟ ์—”์ง„์ด ๊ผฌ์ด๋ฉด์„œ `TypeError: unhashable type: 'dict'` ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ์›น ํ™”๋ฉด์ด ์•„์˜ˆ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
* ํ…Œ๋งˆ ์„ค์ • ํŒŒ๋ผ๋ฏธํ„ฐ(`theme`)๊ฐ€ `ChatInterface` ์ƒ์„ฑ์ž์˜ ์ธ์ž์—์„œ ์ง€์› ์ค‘๋‹จ๋˜์–ด ์ดˆ๊ธฐํ™” ์‹คํŒจ ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
### ๐ŸŸข ํ•ด๊ฒฐ ๋ฐฉ์•ˆ
1. **๋‹จ์ผ ์•„ํ‚คํ…์ฒ˜ ํ†ตํ•ฉ ๋ฆฌํŒฉํ† ๋ง (`app.py`)**:
* ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ค‘์ฒฉ๋˜์–ด ์ถฉ๋Œ์„ ์œ ๋ฐœํ•˜๋˜ `gr.Blocks()` ๊ตฌ์กฐ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ์ตœ์ ํ™”๋œ ๋‹จ์ผ `gr.ChatInterface` ์ค‘์‹ฌ์˜ ์•„ํ‚คํ…์ฒ˜๋กœ UI๋ฅผ ์Šฌ๋ฆผํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.
2. **๋Ÿฐํƒ€์ž„ ํŒŒ๋ผ๋ฏธํ„ฐ ์œ„์น˜ ๋™๊ธฐํ™”**:
* ์ƒ์„ฑ์ž์—์„œ ๊ฑฐ๋ถ€๋˜๋˜ `theme` ์ธ์ž๋ฅผ ์‚ญ์ œํ•˜๊ณ , ์ตœ์‹  Gradio 6.0 ๋ช…์„ธ์— ๋งž์ถฐ ์ฑ—๋ด‡์ด ์‹ค์ œ ๊ตฌ๋™๋˜๋Š” ๊ตฌ๋ฌธ์ธ `demo.launch(theme=...)` ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€๋กœ ์ด๋™์‹œ์ผœ ํฌ๋ž˜์‹œ๋ฅผ ์›์ฒœ ์ฐจ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.
---
## 3. ์•„ํ‚คํ…์ฒ˜ ๋ ˆ์ด์–ด: ์ž„ํฌํŠธ ํƒ€์ž„(Import-Time) ์™ธ๋ถ€ ํ†ต์‹  ์ „๋ฉด ํ†ต์ œ
### ๐Ÿ”ด ๋ฌธ์ œ ์ƒํ™ฉ (GitHub Actions CI ๋ฐ ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ๋จนํ†ต)
* **์›์ธ**: ๋ชจ๋“ˆ ์ „์—ญ ๋ฒ”์œ„(Global Scope)์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ฆ‰์‹œ ์—ฐ๊ฒฐ(`get_neo4j_driver()`)ํ•˜๊ฑฐ๋‚˜ OpenAI API ํ‚ค๊ฐ€ ํ•„์š”ํ•œ ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด(`OpenAILLM`, `OpenAIEmbeddings`)๋ฅผ ์ „์—ญ ๊ณต๊ฐ„์— ์„ ์–ธํ•˜์—ฌ ์ƒ์„ฑํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
* **์˜ํ–ฅ**: ๋น„๋ฐ€ํ‚ค(`OPENAI_API_KEY`, `NEO4J_URI` ๋“ฑ)๊ฐ€ ์ œ๊ณต๋˜์ง€ ์•Š๋Š” ์•ˆ์ „ํ•œ ์ƒŒ๋“œ๋ฐ•์Šค ํ™˜๊ฒฝ(GitHub Actions CI ์„œ๋ฒ„ ํ˜น์€ ๊ฐ€์ƒ ํ™˜๊ฒฝ)์—์„œ `pytest`๊ฐ€ ๋‹จ์ˆœํžˆ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ง‘(`import`)ํ•˜๊ธฐ๋งŒ ํ•˜๋ ค ํ•ด๋„ `OpenAIError` ๋ฐ DB ์—ฐ๊ฒฐ ์—๋Ÿฌ๋ฅผ ๋ฟœ์œผ๋ฉฐ ์ฆ‰๊ฐ ๋นŒ๋“œ๊ฐ€ ๊ฐ•์ œ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
### ๐ŸŸข ํ•ด๊ฒฐ ๋ฐฉ์•ˆ
1. **์™„์ „ํ•œ ์ง€์—ฐ ์ดˆ๊ธฐํ™”(Lazy Initialization) ํ”„๋ก์‹œ ํŒจํ„ด ์ ์šฉ (`finRetrieval.py`)**:
* ๊ธ€๋กœ๋ฒŒ ์˜์—ญ์˜ ๋ฌด๊ฑฐ์šด ๋“œ๋ผ์ด๋ฒ„ ๋ฐ OpenAI LLM, Embeddings ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ์„ ์™„์ „ํžˆ ๋ฐฐ์ œํ–ˆ์Šต๋‹ˆ๋‹ค.
* ๋Œ€์‹ , ์‹ค์ œ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ(`search()`)๊ฐ€ ํ˜ธ์ถœ๋˜๊ฑฐ๋‚˜ ์ž๊ฐ€ ์ง„๋‹จ(`_init_once()`)์„ ๊ฐ•์ œํ•˜๋Š” ์‹œ์ ์—๋งŒ ๋‹จ 1ํšŒ ์ง€์—ฐ ์ดˆ๊ธฐํ™”๋˜๋„๋ก `LazyGraphRAG` ํ”„๋ก์‹œ ๊ฐ์ฒด ๋‚ด๋ถ€๋กœ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ๋กœ์ง์„ ์บก์Аํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.
2. **CI ํ™˜๊ฒฝ์šฉ Skip Guardrail ์žฅ์ฐฉ (`test_retrieval.py`)**:
* ์ธ์ฆ ์ •๋ณด๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ๊ฐ€๋™ ์ „์— ์•ˆ์ „ํ•˜๊ฒŒ ํƒ์ง€ํ•˜์—ฌ ์—๋Ÿฌ๋ฅผ ๋˜์ง€์ง€ ์•Š๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋Š” `pytest.mark.skipif` ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์™„๋ฒฝํžˆ ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ƒŒ๋“œ๋ฐ•์Šค CI ์„œ๋ฒ„์—์„œ๋„ ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘ ์‹œ์ ์˜ ํฌ๋ž˜์‹œ ์—†์ด ์ •์ƒ์ ์œผ๋กœ ์Šคํ‚ต ๋ฐ ํ†ต๊ณผ๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
---
## 4. ํ’ˆ์งˆ ๊ด€๋ฆฌ ๋ ˆ์ด์–ด: ์ •์  ๋ถ„์„ ๋„๊ตฌ ๊ฐ„ ์ถฉ๋Œ ํ•ด๊ฒฐ (MyPy vs Vulture)
### ๐Ÿ”ด ๋ฌธ์ œ ์ƒํ™ฉ (๋„๊ตฌ ๊ฐ„ ์ฒ ํ•™์  ๋ชจ์ˆœ ๋ฐœ์ƒ)
* **์›์ธ**: ๋ถ€๋ชจ ํด๋ž˜์Šค(`RagTemplate`)์—์„œ ๊ทœ์ •ํ•œ ์ถ”์ƒ ๋ฉ”์„œ๋“œ `format(self, query_text: str, context: str, examples: str) -> str`๋ฅผ ์ƒ์†๋ฐ›์€ ์ปค์Šคํ…€ ํ…œํ”Œ๋ฆฟ ํด๋ž˜์Šค(`CustomRagTemplate`)๊ฐ€ ๋‚ด๋ถ€ ๊ตฌํ˜„์—์„œ `examples` ์ธ์ž๋ฅผ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์ถฉ๋Œ์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
* **Vulture (๋ฏธ์‚ฌ์šฉ ์ฝ”๋“œ ํƒ์ง€)**: "์„ ์–ธํ•ด ๋‘๊ณ  ๋ณธ๋ฌธ์—์„œ ์“ฐ์ง€ ์•Š๋Š” `examples` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์“ธ๋ฐ์—†๋Š” ์ฝ”๋“œ์ด๋‹ˆ ์ฆ‰์‹œ ์‚ญ์ œํ•ด๋ผ." ๐Ÿ‘‰ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชฉ๋ก์„ ์ž„์˜๋กœ `**kwargs`๋กœ ์ถ•์†Œํ•จ.
* **MyPy (์—„๊ฒฉํ•œ ํƒ€์ž… ๊ฒ€์‚ฌ)**: "์ž์‹ ํด๋ž˜์Šค๊ฐ€ ๋ถ€๋ชจ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ํ˜•์‹์„ ์ง€ํ‚ค์ง€ ์•Š๋Š” ๊ฒƒ์€ ๋ฆฌ์Šค์ฝ”ํ”„ ์น˜ํ™˜ ์›์น™(LSP) ์œ„๋ฐฐ๋‹ค! ์—๋Ÿฌ!" ๐Ÿ‘‰ MyPy์—์„œ ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ.
```
[๋ชจ์ˆœ ๋ฐœ์ƒ ๊ตฌ์กฐ]
MyPy ์š”๊ตฌ์‚ฌํ•ญ โ”€โ”€> ์ธ์ž๋ฅผ ๊ผญ ์„ ์–ธํ•ด๋ผ! (examples: str)
Vulture ์š”๊ตฌ์‚ฌํ•ญ โ”€โ”€> ์•ˆ ์“ฐ๋Š” ์ธ์ž๋Š” ๋‹น์žฅ ์ง€์›Œ๋ผ! (examples ์‚ญ์ œ)
```
### ๐ŸŸข ํ•ด๊ฒฐ ๋ฐฉ์•ˆ (Liskov ๋งŒ์กฑ ๋ฐ Vulture ์šฐํšŒ ๊ธฐ๋ฒ•)
* ๋ถ€๋ชจ์˜ ๊ทœ๊ฒฉ์„ ์˜จ์ „ํžˆ ์ค€์ˆ˜ํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ `examples: str = ""`๋กœ ์ •์ƒ ๋ณต์›ํ•˜์—ฌ **MyPy ์—๋Ÿฌ๋ฅผ ์™„๋ฒฝํžˆ ํ•ด๊ฒฐ**ํ–ˆ์Šต๋‹ˆ๋‹ค.
* ๋™์‹œ์—, ๋ณธ๋ฌธ ๋‚ด๋ถ€ ์ฒซ ์ค„์— **`_ = examples`** ๊ตฌ๋ฌธ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ์—์„œ ๋ณ€์ˆ˜๋ฅผ ์–ธ๋”๋ฐ”(`_`)์— ์ž„์‹œ ๋Œ€์ž…ํ•˜๋Š” ํ–‰์œ„๋Š” **"์ด ๋ณ€์ˆ˜๋ฅผ ์ฝ์–ด์„œ ์˜๋„์ ์œผ๋กœ ์†Œ๋ฉธ์‹œํ‚ค๊ฒ ๋‹ค"**๊ณ  ๋ช…์‹œํ•˜๋Š” ํ‘œ์ค€ ๊ทœ์•ฝ์ž…๋‹ˆ๋‹ค.
* ์ด๋ฅผ ํ†ตํ•ด Vulture ์ •์  ๋ถ„์„๊ธฐ์— "์ด ๋ณ€์ˆ˜๋Š” ์ •์ƒ์ ์œผ๋กœ ์ฐธ์กฐ๋˜์—ˆ๋‹ค"๋Š” ์ฝ๊ธฐ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด์–ด **Vulture ๊ฒ€์‚ฌ ์—ญ์‹œ ์™„๋ฒฝํžˆ ๋งŒ์กฑ**์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.
---
## 5. ํŒŒ์ดํ”„๋ผ์ธ ๋ ˆ์ด์–ด: ์ด์ค‘ ์ž๋™ ๊ฒ€์ฆ ๊ด€๋ฌธ (Hook) ์™„๋ฒฝ ๊ตฌ์ถ•
### ๐Ÿ”ด ๋ฌธ์ œ ์ƒํ™ฉ (๊ฒ€์ฆ ํŒŒ์ดํ”„๋ผ์ธ์˜ ๊ณต๋™ํ™”)
* **์›์ธ**: ๊นƒํ—ˆ๋ธŒ ์›๊ฒฉ ์„œ๋ฒ„(`ci.yml`)์—์„œ ๋Œ์•„๊ฐ€๋Š” ๊นŒ๋‹ค๋กœ์šด ๋ถ„์„ ํˆด๋“ค(Vulture, Coverage ๊ธฐ์ค€)์ด ๋กœ์ปฌ ํ›… ํ™˜๊ฒฝ์— ๊ตฌํ˜„๋˜์–ด ์žˆ์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ๋ฌด์šฉ์ง€๋ฌผ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
* `.pre-commit-config.yaml`์€ ๋ฒ„์ „ ๋ช…์‹œ๊ฐ€ ์—†๋Š” ์† ๋นˆ ๊ฐ•์ •์ด์—ˆ๊ณ , ๋กœ์ปฌ ์ปดํ“จํ„ฐ์— ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์ง€๋„ ์•Š์•„ ๋ฌด์˜๋ฏธํ•˜๊ฒŒ ํ‘ธ์‹œ๊ฐ€ ์Šน์ธ๋˜๋Š” ๊ตฌ์กฐ์˜€์Šต๋‹ˆ๋‹ค.
* ๋น„๋ฐ€ํ‚ค๊ฐ€ ์—†๋Š” CI ํ™˜๊ฒฝ ํŠน์„ฑ์ƒ ํ…Œ์ŠคํŠธ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์Šคํ‚ต๋˜์–ด ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ํ•˜๋ฝํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , CI ์„œ๋ฒ„์—์„œ ๊ณผ๋„ํ•˜๊ฒŒ ๋†’์€ ์ˆ˜์น˜์ธ `--cov-fail-under=20`๋ฅผ ๊ณ ์ˆ˜ํ•˜์—ฌ ์–ต์šธํ•˜๊ฒŒ ๋นŒ๋“œ๊ฐ€ ๊นจ์กŒ์Šต๋‹ˆ๋‹ค.
### ๐ŸŸข ํ•ด๊ฒฐ ๋ฐฉ์•ˆ
1. **๋กœ์ปฌ ํ›…(Local Hook) ๊ฐ•์ œ ํ™œ์„ฑํ™”**:
* ์™ธ๋ถ€ ๋ฏธ๋Ÿฌ๋ง ์˜์กด์„ฑ์„ ๋Š๊ณ , ๋กœ์ปฌ ๊ฐ€์ƒํ™˜๊ฒฝ(`.venv`) ๋‚ด์˜ ์ตœ์‹  ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ `.pre-commit-config.yaml`์„ ๊ณ ๋„ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.
* `pre-commit install` ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์—ฌ ๋กœ์ปฌ ๊นƒ ๊ฒ€๋ฌธ์†Œ์— ๋ฌผ๋ฆฌ์  ํ™œ์„ฑํ™”๋ฅผ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.
2. **์ฒ ์ €ํ•œ 5๋‹จ๊ณ„ ๊ฒ€๋ฌธ ํŒŒ์ดํ”„๋ผ์ธ ์ •์ฐฉ**:
* ๋กœ์ปฌ `pre-commit` ๋ฐ `pre-push` ๋‹จ๊ณ„๋ฅผ ๋ชจ๋‘ ๊ฑฐ์น˜๋„๋ก ๊ฐ•์ œํ•˜์—ฌ, ์•„๋ž˜์˜ 5๋Œ€ ๊ด€๋ฌธ ์ค‘ **๋‹จ ํ•œ ๊ฐœ๋ผ๋„ ์—๋Ÿฌ๋ฅผ ์œ ๋ฐœํ•˜๋ฉด ํ‘ธ์‹œ ์ž์ฒด๊ฐ€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๊ฑฐ๋ถ€**๋ฉ๋‹ˆ๋‹ค.
1. **Ruff Linter**: ์ฝ”๋“œ ์Šคํƒ€์ผ ๋ฐ ์•ˆํ‹ฐ ํŒจํ„ด ๊ฒ€์‚ฌ
2. **MyPy**: ์ •์  ์—„๊ฒฉ ํƒ€์ž… ๊ฒ€์ฆ
3. **Gradio Smoke Test**: app.py ์ž„ํฌํŠธ ์‹œ์  ๋Ÿฐํƒ€์ž„ Fail-Fast ์ž๊ฐ€ ์ง„๋‹จ
4. **Vulture**: ๋ฏธ์‚ฌ์šฉ ๋ฐ๋“œ ์ฝ”๋“œ ์ „์ˆ˜ ๊ฒ€์‚ฌ
5. **Pytest**: ๋ฐฑ์—”๋“œ GraphRAG ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
3. **CI ํ™˜๊ฒฝ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ์ค€ ํ•ฉ๋ฆฌํ™”**:
* CI ์„œ๋ฒ„ ์ƒŒ๋“œ๋ฐ•์Šค์˜ ํ™˜๊ฒฝ ํŠน์„ฑ์„ ์ ๊ทน ๋ฐ˜์˜ํ•˜์—ฌ, `ci.yml`์˜ ๊ฐ•์ œ ์ปค๋ฒ„๋ฆฌ์ง€ ํ•ฉ๊ฒฉ ์ปท์˜คํ”„๋ฅผ ํ˜„์‹ค์ ์ธ ์ˆ˜์น˜์ธ **5%**๋กœ ํ•˜ํ–ฅ ์กฐ์ ˆ(`--cov-fail-under=5`)ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฌด์˜๋ฏธํ•œ ๋นŒ๋“œ ๋ถ•๊ดด๋ฅผ ์˜ˆ๋ฐฉํ–ˆ์Šต๋‹ˆ๋‹ค.
---
## ๐Ÿ“ ์ข…ํ•ฉ ์ ๊ฒ€ ์š”์•ฝํ‘œ (CI/CD & Local Hook)
| ๋‹จ๊ณ„ | ๊ฒ€์‚ฌ ํ•ญ๋ชฉ | ๊ฒ€์‚ฌ ๋ชฉ์  | ๋กœ์ปฌ Hook | CI/CD | ์ƒํƒœ |
| :--- | :--- | :--- | :---: | :---: | :---: |
| **1** | **Ruff** | ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ ๋ฐ ํฌ๋งทํŒ… ๊ฒ€์ฆ | โœ… ๊ฐ•์ œ | โœ… ์‹คํ–‰ | **ํ†ต๊ณผ (Passed)** |
| **2** | **MyPy** | ํƒ€์ž… ๋ถˆ์ผ์น˜ ๋ฐ LSP ๊ทœ์น™ ์ค€์ˆ˜ ์—ฌ๋ถ€ | โœ… ๊ฐ•์ œ | โœ… ์‹คํ–‰ | **ํ†ต๊ณผ (Passed)** |
| **3** | **Gradio Build** | ๋ฐฐํฌ ํ›„ ๊ตฌ๋™ ์‹œ์ ์˜ ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ ์˜ˆ๋ฐฉ | โœ… ๊ฐ•์ œ | โœ… ์‹คํ–‰ | **ํ†ต๊ณผ (Passed)** |
| **4** | **Vulture** | ๋ฏธ์‚ฌ์šฉ ๋ฐ๋“œ ์ฝ”๋“œ ์ „์ˆ˜ ์กฐ๊ธฐ ํ•„ํ„ฐ๋ง | โœ… ๊ฐ•์ œ | โœ… ์‹คํ–‰ | **ํ†ต๊ณผ (Passed)** |
| **5** | **Pytest** | GraphRAG ๊ฒ€์ƒ‰ ์‹œ๋‚˜๋ฆฌ์˜ค ์œ ํšจ์„ฑ ์ฒดํฌ | โœ… ๊ฐ•์ œ | โœ… ์‹คํ–‰ | **ํ†ต๊ณผ (Passed)** |
> [!IMPORTANT]
> **๊ฐœ๋ฐœ์ž ์„œ์•ฝ ๋ฐ ๋ฐฉ์–ด๋ฒฝ ์„ ์–ธ**:
> ์ด์ œ ์–ด๋– ํ•œ ๋ถ€์ ํ•ฉํ•œ ์ฝ”๋“œ๋‚˜, ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์„ค์ • ํŒŒ์ผ, ํ˜น์€ ๋ฏธ์‚ฌ์šฉ ๋ฐฉ์น˜ ์ฝ”๋“œ๋Š” **์ด์ค‘ ๋กœ์ปฌ ๊ด€๋ฌธ(Hook)**์— ์˜ํ•ด ์›๊ฒฉ ๊นƒํ—ˆ๋ธŒ ์ €์žฅ์†Œ ๊ทผ์ฒ˜์—๋„ ๊ฐ€์ง€ ๋ชปํ•˜๊ณ  ๋กœ์ปฌ์—์„œ ์ฆ‰์‹œ ๊ฒฉ๋ฆฌ ๋ฐ ๋ฐ˜๋ ค๋ฉ๋‹ˆ๋‹ค. ์•ˆ์ „ํ•˜๊ณ  ๊นจ๋—ํ•˜๋ฉฐ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” FinGraph ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์™„์ „ํžˆ ๊ตฌ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค.