| ###### ์ฐธ๊ณ : 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 ํ์ธ ์๋ฃ. |
|
|