# ๐Ÿ•ธ๏ธ FinGraph ํ”„๋กœ์ ํŠธ ์ข…ํ•ฉ ๊ธฐ์ˆ  ์•„ํ‚คํ…์ฒ˜ ๋ฐ ๊ฒ€์ฆ ์ž๋™ํ™” ๋ณด๊ณ ์„œ (Extended Technical Report) ๋ณธ ๋ณด๊ณ ์„œ๋Š” **FinGraph** ํ”„๋กœ์ ํŠธ์˜ ๊ณ ๋ฐ€๋„ ์ง€์‹ ๊ทธ๋ž˜ํ”„ ์„ค๊ณ„ ์‚ฌ์ƒ, ์‹œ์Šคํ…œ ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ ๊ทน๋ณต๊ธฐ, ๊ทธ๋ฆฌ๊ณ  ์ •์  ๋ถ„์„ ๋„๊ตฌ ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ CI/CD & Local Git Hook ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ• ๊ณผ์ •์„ ๊ณตํ•™์ ์œผ๋กœ ์„œ์ˆ ํ•œ ํ†ตํ•ฉ ๊ธฐ์ˆ  ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค. --- ## 1. ์ง€์‹ ๊ทธ๋ž˜ํ”„ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์„ค๊ณ„ ์‚ฌ์ƒ ๋ฐ ๊ทผ๊ฑฐ FinGraph๋Š” ๋‰ด์Šค ๋ฐ์ดํ„ฐ ๋ถ„์„์— ์ผ๋ฐ˜์ ์ธ ๋‹จ์ˆœ ๋ฒกํ„ฐ ๊ฒ€์ƒ‰(Vector Search)์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ณ , ์ •๋ณด์˜ ํŒŒํŽธ์„ฑ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด **๊ทธ๋ž˜ํ”„ ์ง€์‹ ์•„ํ‚คํ…์ฒ˜(Graph Schema)**๋ฅผ ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค. ### ๐Ÿ“Š ์ง€์‹ ๊ทธ๋ž˜ํ”„ ์Šคํ‚ค๋งˆ ๋‹ค์ด์–ด๊ทธ๋žจ (Mermaid) ```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) 1. **`AICompany` vs `AITechnology` vs `AIService` ์‚ผ๊ฐ ๋ถ„๋ฆฌ**: * **์„ค๊ณ„ ์˜๋„**: ์‹œ์žฅ ์ฃผ์ฒด(๊ธฐ์—…), ์ถ”์ƒ์  ๊ธฐ์ˆ  ๋ช…์„ธ(๊ธฐ์ˆ ), ์‹ค๋ฌผ ์ œํ’ˆ/์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์„œ๋น„์Šค)์„ ์—„๊ฒฉํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•˜์—ฌ ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค. * **๊ทผ๊ฑฐ**: ์ผ๋ฐ˜ RAG๋Š” "์นด์นด์˜ค"์™€ "KoGPT 2.0"์„ ๋™์˜์–ด๋กœ ํ˜ผ๋™ํ•˜๊ฑฐ๋‚˜ ๋ณต์žกํ•œ ์‹œ์žฅ ๊ตฌ์กฐ๋ฅผ ์˜คํ•ดํ•˜๋Š” ๊ฒฝํ–ฅ์ด ํฝ๋‹ˆ๋‹ค. ์ด ์„ธ ๊ฐ€์ง€ ๊ฐœ๋…์„ ๊ตฌ์กฐ์ ์œผ๋กœ ๊ฒฉ๋ฆฌํ•จ์œผ๋กœ์จ, **"A๋ผ๋Š” ๊ธฐ์—…์ด B ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•˜์—ฌ C ์„œ๋น„์Šค๋ฅผ ๋ฐฐํฌํ–ˆ๋‹ค"**๋Š” ์ •๊ตํ•œ ํŒฉํŠธ ์ฒด์ธ์„ ํ˜•์„ฑํ•˜๊ณ  Cypher ์ฟผ๋ฆฌ ์ง‘๊ณ„์˜ ์‹ ๋ขฐ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค. 2. **`Content` (์ฒญํฌ)์™€ `Article` (๊ธฐ์‚ฌ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ)์˜ 1:N ๊ฒฉ๋ฆฌ**: * **์„ค๊ณ„ ์˜๋„**: ๋‰ด์Šค ํ…์ŠคํŠธ๋ฅผ ์ ๋‹นํ•œ ํฌ๊ธฐ๋กœ ์ชผ๊ฐ  `Content` ๋…ธ๋“œ์™€ ๊ธฐ์‚ฌ์˜ ๊ณ ์œ  ์†์„ฑ(`url`, `published_date`)์„ ์†Œ์œ ํ•˜๋Š” `Article` ๋…ธ๋“œ๋ฅผ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. * **๊ทผ๊ฑฐ**: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ณธ๋ฌธ ์ฒญํฌ๋“ค์ด ๋™์ผํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(์ถœ์ฒ˜ URL, ๋‚ ์งœ)๋ฅผ ๊ณต์œ ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ค‘๋ณต ์ ์žฌ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ฒ€์ƒ‰๋œ ์ฒญํฌ์—์„œ `(Content)<-[:HAS_CHUNK]-(Article)` ๊ฒฝ๋กœ๋ฅผ ํƒ€๊ณ  ์˜ฌ๋ผ๊ฐ€ **์ถœ์ฒ˜ URL๊ณผ ์ •ํ™•ํ•œ ๊ธฐ์‚ฌ ๋ฐœํ–‰ ์ •๋ณด๋ฅผ LLM ์ตœ์ข… ๋‹ต๋ณ€์— ์ธ์šฉ(Citation)์œผ๋กœ ์ œ๊ณต**ํ•  ์ˆ˜ ์žˆ์–ด ํ™˜๊ฐ(Hallucination) ํ˜„์ƒ์„ ์›์ฒœ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. 3. **`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()` ๋ฉ”์„œ๋“œ ์ธ์ž๋กœ ๋˜์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ### ๐ŸŸข ํ•ด๊ฒฐ ๋ฐฉ์•ˆ: ๋™์  ํ™˜๊ฒฝ ์–ด๋Œ‘ํ„ฐ ํŒจํ„ด (Dynamic Environment Adapter) ์–ด๋А ํ•œ์ชฝ ํ™˜๊ฒฝ์˜ ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ๊ฐ•์ œํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ๋ฅผ ๋งค๋ฒˆ ์ˆ˜๋™ ์ˆ˜์ •ํ•˜๋Š” ๋Œ€์‹ , ๋Ÿฐํƒ€์ž„ ๊ตฌ๋™ ์‹œ์ ์— ์ž์‹ ์˜ ๋ฒ„์ „์„ ์ž๊ฐ€ ์ง„๋‹จํ•˜์—ฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋งตํ•‘์„ ๋‹คํ˜•์ ์œผ๋กœ ์กฐ์œจํ•˜๋Š” ์–ด๋Œ‘ํ„ฐ ๋กœ์ง์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ```python # 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) ```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% ์ถฉ์กฑ**์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. #### 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`)ํ•˜์—ฌ ํŒŒ์ดํ”„๋ผ์ธ ๋นŒ๋“œ๋ฅผ ์˜จ์ „ํžˆ ์ง€์ผœ๋ƒˆ์Šต๋‹ˆ๋‹ค. --- > [!IMPORTANT] > **๊ณตํ•™์  ์‹ ๋ขฐ์„ฑ ๋ณด์žฅ**: > ์ด๋ฒˆ ๋ฆฌํŒฉํ† ๋ง ๋ฐ ๊ฒ€์ฆ ์ž๋™ํ™” ์ž‘์—…์„ ํ†ตํ•ด, FinGraph๋Š” ๋‰ด์Šค ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ• ์•„ํ‚คํ…์ฒ˜์˜ ๋…ผ๋ฆฌ์  ๊ณ ๋ฐ€๋„ํ™”๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, **"์‹คํŒจํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ ์„œ๋ฒ„ ๊ทผ์ฒ˜์—๋„ ๊ฐˆ ์ˆ˜ ์—†๋‹ค"**๋Š” ์—„๊ฒฉํ•œ ์˜ˆ๋ฐฉ์  ์ž๋™ ๊ฒ€์ฆ ์ฒด๊ณ„๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๋ฌด๊ฒฐ์  ์†Œํ”„ํŠธ์›จ์–ด ์ธ๋„ ์ฃผ๊ธฐ๋ฅผ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค.