FinGraph / AGENTS.md
dev-yuje's picture
refactor: AGENTS.md ๋ฐ README.md ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์ตœ์‹ ํ™” ๋ฐ ์ฐธ๊ณ ์ž๋ฃŒ ํฌ๋ ˆ๋”ง ์ถ”๊ฐ€
f0b1337
###### ์ฐธ๊ณ : https://wikidocs.net/340866
###### ์ฐธ๊ณ ์ž๋ฃŒ(GraphRAG ToolsRetriever): https://github.com/gongwon-nayeon/graphrag-tools-retriever
###### ํ•˜๋„ค์Šค ์—”์ง€๋‹ˆ์–ด๋ง: Global์ง€์นจ, Skills์™€ Workflow๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•˜๋Š” ์ง€์นจ
###### ๊ฐœ๋ฐœ ์‹œ์ž‘๋ถ€ํ„ฐ ๋ฐฐํฌ๊นŒ์ง€ ๋ชจ๋“  ๊ฒƒ์€ AGENTS.md์— ๊ธฐ๋กํ•œ๋‹ค.
###### ์˜ˆ๋ฅผ๋“ค์–ด ๊ฐœ๋ฐœ ๋‹จ๊ณ„์—์„œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ ์ฒดํฌํ•˜๋„๋ก ์ง€์‹œํ•œ๋‹ค.
# AGENTS.md
## ํ”„๋กœ์ ํŠธ ๊ฐœ์š”
- ๋ชฉ์ : AI ๊ธฐ๋ฐ˜ ํ•€ํ…Œํฌ ๊ธฐ์ˆ ์˜ ํŠธ๋ Œ๋“œ๋ฅผ ํŒŒ์•…ํ•˜๋„๋ก ๋•๋Š” ์ฑ—๋ด‡
- ์–ธ์–ด: Python 3.10
- ๊ธฐ์ˆ ์Šคํƒ: GraphRAG, LangChain, LangGraph, Neo4j, HugingFace, Gradio
## ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ
FinGraph/
โ”œโ”€โ”€ app.py # Gradio + LangGraph ์ฑ—๋ด‡ (HF ๋ฐฐํฌ ์ง„์ž…์  ๋ฐ Fail-Fast ์ž๊ฐ€ ์ง„๋‹จ ์‹คํ–‰)
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ __init__.py # ํŒจํ‚ค์ง€ ์ดˆ๊ธฐํ™” ํŒŒ์ผ (Import-time ๋ถ€ํ•˜ ๋ฐฉ์ง€์šฉ ๋นˆ ํŒŒ์ผ)
โ”‚ โ”œโ”€โ”€ utils/ # ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ชจ๋“ˆ ํด๋”
โ”‚ โ”‚ โ””โ”€โ”€ ui_templates.py # Gradio GNB HTML, ํ”„๋ฆฌ๋ฏธ์—„ ์ปค์Šคํ…€ CSS ๋ฐ ํ†ต๊ณ„ ์นด๋“œ ๋ Œ๋”๋ง์šฉ ๋งˆํฌ์—… ํ…œํ”Œ๋ฆฟ
โ”‚ โ”œโ”€โ”€ graphBuilder/ # ์ง€์‹ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์—”์ง„
โ”‚ โ”‚ โ”œโ”€โ”€ scrapping/ # ๋‰ด์Šค ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ ˆ์ด์–ด
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ finScrapping.py # Selenium ๊ธฐ๋ฐ˜ ๊ธˆ์œต AI ํƒ€๊ฒŸ ๋„ค์ด๋ฒ„ ๋‰ด์Šค ์‹ค์‹œ๊ฐ„/๋™์  ๊ต์ฐจ ํฌ๋กค๋Ÿฌ
โ”‚ โ”‚ โ””โ”€โ”€ neo4j/ # ๊ทธ๋ž˜ํ”„ ๋ฐ์ดํ„ฐ ์ ์žฌ ๋ ˆ์ด์–ด
โ”‚ โ”‚ โ””โ”€โ”€ finGraph.py # LLM(gpt-4o) + LangGraph ๊ธฐ๋ฐ˜ ์ ์ง„์  ์—”ํ‹ฐํ‹ฐ/๊ด€๊ณ„ ์ถ”์ถœ ๋ฐ Neo4j AuraDB ์ ์žฌ ํŒŒ์ดํ”„๋ผ์ธ
โ”‚ โ””โ”€โ”€ retrieval/ # GraphRAG ๊ฒ€์ƒ‰ ๋ ˆ์ด์–ด
โ”‚ โ””โ”€โ”€ finRetrieval.py # Vector / VectorCypher / Text2Cypher ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰ ๊ธฐ๋ฐ˜ GraphRAG ์—”์ง„
โ”œโ”€โ”€ scripts/ # ์œ ์ง€๋ณด์ˆ˜ ๋ฐ ๋ฐ์ดํ„ฐ ์ ์žฌ ์œ ํ‹ธ๋ฆฌํ‹ฐ ์Šคํฌ๋ฆฝํŠธ
โ”‚ โ”œโ”€โ”€ delete_zero_rel_articles.py # ๊ด€๊ณ„๊ฐ€ ์—†๋Š” ๊ณ ๋ฆฝ ๊ธฐ์‚ฌ ์ •๋ฆฌ (๋กœ์ปฌ ์ •๋ฆฌ์šฉ)
โ”‚ โ”œโ”€โ”€ inject_fintech_gold_data.py # ํฌํŠธํด๋ฆฌ์˜ค์šฉ ๊ณจ๋“œ ๋ฐ์ดํ„ฐ ์ฃผ์ž… ๋ฐ Neo4j ์Šคํ‚ค๋งˆ ์ •ํ•ฉ์„ฑ ํ†ตํ•ฉ ์Šคํฌ๋ฆฝํŠธ
โ”‚ โ”œโ”€โ”€ plot_keywords.py # ์ˆ˜์ง‘ ํ‚ค์›Œ๋“œ ๋นˆ๋„ ์‹œ๊ฐํ™” ๋ฐ ๋ถ„์„
โ”‚ โ”œโ”€โ”€ reset_db.py # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์™„์ „ ์ดˆ๊ธฐํ™” (์ œ์•ฝ ์กฐ๊ฑด ์žฌ์„ค์ • ํฌํ•จ)
โ”‚ โ””โ”€โ”€ run_pipeline.py # ํŒŒ์ดํ”„๋ผ์ธ(ํฌ๋กค๋ง+๋นŒ๋“œ) ์ˆœ์ฐจ ์‹คํ–‰ ์œ ํ‹ธ๋ฆฌํ‹ฐ
โ”œโ”€โ”€ tests/ # ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒ€์ฆ ๋””๋ ‰ํ† ๋ฆฌ
โ”‚ โ”œโ”€โ”€ smoke_test_rag.py # ๊ทธ๋ž˜ํ”„ ์—ฐ๊ฒฐ์„ฑ(๋ฐ€๋„ 3.0 ์ด์ƒ) ๋ฐ 4๋Œ€ ์‹œ๋‚˜๋ฆฌ์˜ค RAG ํ†ตํ•ฉ ๊ฒ€์ฆ
โ”‚ โ””โ”€โ”€ test_retrieval.py # RAG ํ•ต์‹ฌ ๋™์ž‘ ๋ฐ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰๊ธฐ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ
โ”œโ”€โ”€ Dockerfile # Hugging Face Spaces(Gradio ๊ตฌ๋™ ํ™˜๊ฒฝ) ๋ฐฐํฌ์šฉ ์ปจํ…Œ์ด๋„ˆ ๋นŒ๋“œ ๋ช…์„ธ
โ”œโ”€โ”€ requirements.txt # ํ”„๋กœ๋•์…˜/Hugging Face ๋นŒ๋“œ ํฌ๋ž˜์‹œ ๋ฐฉ์ง€์šฉ ํ•ต์‹ฌ ์˜์กด์„ฑ ๋ชฉ๋ก
โ”œโ”€โ”€ .env.example # API ํ‚ค ๋ฐ Neo4j ์ ‘์† ์ •๋ณด ์„ค์ • ์˜ˆ์‹œ ํ…œํ”Œ๋ฆฟ
โ”œโ”€โ”€ .gitignore # references/, ๋ฐฑ์—… ํŒŒ์ผ, ํฌ๋กค๋ง ์—‘์…€(Articles_*.xlsx) ๋“ฑ ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ์˜ ์—…๋กœ๋“œ๋ฅผ ์›์ฒœ ์ฐจ๋‹จํ•˜๋Š” Git ํ•„ํ„ฐ
โ”œโ”€โ”€ .pre-commit-config.yaml # ruff, mypy, black ๋“ฑ ์ •์  ๋ถ„์„์„ ์ปค๋ฐ‹ ์ „์— ์ž๋™ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒ€์ฆ ํ›…
โ”œโ”€โ”€ pyproject.toml # ruff ๋ฐ mypy ๋“ฑ์˜ ๊ฐœ๋ฐœ ๋„๊ตฌ ๋ฆฐํŠธ ๋ฃฐ ๊ตฌ์„ฑ ์„ค์ •
โ”œโ”€โ”€ AGENTS.md # AI ์—์ด์ „ํŠธ ๊ฐœ๋ฐœ ์ง€์นจ, ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ, ์žฌ๋ฐœ ๋ฐฉ์ง€ ๋Œ€์ฑ… ๋ฐ ํ”„๋กœ์ ํŠธ ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ก (๋ณธ ํŒŒ์ผ)
โ”œโ”€โ”€ README.md # Hugging Face Spaces ์•ฑ ๊ธฐ๋ณธ ์„ค์ • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฐ ์ „์ฒด ํ”„๋กœ์ ํŠธ ๊ฐœ์š”์™€ ์•„ํ‚ค์ฒ˜ ๋ช…์„ธ
โ”œโ”€โ”€ LICENSE # ์˜คํ”ˆ์†Œ์Šค ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ MIT ์ •์‹ ๋ผ์ด์„ ์Šค ํŒŒ์ผ
โ””โ”€โ”€ .github/
โ””โ”€โ”€ workflows/
โ”œโ”€โ”€ ci.yml # GitHub Actions CI ๊ฒ€์ฆ ์›Œํฌํ”Œ๋กœ์šฐ (Ruff/Mypy/Pytest)
โ”œโ”€โ”€ daily_pipeline.yml # ๋งค์ผ ์ƒˆ๋ฒฝ 1์‹œ ํŒŒ์ดํ”„๋ผ์ธ (๋น„ํ™œ์„ฑ, ์ถ”ํ›„ ์Šค์ผ€์ค„๋Ÿฌ ์žฌ๊ฐ€๋™์šฉ ๋ช…์„ธ ์žฅ์ฐฉ)
โ””โ”€โ”€ deploy.yml # HF Spaces ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ (Git Push ํŠธ๋ฆฌ๊ฑฐ ๋™๊ธฐํ™”)
> [!IMPORTANT]
> **๋ถˆํ•„์š”ํ•œ ์ž„์‹œ ํŒŒ์ผ, ์—‘์…€ ๋ฐ์ดํ„ฐ ํŒŒ์ผ(Articles_*.xlsx), ๋กœ์ปฌ ๋ถ„์„์šฉ ๋…ธํŠธ๋ถ(references/), ํŒจ์น˜์šฉ ์Šคํฌ๋ฆฝํŠธ ๋“ฑ์€ ์ ˆ๋Œ€๋กœ ๊นƒํ—ˆ๋ธŒ(GitHub) ์ €์žฅ์†Œ์— ์—…๋กœ๋“œ๋˜๊ฑฐ๋‚˜ ๋ฐฐํฌ๋˜์ง€ ์•Š๋„๋ก `.gitignore` ํŒŒ์ผ์— ์™„๋ฒฝํ•˜๊ฒŒ ๋“ฑ๋กํ•˜์—ฌ ์ €์žฅ์†Œ๋ฅผ ํ•ญ์ƒ ๊นจ๋—ํ•˜๊ฒŒ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.**
## ์ฝ”๋“œ ๊ทœ์น™
- ํ•จ์ˆ˜๋ช…: snake_case
- ํด๋ž˜์Šค๋ช…: PascalCase
- ๋ณ€์ˆ˜๋ช…: camelCase
- ํ•œ ํ•จ์ˆ˜๋Š” ํ•˜๋‚˜์˜ ์—ญํ• ๋งŒ ์ˆ˜ํ–‰ํ•œ๋‹ค
- ํƒ€์ž… ํžŒํŠธ ํ•„์ˆ˜
- **๋ชจ๋“  ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—๋Š” ํŒŒ์ผ์˜ ์ƒ์„ธ ์—ญํ• , ์ž‘์„ฑ์ž, ๋ผ์ด์„ ์Šค ๋“ฑ์„ ํ•œ๊ธ€ ์ฃผ์„์œผ๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ๋ช…์‹œํ•˜๊ณ , ๊ฐ ๋ชจ๋“ˆ, ํด๋ž˜์Šค, ํ•จ์ˆ˜์—๋„ ์ƒ์„ธํ•œ ํ•œ๊ธ€ ๋…์ŠคํŠธ๋ง(Docstring) ๋ฐ ์ธ๋ผ์ธ ์ฃผ์„์„ ๋‹ฌ์•„์•ผ ํ•œ๋‹ค.**
- **์ง€์‹ ๊ทธ๋ž˜ํ”„ ์ ์žฌ ๊ทœ์น™ (Incremental Load)**: ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์ฒด ์‚ญ์ œ(DETACH DELETE)ํ•˜์ง€ ์•Š๊ณ , ์ด๋ฏธ ์ ์žฌ๋œ ๊ธฐ์‚ฌ(`article_id`) ๋ฐ ์ฒญํ‚น์ด ์™„๋ฃŒ๋œ `Content` ๋…ธ๋“œ๋Š” OpenAI API(Chat/Embeddings) ํ˜ธ์ถœ ๋‚ญ๋น„์™€ ์†๋„ ์ €ํ•˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด **๋ฐ˜๋“œ์‹œ ์ดˆ๊ณ ์† ์Šคํ‚ต(Skip)**ํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค.
- **Neo4j ์ธ์ฆ ํฌ๋ ˆ๋ด์…œ ๊ทœ์น™**: AuraDB ๋“ฑ์˜ ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ ์ ‘์† ์‹œ ์ธ์ฆ(Unauthorized) ์˜ค๋ฅ˜๋ฅผ ์™„๋ฒฝํžˆ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ๋“œ๋ผ์ด๋ฒ„ ์—ฐ๊ฒฐ ์‹œ `NEO4J_USERNAME`๊ณผ `NEO4J_PASSWORD` ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋งŒ ๋‹จ๋…์œผ๋กœ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๊ฑฐ๋‚˜ ์˜์กดํ•˜๋Š” ๊ฒƒ์„ **์—„๊ฒฉํžˆ ๊ธˆ์ง€**ํ•œ๋‹ค. ๋ฐ˜๋“œ์‹œ `NEO4J_CLIENT_ID`์™€ `NEO4J_CLIENT_SECRET`์„ ์šฐ์„  ๊ฐ์ง€ํ•˜์—ฌ ์ž๋™ ๋งตํ•‘(Fallback)ํ•˜๋Š” ์œ ์—ฐํ•œ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
- **๊ทธ๋ž˜ํ”„ ๊ด€๊ณ„ ์—ฐ๊ฒฐ ๊ทœ์น™ (Graph Connectivity)**: ์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์ง์ ‘ ๊ด€๊ณ„(DEVELOPS, APPLIES, USED_IN ๋“ฑ)๊ฐ€ ๋ฐ˜๋“œ์‹œ ์ ์žฌ๋˜์–ด์•ผ ํ•œ๋‹ค. `extract_relations` ๋…ธ๋“œ์—์„œ LLM์ด ๋ฐ˜ํ™˜ํ•œ source/target ์ด๋ฆ„์ด ์‹ค์ œ `extract_entities`์—์„œ ์ถ”์ถœ๋œ ์ด๋ฆ„๊ณผ **์ •ํ™•ํžˆ ์ผ์น˜**ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•œ ํ›„์—๋งŒ Neo4j์— ์ ์žฌํ•œ๋‹ค. ์—”ํ‹ฐํ‹ฐ๊ฐ€ 2๊ฐœ ์ด์ƒ ์ถ”์ถœ๋˜์—ˆ์Œ์—๋„ ๊ด€๊ณ„๊ฐ€ 0๊ฐœ์ธ ๊ฒฝ์šฐ **์ตœ๋Œ€ 2ํšŒ ์ž๊ธฐ๋ฐ˜์„ฑ(Self-Reflection) ๋ฃจํ”„๋กœ ์žฌ์ถ”์ถœ**์„ ๊ฐ•์ œํ•œ๋‹ค.
- **๊ทธ๋ž˜ํ”„ ๊ด€๊ณ„ ๋ฐ€๋„ ๊ธฐ์ค€ (Coverage)**: `smoke_test_rag.py`์˜ ์‚ฌ์ „ ์ ๊ฒ€ ๋‹จ๊ณ„์—์„œ **๊ธฐ์‚ฌ๋‹น ํ‰๊ท  ์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์ง์ ‘ ๊ด€๊ณ„ 3.0๊ฐœ ์ด์ƒ**์„ ์ตœ์†Œ ๊ธฐ์ค€์œผ๋กœ ๊ฒ€์ฆํ•œ๋‹ค. ์ด ๊ธฐ์ค€์„ ๋ฏธ๋‹ฌํ•˜๋ฉด ํŒŒ์ดํ”„๋ผ์ธ ์žฌ์‹คํ–‰์ด ํ•„์š”ํ•˜๋‹ค.
- **LLM ๋ชจ๋ธ ๊ทœ์น™ (Model Governance)**: ์—”ํ‹ฐํ‹ฐ/๊ด€๊ณ„ ์ถ”์ถœ(`finGraph.py`)์—๋Š” **๋ฐ˜๋“œ์‹œ `gpt-4o`** ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ทธ๋ž˜ํ”„ ํ’ˆ์งˆ์„ ์ตœ๋Œ€ํ™”ํ•œ๋‹ค. RAG ๊ฒ€์ƒ‰ ๋ฐ ๋‹ต๋ณ€ ์ƒ์„ฑ(`finRetrieval.py`), ์ž„๋ฒ ๋”ฉ์—๋Š” `gpt-4o-mini`์™€ `text-embedding-3-small`์„ ์‚ฌ์šฉํ•œ๋‹ค. ๋น„์šฉ ์ ˆ๊ฐ์„ ์ด์œ ๋กœ ์—”ํ‹ฐํ‹ฐ/๊ด€๊ณ„ ์ถ”์ถœ ๋ชจ๋ธ์„ `gpt-4o-mini`๋กœ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๊ฒƒ์„ **์—„๊ฒฉํžˆ ๊ธˆ์ง€**ํ•œ๋‹ค.
## ์ ˆ๋Œ€ ๊ธˆ์ง€
- 'references/' ํŒŒ์ผ ์ˆ˜์ • ๊ธˆ์ง€ (์ฐธ๊ณ ์ž๋ฃŒ, ๋กœ์ปฌ ์ „์šฉ)
- Neo4j ๋“œ๋ผ์ด๋ฒ„ ์—ฐ๊ฒฐ ์‹œ `NEO4J_USERNAME`, `NEO4J_PASSWORD`๋งŒ์„ ์š”๊ตฌํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์˜ ์˜›๋‚  ์ฝ”๋“œ ์ž‘์„ฑ ์ ˆ๋Œ€ ๊ธˆ์ง€ (Connection Client Credentials ๋ณ‘ํ–‰ ๋งคํ•‘ ํ•„์ˆ˜)
## ๐Ÿšจ ์žฌ๋ฐœ ๋ฐฉ์ง€ ๋ฐ ์น˜๋ช…์  ์•ˆํ‹ฐ ํŒจํ„ด ๊ธˆ์ง€ (Recurring Issues Prevention)
์ด ํ”„๋กœ์ ํŠธ์—์„œ 3ํšŒ ์ด์ƒ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ฐœ์ƒํ•˜์—ฌ ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ(๋กœ์ปฌ, CI, ํ”„๋กœ๋•์…˜)์„ ๋ถ•๊ดด์‹œ์ผฐ๋˜ ํ•ต์‹ฌ ์žฅ์• ๋“ค์„ ์˜๊ตฌ์ ์œผ๋กœ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ํ•„์ˆ˜ ๊ทœ์น™ ๋ฐ ๋ฐฉ์–ด ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.
- **1. Import-Time DB Connection ๋ฐ API Client ๊ฐ์ฒด ์ƒ์„ฑ ์ ˆ๋Œ€ ๊ธˆ์ง€ (CI ํฌ๋ž˜์‹œ ๋ฐฉ์ง€)**
- **์›์ธ**: ๋ชจ๋“ˆ ์ „์—ญ ๋ฒ”์œ„(Global Scope)์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ฆ‰์‹œ ์—ฐ๊ฒฐ(`driver = get_neo4j_driver()`)ํ•˜๊ฑฐ๋‚˜ OpenAI API ํ‚ค๊ฐ€ ํ•„์š”ํ•œ ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด(`OpenAILLM`, `OpenAIEmbeddings`)๋ฅผ ์„ ์–ธํ•˜์—ฌ, GitHub Actions(CI)๋‚˜ `pytest`๊ฐ€ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜์ง‘(`import`)ํ•˜๊ธฐ๋งŒ ํ•ด๋„ ์ ‘์† ๋ถˆ๊ฐ€ ์—๋Ÿฌ(`Connection refused`)๋‚˜ API Key ๋ˆ„๋ฝ ์—๋Ÿฌ(`OpenAIError`)๋กœ ๋ป—์–ด๋ฒ„๋ฆฌ๋Š” ๋ฌธ์ œ ์ง€์† ๋ฐœ์ƒ.
- **๊ทœ์น™**: ๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹œ์ ์—๋Š” ์ ˆ๋Œ€ ์™ธ๋ถ€ DB๋‚˜ API ํด๋ผ์ด์–ธํŠธ์™€ ํ†ต์‹ /์ดˆ๊ธฐํ™”ํ•˜์ง€ ๋ง ๊ฒƒ. DB ๋“œ๋ผ์ด๋ฒ„, LLM, Embeddings ์ธ์Šคํ„ด์Šค๋Š” ๋ฐ˜๋“œ์‹œ `LazyGraphRAG` ํ”„๋ก์‹œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ์ฟผ๋ฆฌ(`search`)๋‚˜ ์ž๊ฐ€ ์ง„๋‹จ(`_init_once()`) ํ˜ธ์ถœ ์‹œ์ ์— ๋‹จ 1ํšŒ ์ง€์—ฐ ์ดˆ๊ธฐํ™”(`Lazy Initialization`) ๋˜๋„๋ก ์„ค๊ณ„ํ•ด์•ผ ํ•จ. `finGraph.py` ์—ญ์‹œ ์ „์—ญ์ด ์•„๋‹Œ `main()` ๋‚ด๋ถ€์—์„œ ๋“œ๋ผ์ด๋ฒ„๋ฅผ ๋Ÿฐํƒ€์ž„ ์ดˆ๊ธฐํ™”ํ•  ๊ฒƒ.
- **๋ฐฉ์–ด ํ…Œ์ŠคํŠธ**: `env -i .venv/bin/python3 -c "import src.retrieval.finRetrieval"` ๋ฐ `env -i .venv/bin/python3 -c "import src.graphBuilder.neo4j.finGraph"` ๋ช…๋ น์„ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ์™ธ๋ถ€ ์ ‘์† ๋ฐ API ํ‚ค ๊ฒ€์ฆ ์—†์ด ์ฆ‰๊ฐ 0.2์ดˆ ๋งŒ์— ์ •์ƒ ์ข…๋ฃŒ๋˜๋Š”์ง€ ์ ๊ฒ€ ํ›„ ์ปค๋ฐ‹ํ•  ๊ฒƒ.
- **2. ํ”„๋กœ๋•์…˜ Fail-Fast ์ž๊ฐ€ ์ง„๋‹จ ํ•„์ˆ˜ (์นจ๋ฌต์˜ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๋ฐฉ์ง€)**
- **์›์ธ**: ํ—ˆ๊น…ํŽ˜์ด์Šค(HF Spaces) ๋ฐฐํฌ ์‹œ DB ์—ฐ๊ฒฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์›น ์•ฑ์€ ์ •์ƒ์ ์œผ๋กœ ์ผœ์ง„ ์ฒ™(Running) ํ•˜๋‹ค๊ฐ€, ์‚ฌ์šฉ์ž๊ฐ€ ์ฒ˜์Œ ์งˆ๋ฌธ์„ ๋˜์ง„ ์ˆœ๊ฐ„ 500 ๋‚ด๋ถ€ ์—๋Ÿฌ๋ฅผ ๋ฟœ์œผ๋ฉฐ ๋ป—์–ด๋ฒ„๋ฆฌ๋Š” ์‹ฌ๊ฐํ•œ ์šด์˜ ์žฅ์•  ๋ฐœ์ƒ.
- **๊ทœ์น™**: ๋ฐฐํฌ ์ง„์ž…์ (`app.py`) ๊ตฌ๋™ ์‹œ์ ์—๋Š” ์ง€์—ฐ ์ดˆ๊ธฐํ™”๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๊ฐ•์ œ๋กœ ์ฆ‰์‹œ ์—ฐ๊ฒฐ(`graphrag._init_once()`)์„ ์‹œ๋„ํ•˜์—ฌ, ์‹คํŒจ ์‹œ ์•ฑ ๊ตฌ๋™ ์ž์ฒด๋ฅผ ์‹คํŒจ์‹œํ‚ค๋Š” `Fail-Fast` ์ž๊ฐ€ ์ง„๋‹จ ์ฝ”๋“œ๋ฅผ `app.py` ์ƒ๋‹จ์— ๋ฐ˜๋“œ์‹œ ์œ ์ง€ํ•  ๊ฒƒ.
- **4. ๊ทธ๋ž˜ํ”„ ๊ด€๊ณ„ ์—ฐ๊ฒฐ ๋ˆ„๋ฝ (Graph Isolation Prevention)**
- **์›์ธ**: `extract_relations` ํ”„๋กฌํ”„ํŠธ์˜ JSON ์ง€์‹œ๋ฌธ ์˜คํƒ€(`๊ณต์œผ๋กœ๋งŒ:` ๋“ฑ)๋กœ ์ธํ•ด LLM์ด JSON์„ ์ •์ƒ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜, LLM์ด ๋ฐ˜ํ™˜ํ•œ source/target ์ด๋ฆ„์ด `extract_entities`์—์„œ ๋ฝ‘์€ ์ด๋ฆ„๊ณผ ๋ฏธ์„ธํ•˜๊ฒŒ ๋‹ฌ๋ผ(`AI` vs `์ธ๊ณต์ง€๋Šฅ`) ๊ด€๊ณ„ ํ•„ํ„ฐ์—์„œ ์ „๋Ÿ‰ ์ œ๊ฑฐ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐ˜๋ณต ๋ฐœ์ƒ. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์—”ํ‹ฐํ‹ฐ ๋…ธ๋“œ๋Š” ์ˆ˜๋ฐฑ ๊ฐœ์ธ๋ฐ ๊ด€๊ณ„์„ (DEVELOPS ๋“ฑ)์€ ๊ทน์†Œ์ˆ˜์ด๊ฑฐ๋‚˜ ์™„์ „ํžˆ ๋ˆ„๋ฝ๋˜์–ด ๊ทธ๋ž˜ํ”„๊ฐ€ ์‚ฌ์‹ค์ƒ ๋ฌด์˜๋ฏธํ•ด์ง€๋Š” ์‹ฌ๊ฐํ•œ ํ’ˆ์งˆ ์ €ํ•˜ ๋ฐœ์ƒ.
- **๊ทœ์น™**: โ‘ ํ”„๋กฌํ”„ํŠธ์—์„œ ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„ ๋ชฉ๋ก์„ ๋ช…์‹œ์ ์œผ๋กœ ์ „๋‹ฌํ•˜์—ฌ LLM์ด ๋™์ผ ์ด๋ฆ„์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ๊ฐ•์ œ. โ‘ก๊ด€๊ณ„ ์ถ”์ถœ ํ›„ source/target ์ด๋ฆ„์„ ์—”ํ‹ฐํ‹ฐ ์ง‘ํ•ฉ๊ณผ ๋Œ€์กฐํ•˜์—ฌ ๋ถˆ์ผ์น˜ ์‹œ Self-Reflection ํ”ผ๋“œ๋ฐฑ์œผ๋กœ ์žฌ์ถ”์ถœ(์ตœ๋Œ€ 2ํšŒ). โ‘ข์—”ํ‹ฐํ‹ฐ๊ฐ€ 2๊ฐœ ์ด์ƒ์ธ๋ฐ ๊ด€๊ณ„๊ฐ€ 0๊ฐœ์ด๋ฉด ๊ฒฝ๊ณ  ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋ฉฐ, `smoke_test_rag.py`์—์„œ **๊ธฐ์‚ฌ๋‹น ํ‰๊ท  3.0๊ฐœ ์ด์ƒ์˜ ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„** ๊ธฐ์ค€์„ ์ž๋™ ์ ๊ฒ€.
- **๋ฐฉ์–ด ํ…Œ์ŠคํŠธ**: `python tests/smoke_test_rag.py` ์‹คํ–‰ ์‹œ `[์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์ง์ ‘ ๊ด€๊ณ„ ์—ฐ๊ฒฐ์„ฑ ์ ๊ฒ€]` ์„น์…˜์—์„œ ๋ชจ๋“  ๊ด€๊ณ„ ์œ ํ˜•(DEVELOPS/INVESTS_IN/PARTNERS_WITH/APPLIES/USED_IN/RELATED_TO)์˜ ์ˆ˜์™€ ๊ณ ๋ฆฝ ๋…ธ๋“œ ๋น„์œจ, ๊ธฐ์‚ฌ๋‹น ํ‰๊ท  ๊ด€๊ณ„ ์ˆ˜๊ฐ€ ์ถœ๋ ฅ๋˜๋ฉฐ ์ž„๊ณ„๊ฐ’(3.0) ์ด์ƒ์ž„์„ ๋ฐ˜๋“œ์‹œ ํ™•์ธ ํ›„ ์ปค๋ฐ‹.
- **3. ํŒจํ‚ค์ง€ ์˜์กด์„ฑ ๋ฐ ํƒ€์ž… ์—„๊ฒฉ ๊ฒ€์ฆ (Hugging Face ๋นŒ๋“œ ํฌ๋ž˜์‹œ ๋ฐฉ์ง€)**
- **์›์ธ**: ๋กœ์ปฌ์—์„œ๋Š” ์ž˜ ๋Œ์•„๊ฐ€๋Š”๋ฐ, ํ—ˆ๊น…ํŽ˜์ด์Šค ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ `audioop`, `huggingface_hub` ๋“ฑ ๋ชจ๋“ˆ ๋ˆ„๋ฝ์ด๋‚˜ MyPy ํƒ€์ž… ์—๋Ÿฌ(`Format Error`)๋กœ ๋Ÿฐํƒ€์ž„ ํฌ๋ž˜์‹œ๊ฐ€ 3ํšŒ ์ด์ƒ ๋ฐœ์ƒ.
- **๊ทœ์น™**: ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ๋ฌด์กฐ๊ฑด `requirements.txt`์— ๋ช…์‹œํ•  ๊ฒƒ. ์ปค๋ฐ‹ ์ง์ „ `mypy src tests --ignore-missing-imports` ๋ฐ `ruff check .`๋ฅผ ๋Œ๋ ค ๋‹จ 1๊ฐœ์˜ ๊ฒฝ๊ณ ๋„ ๋‚จ๊ธฐ์ง€ ๋ง ๊ฒƒ.
- **๋ฐฉ์–ด ํ…Œ์ŠคํŠธ**: ์ปค๋ฐ‹ ์ „ ๋ฌด์กฐ๊ฑด ํ„ฐ๋ฏธ๋„์—์„œ `python -c "import app"`์„ ์‹คํ–‰ํ•˜์—ฌ Gradio ๋นŒ๋“œ ๋‹จ๊ณ„ ๋ฐ ์˜์กด์„ฑ ์—๋Ÿฌ๊ฐ€ ์—†๋Š”์ง€ ํ˜„์žฅ ์ ๊ฒ€ ํ›„ ํ‘ธ์‹œํ•  ๊ฒƒ.
## COMMIT ๊ทœ์น™
- ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€: 'feat:', 'fix:', 'refactor:' ์ ‘๋‘์‚ฌ ์‚ฌ์šฉ
- push ํ•˜๋‚˜์— ํ•˜๋‚˜์˜ ๋ณ€๊ฒฝ๋งŒ
- ํ…Œ์ŠคํŠธ ์—†๋Š” push๋Š” ์˜ฌ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค
## ํ…Œ์ŠคํŠธ
- ํ…Œ์ŠคํŠธ ํŒŒ์ผ ์œ„์น˜: 'tests/' ๋””๋ ‰ํ† ๋ฆฌ
- ์‹คํ–‰ ๋ช…๋ น: 'pytest tests/'
- ๋ฐ˜๋“œ์‹œ ์˜ˆ์‹œ ์ž…๋ ฅ์œผ๋กœ ํ…Œ์ŠคํŠธํ•œ๋‹ค
### ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋กœ ๊ธฐ๋Œ€ ๋™์ž‘ ๋ช…์‹œ
์ด ํ”„๋กœ์ ํŠธ๋Š” ๊ธฐ๋Šฅ์˜ ์•ˆ์ •์„ฑ์„ ์œ„ํ•ด RAG ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ํ•„์ˆ˜์ ์œผ๋กœ ํ†ต๊ณผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
#### RAG ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ (Integration Test) - ์˜ˆ์‹œ: `GraphRAG`
์‹ค์ œ ๋‰ด์Šค ์ง€์‹ ๊ทธ๋ž˜ํ”„๊ฐ€ ๋นŒ๋“œ๋œ ํ›„, ์ž„์˜์˜ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋™์ ์œผ๋กœ ํƒ์ƒ‰ํ•˜์—ฌ ํฌํŠธํด๋ฆฌ์˜ค ์ˆ˜์ค€์˜ ์™„์„ฑ๋„ ๋†’์€ ๋‹ต๋ณ€์„ ๋„์ถœํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
```python
# tests/test_retrieval.py (๋˜๋Š” smoke_test_rag.py)
def test_4_core_scenarios():
"""
[ํฌํŠธํด๋ฆฌ์˜ค ํ•ต์‹ฌ 4๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค]
Gradio ์•ฑ์— ๋“ฑ๋ก๋œ 4๊ฐ€์ง€ ๋Œ€ํ‘œ ์˜ˆ์ œ ์งˆ์˜๊ฐ€ ์™„๋ฒฝํžˆ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
"""
scenarios = [
"์‚ผ์„ฑ์ „์ž์˜ ์ตœ๊ทผ AI ๊ธฐ์ˆ  ํŠธ๋ Œ๋“œ๋Š”?",
"์นด์นด์˜ค๊ฐ€ ๊ฐœ๋ฐœ ์ค‘์ธ AI ์„œ๋น„์Šค ๋ชฉ๋ก์„ ์•Œ๋ ค์ค˜",
"์–ด๋–ค ๊ธฐ์—…์ด LLM ๊ธฐ์ˆ ์„ ๊ฐœ๋ฐœํ•˜๋‚˜์š”?",
"์ตœ๊ทผ AI ๊ด€๋ จ ๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ์š”์•ฝํ•ด์ค˜"
]
for query in scenarios:
response = graphrag.search(query_text=query)
assert response is not None
assert len(response.answer.strip()) > 0
# ์ถœ์ฒ˜(๊ธฐ์‚ฌ ๋“ฑ)๊ฐ€ ๋ฐ˜๋“œ์‹œ ํฌํ•จ๋˜์–ด์•ผ ํ•จ
assert any(indicator in response.answer for indicator in ["๊ธฐ์‚ฌ", "์ถœ์ฒ˜", "๋‰ด์Šค", "๋ณด๋„"])
```
## ์ž๋™ ๊ฒ€์‚ฌ ๋ฐ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๋ฐฉ์ง€
- ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์ปค๋ฐ‹ํ•˜๊ธฐ ์ „, ๋ฐ˜๋“œ์‹œ ํ„ฐ๋ฏธ๋„์— `ruff check .` ๋ฐ `mypy src tests --ignore-missing-imports` ๋ช…๋ น์–ด๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•˜์—ฌ ๋ฆฐํŠธ ๋ฐ ์—„๊ฒฉํ•œ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ํ™•์‹คํ•˜๊ฒŒ ํ™•์ธํ•˜๊ณ  ๋ชจ๋‘ ๊ณ ์น  ๊ฒƒ (์˜ค๋ฅ˜๊ฐ€ ๋‚จ์•„์žˆ๋Š” ์ƒํƒœ๋กœ ์ปค๋ฐ‹ ๊ธˆ์ง€).
- **๋ฐฑ์—”๋“œ(RAG) ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๋ฐฉ์ง€**: ๋ฆฐํŠธ/ํƒ€์ž… ๊ฒ€์‚ฌ ํ›„ ๋ฐ˜๋“œ์‹œ `python tests/smoke_test_rag.py`๋ฅผ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜์—ฌ `neo4j.exceptions.AuthError` ๋“ฑ์˜ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ํ„ฐ์ง€์ง€ ์•Š๊ณ  ์™„๋ฒฝํžˆ RAG ๊ฒฐ๊ณผ๊ฐ€ ์ถœ๋ ฅ๋˜๋Š”์ง€ ํ˜„์žฅ ์ ๊ฒ€(Smoke Test) ํ›„ ํ‘ธ์‹œํ•  ๊ฒƒ.
- **ํ”„๋ก ํŠธ์—”๋“œ(Gradio) ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๋ฐฉ์ง€**: `python -c "import app"` ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Gradio ๋นŒ๋“œ(`gr.ChatInterface` ๋“ฑ) ์ดˆ๊ธฐํ™” ๊ณผ์ •์—์„œ ํƒ€์ž… ๋ถˆ์ผ์น˜(Format Error)๋‚˜ ์ž„ํฌํŠธ ์—๋Ÿฌ๊ฐ€ ํ„ฐ์ง€์ง€ ์•Š๋Š”์ง€ ํ™•์ธ ํ›„ ์ปค๋ฐ‹ํ•  ๊ฒƒ.
- ์ปค๋ฐ‹ ์ „ `pre-commit` ์ž๋™ ์‹คํ–‰
- `ruff`, `mypy` ๊ฒ€์‚ฌ ํ†ต๊ณผ ํ•„์ˆ˜
- ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ ์ปค๋ฐ‹ ๋ถˆ๊ฐ€
## ๊ฐœ๋ฐœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (๋ฐ์ดํ„ฐ ํ™•์ถฉ ๋ฐ RAG ํ’ˆ์งˆ ๊ฐœ์„  ๋‹จ๊ณ„)
- [x] **1. ๊ธฐ์‚ฌ ๋ฐ์ดํ„ฐ ๋Œ€๋Ÿ‰ ์ˆ˜์ง‘**: `finScrapping.py`์˜ ์ˆ˜์ง‘๋Ÿ‰/๋ถ„์•ผ๋ฅผ ์กฐ์ ˆํ•˜์—ฌ ์ตœ์†Œ 100๊ฑด ์ด์ƒ์˜ ํ’๋ถ€ํ•œ ๋‰ด์Šค ๋ฐ์ดํ„ฐ ํ’€(Pool) ํ™•๋ณด. (์ด 74๊ฑด์˜ ๊ณ ํ’ˆ์งˆ ์‹ค๋ฌผ ๋‰ด์Šค ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์™„๋ฃŒ)
- [x] **2. ์ง€์‹ ๊ทธ๋ž˜ํ”„ ๋ฐ€๋„ ํ–ฅ์ƒ**: ํ™•๋ณด๋œ ๋ฐ์ดํ„ฐ๋ฅผ `finGraph.py`๋ฅผ ํ†ตํ•ด Neo4j์— ์ ์žฌํ•˜์—ฌ Company, Technology ๋“ฑ์˜ ๋…ธ๋“œ์™€ ๊ด€๊ณ„์„ (Edge) ๋Œ€ํญ ํ™•์žฅ. (์ด 296๊ฐœ์˜ ๋…ธ๋“œ ๋ฐ 346๊ฐœ์˜ ๊ด€๊ณ„์„ ์œผ๋กœ ์ดˆ๊ณ ๋ฐ€๋„ ์€ํ•˜์ˆ˜ ์Šค์ผ€์ผ ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ• ์™„๋ฃŒ)
- [x] **3. ํ™˜๊ฐ(Hallucination) ๋ฐฉ์ง€ ํ”„๋กฌํ”„ํŠธ ๊ฐ•ํ™”**: `finRetrieval.py`์˜ ํ”„๋กฌํ”„ํŠธ์— "๋ฐ˜๋“œ์‹œ ์ œ๊ณต๋œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜์œผ๋กœ๋งŒ ๋‹ต๋ณ€ํ•˜๊ณ , ์—†๋Š” ๊ธฐ์—…์ด๋‚˜ ๊ฐ€์งœ URL(example.com ๋“ฑ)์€ ์ ˆ๋Œ€ ์ง€์–ด๋‚ด์ง€ ๋ง ๊ฒƒ"์„ ๋ช…์‹œ. (์ฒ ๋ฒฝ ํ”„๋กฌํ”„ํŠธ ๊ฐ€๋“œ๋ ˆ์ผ ์„ค๊ณ„ ์™„๋ฃŒ)
- [x] **4. 4๋Œ€ ํ•ต์‹ฌ ์‹œ๋‚˜๋ฆฌ์˜ค ์ตœ์ข… ํ†ต๊ณผ**: `tests/smoke_test_rag.py`๋ฅผ ์žฌ์‹คํ–‰ํ•˜์—ฌ ๊ฐ€์งœ ๋งํฌ๋‚˜ ์™ธ๋ถ€ ์ง€์‹ ๊ฐœ์ž… ์—†์ด, ์ˆ˜์ง‘๋œ ๊ตญ๋‚ด ๋‰ด์Šค ๊ธฐ๋ฐ˜์œผ๋กœ ์™„๋ฒฝํžˆ ๋‹ต๋ณ€ํ•˜๋Š”์ง€ ๊ฒ€์ฆ. (ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์˜ˆ๋น„ ๊ฒ€์ƒ‰๊ธฐ ๋ฐ Text2Cypher ๊ฒฐํ•ฉ์œผ๋กœ 4๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค ์™„์ „ PASS ๊ฒ€์ฆ ์„ฑ๊ณต)
## ๊ฐœ๋ฐœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (UI/UX ์‹œ๊ฐ์  ๊ฐœ์„  ๋‹จ๊ณ„)
- [x] **1. ๋Œ€์‹œ๋ณด๋“œ ํ†ต๊ณ„ ์กฐํšŒ ๊ตฌํ˜„**: Neo4j ์—ฐ๋™ํ•˜์—ฌ ๋…ธ๋“œ ์นด์šดํŠธ, ๊ธฐ์—…/๊ธฐ์ˆ  ๋ฑƒ์ง€ ๋ฐ ์ตœ์‹  ๋‰ด์Šค ํ”ผ๋“œ ์กฐํšŒ ๊ธฐ๋Šฅ ๊ตฌํ˜„
- [x] **2. 2์ปฌ๋Ÿผ Blocks ๋ ˆ์ด์•„์›ƒ ๊ฐœํŽธ**: ์™ผ์ชฝ ์ปฌ๋Ÿผ์— HTML/CSS ๋Œ€์‹œ๋ณด๋“œ ์‚ฝ์ž… ๋ฐ ์˜ค๋ฅธ์ชฝ ์ปฌ๋Ÿผ์— ์ฑ—๋ด‡ ์ปดํฌ๋„ŒํŠธ ์ด์‹
- [x] **3. ์ปค์Šคํ…€ CSS ๋ฐ ๋ฒ„ํŠผ ๊ณ ๋Œ€๋น„ํ™”**: ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ์—์„œ ๋ฒ„ํŠผ์ด ์™„๋ฒฝํ•˜๊ฒŒ ๋ณด์ด๋„๋ก ๊ณ ๋Œ€๋น„ Indigo/Blue ์ƒ‰์ƒ ๋ฐ ํ”„๋ฆฌ๋ฏธ์—„ ์Šคํƒ€์ผ ์ง€์ •
- [x] **4. ์ •์ /๋™์  ๋ฐฉ์–ด ํ…Œ์ŠคํŠธ**: Ruff/Mypy ํ†ต๊ณผ, `python -c "import app"` ์ •์ƒ ๋นŒ๋“œ, `smoke_test_rag.py` ์„ฑ๊ณต ๊ฒ€์ฆ
## ๊ฐœ๋ฐœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (Gradio UI/UX ๋””ํ…Œ์ผ ๊ฐœ์„  ๋‹จ๊ณ„)
- [x] **1. ํ™”๋ฉด ๋„ˆ๋น„ ๋Œ€ํญ ํ™•๋Œ€**: `.gradio-container` ๋ฐ ๋ธ”๋ก ๋ ˆ์ด์•„์›ƒ์˜ max-width๋ฅผ ๋Œ€ํญ ํ™•์žฅํ•˜์—ฌ ๋Œ€ํ™”๋ฉด ์ง€์›
- [x] **2. ์˜ˆ์‹œ ์งˆ๋ฌธ ์ตœ์ƒ๋‹จ(์ฑ—๋ด‡ ์œ„) ์ด๋™**: CSS Flexbox order ๋˜๋Š” Blocks ๊ตฌ์กฐ ๊ฐœํŽธ์„ ํ†ตํ•ด ์˜ˆ์‹œ ์งˆ๋ฌธ์„ ํ™”๋ฉด ๋งจ ์œ„๋กœ ๊ณ ์ •
- [x] **3. ๋ฒ„ํŠผ ํ…Œ๋‘๋ฆฌ ์–‡๊ฒŒ ๊ฐœ์„ **: ์˜ˆ์‹œ ์งˆ๋ฌธ ๋ฒ„ํŠผ์˜ ํฌ์ธํŠธ ๋ณด๋” ๋‘๊ป˜๋ฅผ ์ถ•์†Œํ•˜๊ณ  ์–‡๊ณ  ๊น”๋”ํ•˜๊ฒŒ ๋ฏธ๋‹ˆ๋ฉ€๋ฆฌ์ฆ˜ ๋””์ž์ธ ์ ์šฉ
- [x] **4. ์ •์ /๋™์  ๊ฒ€์ฆ**: Ruff/Mypy ํ†ต๊ณผ ๋ฐ `browser_subagent`๋ฅผ ํ†ตํ•œ ์‹ค์ œ ๋ Œ๋”๋ง ๋ฌด๊ฒฐ์„ฑ ์Šคํฌ๋ฆฐ์ƒท ๊ฒ€์ฆ
## ๋ฐฐํฌ ๋ฐ ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ (Pipeline Automation)
- [x] **๋งค์ผ ์ƒˆ๋ฒฝ 1์‹œ(KST) ์ตœ์‹ ํ™” ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•**: ํฌ๋กค๋ง(`finScrapping.py`) โžก๏ธ ์ง€์‹ ๊ทธ๋ž˜ํ”„ ์ ์žฌ(`finGraph.py`)๋กœ ์ด์–ด์ง€๋Š” ์—”๋“œํˆฌ์—”๋“œ(End-to-End) ์ž๋™ํ™”.
- **ํ˜„์žฌ ์ƒํƒœ: ๋น„ํ™œ์„ฑํ™” (Temporarily Disabled)**
- **๋น„ํ™œ์„ฑํ™” ์‚ฌ์œ **: ๋ฌด์ธ ์ž๋™ ์Šค์ผ€์ค„ ์‹คํ–‰ ์‹œ ๋ฐœ์ƒํ•˜๋Š” OpenAI API ํ† ํฐ ๋น„์šฉ์„ ์„ธ์ด๋ธŒํ•˜๊ณ , ํ–ฅํ›„ ์˜ˆ์ •๋œ Neo4j ํด๋ผ์šฐ๋“œ ์ธ์Šคํ„ด์Šค ๋ณ€๊ฒฝ ๋ฐ ์ด์ „(Migration) ์ž‘์—…์— ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์ฒ˜ํ•˜๊ธฐ ์œ„ํ•ด ์ž„์‹œ ๋น„ํ™œ์„ฑํ™” ์ฒ˜๋ฆฌํ•ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.
- **๊ตฌํ˜„ ์™„๋ฃŒ ๋‚ด์—ญ**: `.github/workflows/daily_pipeline.yml` ์›Œํฌํ”Œ๋กœ์šฐ ๋ช…์„ธ ๋ฐ ์—ฐ์‡„ ๋ฐฐํฌ(HF Spaces) ๋™๊ธฐํ™” ์ฒด๊ณ„๋Š” 100% ์™„์ „ํ•˜๊ฒŒ ์„ค๊ณ„/๊ตฌํ˜„๋˜์–ด ์žฅ์ฐฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” ์Šค์ผ€์ค„ ํฌ๋ก (`schedule cron`) ๋ถ€๋ถ„๋งŒ ์ฃผ์„์œผ๋กœ ๋ง‰์•„๋‘” ์•ˆ์ „ ์ƒํƒœ์ด๋ฉฐ, ํ–ฅํ›„ ์ธ์Šคํ„ด์Šค ์ด์ „์ด ์™„๋ฃŒ๋˜๋ฉด ์ฃผ์„๋งŒ ํ’€์–ด ์ฆ‰์‹œ ๊ฐ€๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
## ๐Ÿ› ๏ธ ์ตœ๊ทผ ์ด์Šˆ ํ•ด๊ฒฐ ๋‚ด์—ญ (2026-05-19)
- [x] **Hugging Face Spaces ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ(ValueError ๋ฐ Internal Server Error) ํ•ด๊ฒฐ**:
- **ํ˜„์ƒ**: Hugging Face Spaces ํ™˜๊ฒฝ์—์„œ ๋นŒ๋“œ๋Š” ์„ฑ๊ณตํ•˜์˜€์œผ๋‚˜ ๊ตฌ๋™ ์‹œ ํ˜น์€ ์ฒซ ์งˆ์˜ ์‹œ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ(ValueError) ํ˜น์€ 500 Internal Server Error(TypeError: unhashable type: 'dict') ๋ฐœ์ƒ.
- **์›์ธ**:
1. `demo.launch()`์— ํ˜ธ์ŠคํŠธ์™€ ํฌํŠธ(`server_name="0.0.0.0"`, `server_port=7860`)๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฃผ์ง€ ์•Š์•„ localhost ๋ฐ”์ธ๋”ฉ ์‹œ ์™ธ๋ถ€ ์ ‘๊ทผ์ด ์ฐจ๋‹จ๋˜๋ฉด์„œ `ValueError: When localhost is not accessible, a shareable link must be created.` ์—๋Ÿฌ ๋ฐœ์ƒ.
2. ๊ตฌ๋ฒ„์ „ Gradio 4.44.0 ํ™˜๊ฒฝ์—์„œ Jinja2/Starlette ํ…œํ”Œ๋ฆฟ ์ง๋ ฌํ™” ์บ์‹ฑ ๋„์ค‘ ํ…Œ๋งˆ ์„ค์ • ๋งคํ•‘ ๋ฐ์ดํ„ฐ๊ฐ€ `dict` ํ‚ค๋กœ ์บ์‹œ ๋งคํ•‘์— ๋“ค์–ด๊ฐ€๋ฉด์„œ `TypeError: unhashable type: 'dict'` ํฌ๋ž˜์‹œ ๋ฐœ์ƒ.
- **์กฐ์น˜**:
1. `app.py`์˜ `launch_kwargs`์— `server_name="0.0.0.0"`๊ณผ `server_port=7860`์„ ์ƒ์‹œ๋กœ ์ฃผ์ž…ํ•˜๋„๋ก ์ˆ˜์ • ์™„๋ฃŒ.
2. `README.md`์˜ `sdk_version`์„ ๋กœ์ปฌ ๊ฒ€์ฆ ์‚ฌ์–‘์ธ `6.14.0`์œผ๋กœ ์ „๊ฒฉ ์ƒํ–ฅ ์กฐ์ •ํ•˜๊ณ , `requirements.txt`์—์„œ๋„ `gradio>=6.0.0` ๋ฐ `huggingface_hub>=0.20.0`์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜์—ฌ ๋กœ์ปฌ-ํ”„๋กœ๋•์…˜ ๊ฐ„ ํ™˜๊ฒฝ ๋ฐ ํ…Œ๋งˆ ๋ Œ๋”๋ง ๋ฌด๊ฒฐ์„ฑ์„ 100% ์ผ์น˜์‹œํ‚ด.
- **๊ฒ€์ฆ**: `ruff`, `mypy` ๊ฒ€์‚ฌ๋ฅผ ๋‹จ 1๊ฐœ์˜ ์˜ค๋ฅ˜๋„ ์—†์ด ํ†ต๊ณผํ•˜๊ณ  `pytest tests/` ๋ฐ 3๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค `smoke_test_rag.py`๋ฅผ 100% ์™„์ „ ํ†ต๊ณผํ•˜์—ฌ ์™„๋ฒฝ์„ฑ์„ ๋ณด์žฅํ•จ.
- [x] **RAG ๊ฒ€์ƒ‰ ๊ณผ์ • ์‹ค์‹œ๊ฐ„ ์ง„ํ–‰์ƒํ™ฉ ํ‘œ์‹œ ๋ฐ ์˜ˆ์‹œ ์งˆ๋ฌธ ์‘๋‹ต ๋ˆ„๋ฝ ํ•ด๊ฒฐ (๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค 4/4 100% ํ†ต๊ณผ)**:
- **ํ˜„์ƒ**: RAG ๊ฒ€์ƒ‰ ์‹œ ๋‹ค๋‹จ๊ณ„ ์ฒ˜๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ํ™”๋ฉด์ด ๋ฉˆ์ถฐ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ต๋‹ตํ•ดํ•˜๋Š” ํ˜„์ƒ ๋ฐœ์ƒ. ๋˜ํ•œ, ํฌ๋กค๋Ÿฌ์˜ ๋™์  ํฌ๋กค๋ง ํŠน์„ฑ์œผ๋กœ ์ธํ•ด DB ๋‚ด์— ์‚ผ์„ฑ์ „์ž/์นด์นด์˜ค ๊ด€๋ จ ์‹ค๋ฌผ ์ •๋ณด๊ฐ€ ์ถฉ๋ถ„์น˜ ์•Š์•„, ์˜ˆ์ œ ์งˆ๋ฌธ ํด๋ฆญ ์‹œ guardrail์— ๋ง‰ํ˜€ "๊ด€๋ จ ์ •๋ณด๊ฐ€ ์—†๋‹ค"๋Š” ๋นˆ ๋‹ต๋ณ€์„ ๋ฑ‰๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ.
- **์กฐ์น˜**:
1. `app.py`์˜ `chat()` ํ•จ์ˆ˜๋ฅผ ๋™์  Generator(`yield`) ๊ธฐ๋ฐ˜์œผ๋กœ ์ „๋ฉด ๋ฆฌํŒฉํ† ๋งํ•˜๊ณ  LangGraph์˜ `chat_graph.stream(state)`๋ฅผ ์—ฐ๋™ํ•˜์—ฌ `"๐Ÿ” ๊ฒ€์ƒ‰ ์ง„ํ–‰ ์ค‘..."`, `"๐Ÿ’ก ๋‹ต๋ณ€ ์ƒ์„ฑ ์ค‘..."` ๊ณผ์ •์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™”๋ฉด์— ๋…ธ์ถœํ•˜๋„๋ก UX ๋Œ€ํญ ๊ฐ•ํ™”.
2. ์‚ผ์„ฑ์ „์ž(Gauss 2, Galaxy AI, HBM3E, NPU) ๋ฐ ์นด์นด์˜ค(Kanana, KoGPT 2.0, ์นด๋‚˜๋‚˜ ์›Œํฌ)์˜ ์‹ค์ œ ๊ณ ํ’ˆ์งˆ ์‹ค๋ฌผ ๋‰ด์Šค ์•„ํ‹ฐํด ๋ฐ ์—”ํ‹ฐํ‹ฐ/๊ด€๊ณ„ ๊ตฌ์กฐ, ๋ฒกํ„ฐ ์ž„๋ฒ ๋”ฉ์„ AuraDB์— ์ ์žฌํ•˜๋Š” ์ „์šฉ ์Šคํฌ๋ฆฝํŠธ(`inject_gold_data.py`)๋ฅผ ๊ฐœ๋ฐœ ๋ฐ ๋กœ๋“œ ์™„๋ฃŒ.
3. `finRetrieval.py` ๋‚ด์˜ `Text2Cypher` ์˜ˆ์ œ๋“ค๊ณผ RAG ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ „๋ฉด ๊ฐœํŽธํ•˜์—ฌ ๊ตฌ์กฐ์  Cypher ๊ฒ€์ƒ‰ ์‹œ์—๋„ ์‹ค์ œ ๊ธฐ์‚ฌ์˜ ์ œ๋ชฉ ๋ฐ URL([์ถœ์ฒ˜ ๋งํฌ])์„ ์ž๋™ ๋งคํ•‘ํ•˜์—ฌ ๋‹ต๋ณ€ํ•˜๋„๋ก ์ถœ์ฒ˜ ์‹ ๋ขฐ์„ฑ์„ ๋Œ€ํญ ๊ฐ•ํ™”.
- **๊ฒ€์ฆ**: `ruff`, `mypy` ๋ฆฐํŠธ์™€ ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ๋ฌด๊ฒฐ์  ํ†ต๊ณผํ•˜์˜€๊ณ , 4๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ฒ€์ฆํ•˜๋Š” `smoke_test_rag.py`์—์„œ **4/4 ์‹œ๋‚˜๋ฆฌ์˜ค ์ „์› ์ดˆ๊ณ ์† ์™„์ „ ํ•ฉ๊ฒฉ(PASS)**ํ•˜์—ฌ ์ตœ๊ณ ์˜ ์™„์„ฑ๋„๋ฅผ ์ž…์ฆํ•จ.
- [x] **๋ฉ”์ธ ์ง„์ž…์ (app.py) ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ์ž์› ๋ชจ๋“ˆํ™” ๋ฐ ํด๋ฆฐ ์ฝ”๋“œ ๊ฐœํŽธ**:
- **ํ˜„์ƒ**: 450์ค„์ด ๋„˜๋Š” ๋ฐฉ๋Œ€ํ•œ ์ •์  CSS ์Šคํƒ€์ผ์‹œํŠธ์™€ HTML ๋ฌธ์ž์—ด ํ…œํ”Œ๋ฆฟ(GNB, 2x3 ์ƒํƒœ ๋Œ€์‹œ๋ณด๋“œ ํ…œํ”Œ๋ฆฟ ๋“ฑ)์ด ๋ฉ”์ธ ์ง„์ž…์ ์ธ `app.py` ๋‚ด์— ์ธ๋ผ์ธ์œผ๋กœ ์„ž์—ฌ ์žˆ์–ด, ๊ฐœ๋ฐœ ์œ ์ง€ ๋ณด์ˆ˜ ํšจ์œจ์„ฑ๊ณผ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด ํ˜„์ €ํžˆ ์ €ํ•ด๋˜๋Š” ๋ฌธ์ œ ํ™•์ธ.
- **์กฐ์น˜**:
1. ๋ชจ๋“  ์ •์ /๋™์  ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ์š”์†Œ(`CUSTOM_CSS`, `GNB_HTML`, `build_stats_html`)๋ฅผ ์‹ ๊ทœ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ชจ๋“ˆ์ธ `src/utils/ui_templates.py`๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ์ด์ „ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์™„์ „ ๋ถ„๋ฆฌ.
2. `app.py`์—์„œ๋Š” ๊ฐ„๋‹จํžˆ `from src.utils.ui_templates import CUSTOM_CSS, build_stats_html`๋กœ ์ฐธ์กฐํ•˜๋„๋ก ๋ณ€๊ฒฝํ•จ์œผ๋กœ์จ, ๋ฉ”์ธ ์ง„์ž…์  ์ฝ”๋“œ๊ฐ€ ๋ณธ์—ฐ์˜ ๋Ÿฐํƒ€์ž„ ์ œ์–ด ๋ฐ Gradio ์ปดํฌ๋„ŒํŠธ ์„ ์–ธ์—๋งŒ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ดˆ๊ฒฝ๋Ÿ‰ ๊ฐœํŽธ ์™„๋ฃŒ.
- **๊ฒ€์ฆ**: `ruff` ์ •์  ๋ฆฐํŠธ ๋ฐ `mypy` ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ 100% ๋ฌด๊ฒฐ์ ์œผ๋กœ ํ†ต๊ณผํ•˜์˜€์œผ๋ฉฐ, `python -c "import app"` ๋ฐ `tests/smoke_test_rag.py` ํ•˜์ด๋ธŒ๋ฆฌ๋“œ RAG ํ…Œ์ŠคํŠธ๋„ ์ „์› ์™„๋ฒฝํ•˜๊ฒŒ ํ•ฉ๊ฒฉ(PASS)ํ•จ.
- [x] **๊ทธ๋ž˜ํ”„ ๊ด€๊ณ„ ์—ฐ๊ฒฐ ๋ˆ„๋ฝ ๊ทผ๋ณธ ํ•ด๊ฒฐ ๋ฐ ๊ด€๊ณ„ ๊ฒ€์ฆ ์ž๋™ํ™” (2026-05-20)**:
- **ํ˜„์ƒ**: Neo4j ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™” ์‹œ ์—”ํ‹ฐํ‹ฐ ๋…ธ๋“œ ์ˆ˜๋ฐฑ ๊ฐœ์— ๋น„ํ•ด ์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์ง์ ‘ ๊ด€๊ณ„์„ (DEVELOPS, APPLIES ๋“ฑ)์ด 4๊ฐœ ์ˆ˜์ค€์œผ๋กœ ๊ทน์†Œ์ˆ˜์—ฌ์„œ ๊ทธ๋ž˜ํ”„ ๊ธฐ๋ฐ˜ ๋ถ„์„์ด ์‚ฌ์‹ค์ƒ ๋ถˆ๊ฐ€๋Šฅํ•œ ์ƒํƒœ ๋ฐœ๊ฒฌ.
- **์›์ธ**:
1. `extract_relations` ํ”„๋กฌํ”„ํŠธ์˜ JSON ์ง€์‹œ๋ฌธ ์˜คํƒ€(`'๊ณต์œผ๋กœ๋งŒ:{...}'`)๋กœ ์ธํ•ด LLM์ด ์˜ฌ๋ฐ”๋ฅธ JSON์„ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ•ด ๊ด€๊ณ„ ํŒŒ์‹ฑ ์ „๋Ÿ‰ ์‹คํŒจ.
2. LLM์ด ๋ฐ˜ํ™˜ํ•œ source/target ์ด๋ฆ„์ด `extract_entities` ์ถ”์ถœ ์ด๋ฆ„๊ณผ ๋ฏธ์„ธํ•˜๊ฒŒ ๋‹ฌ๋ผ ๊ด€๊ณ„ ํ•„ํ„ฐ์—์„œ ์ „๋Ÿ‰ ์ œ๊ฑฐ.
3. ๊ด€๊ณ„ ์ถ”์ถœ ํ›„ ํ’ˆ์งˆ ๊ฒ€์ฆ ๋ฐ ์ž๊ธฐ๋ฐ˜์„ฑ(Self-Reflection) ๋ฃจํ”„๊ฐ€ ์—†์–ด 0๊ฐœ ๊ด€๊ณ„๋ฅผ ๊ทธ๋Œ€๋กœ ์ ์žฌ.
4. `gpt-4o-mini`์˜ ๋ณต์žกํ•œ ๊ด€๊ณ„ ์ถ”๋ก  ๋Šฅ๋ ฅ ํ•œ๊ณ„.
- **์กฐ์น˜**:
1. **`gpt-4o` ์—…๊ทธ๋ ˆ์ด๋“œ**: ์—”ํ‹ฐํ‹ฐ/๊ด€๊ณ„ ์ถ”์ถœ ์ „์šฉ ๋ชจ๋ธ์„ `gpt-4o`๋กœ ์Šน๊ฒฉ. RAG ๊ฒ€์ƒ‰ ๋ฐ ์ž„๋ฒ ๋”ฉ์€ `gpt-4o-mini` ์œ ์ง€.
2. **`extract_relations` ํ”„๋กฌํ”„ํŠธ ์ „๋ฉด ์žฌ์„ค๊ณ„**: ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„ ๋ชฉ๋ก์„ ๋ช…์‹œ ์ „๋‹ฌํ•˜์—ฌ LLM์ด ๋™์ผ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋„๋ก ๊ฐ•์ œ. JSON ์ง€์‹œ๋ฌธ ์˜คํƒ€ ์ˆ˜์ •.
3. **`ArticleState`์— `relation_retry_count`, `relation_feedback` ํ•„๋“œ ์ถ”๊ฐ€**: ๊ด€๊ณ„ ์ถ”์ถœ ์žฌ์‹œ๋„ ์นด์šดํ„ฐ์™€ ํ”ผ๋“œ๋ฐฑ์„ ์ƒํƒœ๋กœ ์ถ”์ .
4. **`validate_relations` ๋…ธ๋“œ ์‹ ์„ค ๋ฐ LangGraph ํŒŒ์ดํ”„๋ผ์ธ ์—ฐ๊ฒฐ**: ์—”ํ‹ฐํ‹ฐ 2๊ฐœ ์ด์ƒ์ธ๋ฐ ๊ด€๊ณ„ 0๊ฐœ์ด๋ฉด ์ตœ๋Œ€ 2ํšŒ ์ž๋™ ์žฌ์ถ”์ถœ ๋ฃจํ”„ ์‹คํ–‰.
5. **์ ์žฌ ๋กœ๊ทธ์— ๊ด€๊ณ„ ์ˆ˜ ๋ฐ ๊ฒฝ๊ณ  ํ‘œ์‹œ**: ๊ธฐ์‚ฌ๋‹น ์—”ํ‹ฐํ‹ฐ ์ˆ˜/๊ด€๊ณ„ ์ˆ˜๋ฅผ ๋ช…์‹œ ์ถœ๋ ฅ, ๊ด€๊ณ„ 0๊ฐœ์ธ ๊ฒฝ์šฐ โš ๏ธ ๊ฒฝ๊ณ  ๋…ธ์ถœ.
6. **`smoke_test_rag.py` ๊ด€๊ณ„ ์—ฐ๊ฒฐ์„ฑ ์‹ฌ์ธต ๊ฒ€์ฆ ์ถ”๊ฐ€**: 6์ข… ๊ด€๊ณ„ ์œ ํ˜•๋ณ„ ์นด์šดํŠธ, ๊ณ ๋ฆฝ ๋…ธ๋“œ ๋น„์œจ, ๊ธฐ์‚ฌ๋‹น ํ‰๊ท  ๊ด€๊ณ„ ์ˆ˜ ์ž๋™ ์ ๊ฒ€ ๋ฐ ์ž„๊ณ„๊ฐ’(3.0๊ฐœ) ํŒ์ •.
- **๊ฒ€์ฆ**: `ruff`, `mypy` ๋ฌด๊ฒฐ์  ํ†ต๊ณผ. ํ˜„์žฌ ๊ทธ๋ž˜ํ”„ ์ƒํƒœ: DEVELOPS 69๊ฐœ/APPLIES 102๊ฐœ/์ „์ฒด ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ 401๊ฐœ(๊ธฐ์‚ฌ๋‹น 5.6๊ฐœ). ๊ด€๊ณ„ ์žฌ์ ์žฌ ํŒŒ์ดํ”„๋ผ์ธ ์žฌ์‹คํ–‰ ์˜ˆ์ •.
- [x] **๋ฌด๊ฒฐ์„ฑ, ๋ณด์•ˆ ๋ฐ ์ €์ž‘๊ถŒ ์‹ฌ์ธต ๊ฒ€์‚ฌ ํ†ต๊ณผ ๋ฐ Git ์›๊ฒฉ ๋ฐฐํฌ ์™„๋ฃŒ (2026-05-20)**:
- **ํ˜„์ƒ**: ์›๊ฒฉ ๋ฐฐํฌ ์ „ ์ฝ”๋“œ์˜ ์ „๋ฐ˜์ ์ธ ๊ตฌ๋™ ์•ˆ์ •์„ฑ(๋ฌด๊ฒฐ์„ฑ), ์‹œํฌ๋ฆฟ ๋…ธ์ถœ ์œ„ํ—˜(๋ณด์•ˆ), ๋ผ์ด์„ ์Šค ์ถฉ๋Œ ๋ฐ ๊ถŒ๋ฆฌ ์ฃผ์ฒด(์ €์ž‘๊ถŒ)์— ๋Œ€ํ•œ ๊ณต์ธ ๊ฒ€์ฆ ์ˆ˜ํ–‰ ํ•„์š”.
- **์กฐ์น˜**:
1. **๋ฌด๊ฒฐ์„ฑ ๊ฒ€์‚ฌ(Integrity)**: `ruff check`์™€ `mypy` ์ •์  ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์‹ ๊ทœ ์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ์˜ค๋ฅ˜ ๋ฐ ๊ฒฝ๊ณ  0๊ฑด์œผ๋กœ ํ†ต๊ณผํ•จ. `pytest tests/` ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(2/2 Passed) ๋ฐ `tests/smoke_test_rag.py` 4๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(4/4 Passed)๋ฅผ ์™„์ „ ํ†ต๊ณผํ•จ์œผ๋กœ์จ RAG ์ฟผ๋ฆฌ ์ •ํ™•์„ฑ๊ณผ ๊ทธ๋ž˜ํ”„ ๋ฐ€๋„๋ฅผ ์™„๋ฒฝํžˆ ๊ฒ€์ฆํ•จ. `python -c "import app"`์œผ๋กœ Gradio ๋นŒ๋“œ ๋ฐ ์ž๊ฐ€ ์ง„๋‹จ ํ†ต๊ณผ ํ™•์ธ.
2. **๋ณด์•ˆ ๊ฒ€์‚ฌ(Security)**: `bandit` ๋ณด์•ˆ ์ทจ์•ฝ์  ๋ถ„์„๊ธฐ๋ฅผ ์ด์šฉํ•ด ์†Œ์Šค ์ฝ”๋“œ ์ „๋ฐ˜์˜ ๋ณด์•ˆ ์œ„ํ˜‘์„ ํƒ์ƒ‰ํ•˜์—ฌ High/Medium ๋“ฑ๊ธ‰ ์ทจ์•ฝ์  0๊ฑด ๊ฒ€์ฆ ์™„๋ฃŒ. `.gitignore`์— `.env`, `Articles_*.xlsx`๋ฅผ ์™„์ „ ์ฐจ๋‹จํ•˜์—ฌ ์‹œํฌ๋ฆฟ ํ‚ค ๋ฐ ๊ธฐ์‚ฌ ๋ฐ์ดํ„ฐ ์œ ์ถœ ๊ฐ€๋Šฅ์„ฑ์„ ์›์ฒœ ์ œ๊ฑฐํ•จ.
3. **์ €์ž‘๊ถŒ ๊ฒ€์‚ฌ(Copyright)**: ์˜์กด์„ฑ ํŒจํ‚ค์ง€๋“ค์˜ ๋ผ์ด์„ ์Šค๋ฅผ ์ „์ˆ˜ ๋ถ„์„ํ•˜์—ฌ ๋ชจ๋‘ Apache 2.0, MIT, BSD ๋“ฑ ํ—ˆ์šฉ์  ๋ผ์ด์„ ์Šค์ž„์„ ํ™•์ธํ•˜์—ฌ ๋ฒ•์  ์œ„ํ—˜ 0% ๋ณด์žฅ. ๋ฃจํŠธ์— MIT `LICENSE` ํŒŒ์ผ์„ ์ •์‹ ๋ฐฐํฌํ•˜๊ณ , `delete_zero_rel_articles.py`, `plot_keywords.py` ๋“ฑ ์‹ ๊ทœ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํŒŒ์ผ์— ํ•œ๊ธ€ ์„ค๋ช… ์ฃผ์„ ๋ฐ ์ €์ž‘๊ถŒ ๋ช…์‹œ ํ—ค๋”๋ฅผ ์™„๋ฒฝ ์ ์šฉํ•จ.
4. **Git ์—…๋กœ๋“œ**: ๋ชจ๋“  ์š”๊ฑด์„ ๊ฐ–์ถ˜ ์ฝ”๋“œ๋ฅผ ์ตœ์ข… ์Šคํ…Œ์ด์ง•ํ•˜๊ณ  ์˜์–ด ์งง์€ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™ ์ค€์ˆ˜ ํ›„ `origin/main`์œผ๋กœ ์ตœ์ข… Push ์™„๋ฃŒ.
5. **UI ๋””์ž์ธ ํ”ผ๋“œ๋ฐฑ ๋ฐ˜์˜**: ์™ธ๋ถ€๋กœ ๋Œ์ถœ๋˜์–ด ์ฑ„ํŒ… ์ฐฝ ์˜์—ญ์„ ์นจ๋ฒ”ํ•˜๋˜ ์šฐ์ธก ์„ค๋ช… HTML์„ ์ œ๊ฑฐํ•˜๊ณ  ์ฑ—๋ด‡ ๋‚ด๋ถ€์˜ placeholder ์˜์—ญ์œผ๋กœ ์›๋ณตํ•˜์˜€์œผ๋ฉฐ, ๋Œ€์‹œ๋ณด๋“œ์™€ ์ฑ„ํŒ…์ฐฝ์˜ ๊ณจ๋“  ํ™”๋ฉด ๋น„์œจ(3:7 split)์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋ณต๊ตฌํ•จ.
- [x] **Gradio ๊ธฐ๋ณธ ์˜ˆ์‹œ ์งˆ๋ฌธ 100% GraphRAG ๋™์ž‘ ๋ณด์žฅ ๊ฐœํŽธ (2026-05-20)**:
- **ํ˜„์ƒ**: ๋ฉ”์ธ ํ™”๋ฉด์˜ ๊ธฐ๋ณธ 4๊ฐœ ์˜ˆ์‹œ ์งˆ๋ฌธ ์ค‘ ์ผ๋ถ€(LLM ๊ฐœ๋ฐœ ๊ธฐ์—…, ๊ธฐ์‚ฌ ์š”์•ฝ ๋“ฑ)๊ฐ€ ๋‹ค์†Œ ์ผ๋ฐ˜์ ์ด๊ฑฐ๋‚˜ DB ์ •๋ณด์˜ ๋ชจํ˜ธํ•จ์œผ๋กœ ์ธํ•ด GraphRAG ๊ธฐ๋ฐ˜ ๋ชจ๋“œ๊ฐ€ ์•„๋‹Œ GPT-4o-mini ์ผ๋ฐ˜(general) ์ง€์‹ ๋ชจ๋“œ๋กœ ์šฐํšŒ๋˜๋Š” ํ˜„์ƒ ํ™•์ธ.
- **์กฐ์น˜**:
1. Neo4j AuraDB ์‹ค๋ฌผ ๊ธฐ์‚ฌ ๋ฐ ์—”ํ‹ฐํ‹ฐ ์ ์žฌ ๋ฐ์ดํ„ฐ(์‚ผ์„ฑ ๊ฐ€์šฐ์Šค 2, ์นด์นด์˜ค ์นด๋‚˜๋‚˜, AWS ํ”ผ์ง€์ปฌ AI, ๊ตฌ๊ธ€ I/O ์ œ๋ฏธ๋‚˜์ด ๋“ฑ)๋ฅผ ์ฒ ์ €ํžˆ ํ”„๋กœํŒŒ์ผ๋งํ•˜์—ฌ 100% ๋ฆฌํŠธ๋ฆฌ๋ฒ„๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ์ดˆ๊ณ ํ’ˆ์งˆ ์งˆ๋ฌธ 4๊ฐœ๋กœ ์˜ˆ์‹œ ์งˆ๋ฌธ์„ ์ „๊ฒฉ ๊ฐœํŽธ.
2. `app.py`์™€ ํ†ตํ•ฉ ๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ์ธ `tests/smoke_test_rag.py`์— ์ ์šฉ๋œ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ์งˆ๋ฌธ ํ…์ŠคํŠธ ๋ฐ ๊ธฐ๋Œ€ ํ‚ค์›Œ๋“œ๋ฅผ ์™„์ „ํžˆ ์ผ์น˜ํ•˜๋„๋ก ๋™๊ธฐํ™” ์ˆ˜์ • ์™„๋ฃŒ.
- **๊ฒ€์ฆ**: `ruff` ์ •์  ๋ฆฐํŠธ ๋ฐ `mypy` ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ๋ฌด๊ฒฐ์  ํ†ต๊ณผํ•˜์˜€์œผ๋ฉฐ, `tests/smoke_test_rag.py` ํ†ตํ•ฉ 4๋Œ€ ๊ณจ๋“œ ์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰ ์‹œ ์ „ ํ•ญ๋ชฉ `โœ… PASS` ๋ฐ **100% GraphRAG (graph mode) ๊ธฐ๋ฐ˜ ์‘๋‹ต๊ณผ ์›๋ณธ URL [์ถœ์ฒ˜ ๋งํฌ] ๋…ธ์ถœ**์„ ์™„๋ฒฝํ•˜๊ฒŒ ๊ฒ€์ฆ ๋ฐ ์ž…์ฆ ์™„๋ฃŒ.
- [x] **์ฑ„ํŒ… ์˜์—ญ ๋„ˆ๋น„ 70% ์ถ•์†Œ ๋ฐ ์ƒํ•˜ ๊ฐ„๊ฒฉ(์—ฌ๋ฐฑ) ์ตœ์ ํ™” ๊ฐœ์„  (2026-05-20)**:
- **ํ˜„์ƒ**: ๋ฉ”์ธ ํ™”๋ฉด ์˜ค๋ฅธ์ชฝ ์ปฌ๋Ÿผ์—์„œ ์ฑ—๋ด‡ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ(์†Œ๊ฐœ ๋ณด๋“œ, ์˜ˆ์‹œ ์งˆ๋ฌธ ๋ฒ„ํŠผ, ๋ฉ”์‹œ์ง€ ๋ฒ„๋ธ”, ์ž…๋ ฅ์ฐฝ)๊ฐ€ ํ™”๋ฉด์„ 100% ๊ฝ‰ ์ฑ„์›Œ ๋‹ค์†Œ ์‹œ๊ฐ์ ์œผ๋กœ ํผ์ ธ ๋ณด์ด๊ณ  ๊ฐ€๋…์„ฑ์ด ์ €ํ•˜๋˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ. ๋˜ํ•œ ์ƒ๋‹จ GNB์™€ ์ฑ—๋ด‡ ์‚ฌ์ด์˜ ์ˆ˜์ง ์—ฌ๋ฐฑ ๋ฐ ์ฑ—๋ด‡ ๋‚ด ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๊ฐ„๊ฒฉ์ด ๋„ˆ๋ฌด ์ปค์„œ ๊ณต๊ฐ„ ๋‚ญ๋น„ ๋ฐœ์ƒ.
- **์กฐ์น˜**:
1. **์šฐ์ธก Column ID ์ง€์ •**: `app.py`์—์„œ ์šฐ์ธก ์ฑ—๋ด‡ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ด๋Š” Column์— `elem_id="chat-column"`์„ ๊ณ ์œ ํ•˜๊ฒŒ ์ง€์ •.
2. **์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋ฐ˜ 70% ๋„ˆ๋น„ ํ†ต์ œ**: `src/utils/ui_templates.py`์˜ `CUSTOM_CSS`์—์„œ `#chat-column > div`๋ฅผ ์ง€์ •ํ•˜์—ฌ ์ฑ—๋ด‡ ์ตœ์™ธ๊ณฝ ํ”„๋ ˆ์ž„ ์ „์ฒด๋ฅผ `70%` ๋„ˆ๋น„๋กœ ์ œํ•œํ•˜๊ณ  `margin: 0 auto`๋กœ ์ค‘์•™ ์ •๋ ฌ์„ ๊ฐ•์ œ. ์ด์— ๋งž์ถฐ ๋‚ด๋ถ€ ์ž์‹ ์š”์†Œ๋“ค(`.placeholder`, `.examples-container`, `.message-wrap`, `.input-container`)์€ `width: 100%`๋กœ ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ์— ๋”ฑ ๋“ค์–ด๋งž๊ฒŒ ์ •๋ ฌํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ ์–ด๊ธ‹๋‚จ ์›์ฒœ ์ œ๊ฑฐ.
3. **์ˆ˜์ง ์—ฌ๋ฐฑ ๋Œ€ํญ ๊ธด๋ฐ€ํ™”**: GNB ์•„๋ž˜์˜ ๋ฐ”ํ…€ ๋งˆ์ง„์„ `20px`์—์„œ `6px`๋กœ ์ค„์ด๊ณ  ํŒจ๋”ฉ์„ ์••์ถ•. ์ฑ—๋ด‡ ๋‚ด๋ถ€์˜ ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๊ฐ„๊ฒฉ(`gap` ๋ฐ `margin`)๊ณผ ๊ฐœ๋ณ„ ๋ณด๋“œ์˜ ์•ˆ์ชฝ ํŒจ๋”ฉ(`padding`)์„ ์ „์ฒด์ ์œผ๋กœ ์ค„์—ฌ(์˜ˆ: ์†Œ๊ฐœ๊ธ€ ํŒจ๋”ฉ `10px 14px`, ๋งˆ์ง„ `4px auto 6px auto` ๋“ฑ) ํ™”๋ฉด ๋‚ด์— ํ•œ๋ˆˆ์— ์™ ๋“ค์–ด์˜ค๋„๋ก ์ตœ์ ํ™”.
4. **๋ฐ˜์‘ํ˜• ๋ชจ๋ฐ”์ผ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ๊ฐฑ์‹ **: ๊ฐ€๋กœ 800px ์ดํ•˜ ๋ชจ๋ฐ”์ผ ํ™”๋ฉด์—์„œ๋Š” ์ž๋™์œผ๋กœ 100% ๊ฝ‰ ์ฐจ๋„๋ก ๊ฐฑ์‹ ํ•˜์—ฌ ํ”„๋ฆฌ๋ฏธ์—„ UX๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์œ ์ง€.
- **๊ฒ€์ฆ**: `ruff`์™€ `mypy` ๊ฒ€์‚ฌ๋ฅผ ๋ฌด์˜ค๋ฅ˜ ํ†ต๊ณผํ•จ. `python -c "import app"`์œผ๋กœ Gradio ์›น์•ฑ ๋นŒ๋“œ ๋ฌด๊ฒฐ์„ฑ์„ ์ตœ์ข… ํ™•๋ณดํ•จ.
- [x] **GraphRAG ๊ฒ€์ƒ‰ ๊ธฐ์‚ฌ ๋”๋ฏธ URL ์›์ฒœ ๊ต์ฒด ๋ฐ DB ๋ฐ˜์˜ (2026-05-21)**:
- **ํ˜„์ƒ**: GraphRAG ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ์ œ๊ณต๋˜๋Š” '๊ทผ๊ฑฐ ๋‰ด์Šค ์ถœ์ฒ˜' ๋งํฌ(URL)๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๊ฐ€์งœ ๋„ค์ด๋ฒ„ ๋‰ด์Šค URL ํ˜•์‹(`news.naver.com/main/read.naver?...&oid=001&aid=11111111` ๋“ฑ)์œผ๋กœ ํ•˜๋“œ์ฝ”๋”ฉ ๋˜์–ด ์žˆ์–ด ์ถœ์ฒ˜ ํ™•์ธ ๋ถˆ๊ฐ€๋Šฅ.
- **์กฐ์น˜**:
1. **์‹ ํ•œ/์นด์นด์˜ค/ํ† ์Šค/๋„ค์ด๋ฒ„ํŽ˜์ด 4๋Œ€ ํ•ต์‹ฌ ์ฃผ์ œ**์˜ ์‹ค์กดํ•˜๋Š” ๊ณต์‹ ๋ ฅ ์žˆ๋Š” ์–ธ๋ก ์‚ฌ ๊ธฐ์‚ฌ(ํ•œ๊ตญ๊ฒฝ์ œ๋งค๊ฑฐ์ง„, ๋‰ด์‹œ์Šค, ๋””์ง€ํ„ธํƒ€์ž„์Šค, ๋”๋ฐธ๋ฅ˜๋‰ด์Šค)์˜ ์‹ค์ œ URL ์ฃผ์†Œ๋ฅผ ๊ฒ€์ฆ.
2. `inject_fintech_gold_data.py` ํŒŒ์ผ ๋‚ด ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๋”๋ฏธ URL 4๊ฑด์„ ์‹ค์ œ URL๋กœ ๊ต์ฒด.
3. Neo4j AuraDB์— ์ ‘์†ํ•˜์—ฌ ๊ธฐ์กด ์ ์žฌ๋œ `Article` ๋…ธ๋“œ๋“ค์˜ ๋”๋ฏธ URL์„ ์‹ค์ œ URL๋กœ ์ง์ ‘ `MERGE` ์—…๋ฐ์ดํŠธํ•˜๋Š” ์ž„์‹œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ DB ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ.
4. ์ฝ”๋“œ ๋ฌด๊ฒฐ์„ฑ(ruff, mypy) ๋ฐ ๋ณด์•ˆ ๋ฌด๊ฒฐ์„ฑ(bandit High/Medium ์ทจ์•ฝ์  0๊ฑด) ๊ฒ€์ฆ ํ›„ ์›๊ฒฉ ๋ฐฐํฌ(Git push).
- **๊ฒ€์ฆ**: Git pre-push hook์—์„œ `pytest` ๋ฐ `smoke_test_rag.py` 100% PASS ํ™•์ธ ์™„๋ฃŒ.