A newer version of the Gradio SDK is available: 6.15.2
μ°Έκ³ : 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 νΈλ¦¬κ±° λκΈ°ν)
λΆνμν μμ νμΌ, μμ λ°μ΄ν° νμΌ(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μ΄ λ§μ μ μ μ’ λ£λλμ§ μ κ² ν 컀λ°ν κ².
- μμΈ: λͺ¨λ μ μ λ²μ(Global Scope)μμ λ°μ΄ν°λ² μ΄μ€λ₯Ό μ¦μ μ°κ²°(
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μμ λ½μ μ΄λ¦κ³Ό λ―ΈμΈνκ² λ¬λΌ(AIvsμΈκ³΅μ§λ₯) κ΄κ³ νν°μμ μ λ μ κ±°λλ λ¬Έμ κ° λ°λ³΅ λ°μ. κ²°κ³Όμ μΌλ‘ μν°ν° λ Έλλ μλ°± κ°μΈλ° κ΄κ³μ (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
μ€μ λ΄μ€ μ§μ κ·Έλνκ° λΉλλ ν, μμμ μ΅μ λ°μ΄ν°λ₯Ό λμ μΌλ‘ νμνμ¬ ν¬νΈν΄λ¦¬μ€ μμ€μ μμ±λ λμ λ΅λ³μ λμΆνλμ§ κ²μ¦ν©λλ€.
# 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 νμ§ κ°μ λ¨κ³)
- 1. κΈ°μ¬ λ°μ΄ν° λλ μμ§:
finScrapping.pyμ μμ§λ/λΆμΌλ₯Ό μ‘°μ νμ¬ μ΅μ 100건 μ΄μμ νλΆν λ΄μ€ λ°μ΄ν° ν(Pool) ν보. (μ΄ 74건μ κ³ νμ§ μ€λ¬Ό λ΄μ€ λ°μ΄ν° μμ§ μλ£) - 2. μ§μ κ·Έλν λ°λ ν₯μ: ν보λ λ°μ΄ν°λ₯Ό
finGraph.pyλ₯Ό ν΅ν΄ Neo4jμ μ μ¬νμ¬ Company, Technology λ±μ λ Έλμ κ΄κ³μ (Edge) λν νμ₯. (μ΄ 296κ°μ λ Έλ λ° 346κ°μ κ΄κ³μ μΌλ‘ μ΄κ³ λ°λ μνμ μ€μΌμΌ κ·Έλν κ΅¬μΆ μλ£) - 3. νκ°(Hallucination) λ°©μ§ ν둬ννΈ κ°ν:
finRetrieval.pyμ ν둬ννΈμ "λ°λμ μ 곡λ κ²μ κ²°κ³Ό κΈ°λ°μΌλ‘λ§ λ΅λ³νκ³ , μλ κΈ°μ μ΄λ κ°μ§ URL(example.com λ±)μ μ λ μ§μ΄λ΄μ§ λ§ κ²"μ λͺ μ. (μ² λ²½ ν둬ννΈ κ°λλ μΌ μ€κ³ μλ£) - 4. 4λ ν΅μ¬ μλλ¦¬μ€ μ΅μ’
ν΅κ³Ό:
tests/smoke_test_rag.pyλ₯Ό μ¬μ€ννμ¬ κ°μ§ λ§ν¬λ μΈλΆ μ§μ κ°μ μμ΄, μμ§λ κ΅λ΄ λ΄μ€ κΈ°λ°μΌλ‘ μλ²½ν λ΅λ³νλμ§ κ²μ¦. (νμ΄λΈλ¦¬λ μλΉ κ²μκΈ° λ° Text2Cypher κ²°ν©μΌλ‘ 4λ 골λ μλλ¦¬μ€ μμ PASS κ²μ¦ μ±κ³΅)
κ°λ° 체ν¬λ¦¬μ€νΈ (UI/UX μκ°μ κ°μ λ¨κ³)
- 1. λμ보λ ν΅κ³ μ‘°ν ꡬν: Neo4j μ°λνμ¬ λ Έλ μΉ΄μ΄νΈ, κΈ°μ /κΈ°μ λ±μ§ λ° μ΅μ λ΄μ€ νΌλ μ‘°ν κΈ°λ₯ ꡬν
- 2. 2μ»¬λΌ Blocks λ μ΄μμ κ°νΈ: μΌμͺ½ 컬λΌμ HTML/CSS λμ보λ μ½μ λ° μ€λ₯Έμͺ½ 컬λΌμ μ±λ΄ μ»΄ν¬λνΈ μ΄μ
- 3. 컀μ€ν CSS λ° λ²νΌ κ³ λλΉν: ν°μ λ°°κ²½μμ λ²νΌμ΄ μλ²½νκ² λ³΄μ΄λλ‘ κ³ λλΉ Indigo/Blue μμ λ° ν리미μ μ€νμΌ μ§μ
- 4. μ μ /λμ λ°©μ΄ ν
μ€νΈ: Ruff/Mypy ν΅κ³Ό,
python -c "import app"μ μ λΉλ,smoke_test_rag.pyμ±κ³΅ κ²μ¦
κ°λ° 체ν¬λ¦¬μ€νΈ (Gradio UI/UX λν μΌ κ°μ λ¨κ³)
- 1. νλ©΄ λλΉ λν νλ:
.gradio-containerλ° λΈλ‘ λ μ΄μμμ max-widthλ₯Ό λν νμ₯νμ¬ λνλ©΄ μ§μ - 2. μμ μ§λ¬Έ μ΅μλ¨(μ±λ΄ μ) μ΄λ: CSS Flexbox order λλ Blocks ꡬ쑰 κ°νΈμ ν΅ν΄ μμ μ§λ¬Έμ νλ©΄ 맨 μλ‘ κ³ μ
- 3. λ²νΌ ν λ리 μκ² κ°μ : μμ μ§λ¬Έ λ²νΌμ ν¬μΈνΈ 보λ λκ»λ₯Ό μΆμνκ³ μκ³ κΉλνκ² λ―Έλλ©λ¦¬μ¦ λμμΈ μ μ©
- 4. μ μ /λμ κ²μ¦: Ruff/Mypy ν΅κ³Ό λ°
browser_subagentλ₯Ό ν΅ν μ€μ λ λλ§ λ¬΄κ²°μ± μ€ν¬λ¦°μ· κ²μ¦
λ°°ν¬ λ° μλν νμ΄νλΌμΈ (Pipeline Automation)
- λ§€μΌ μλ²½ 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)
Hugging Face Spaces λ°νμ μλ¬(ValueError λ° Internal Server Error) ν΄κ²°:
- νμ: Hugging Face Spaces νκ²½μμ λΉλλ μ±κ³΅νμμΌλ ꡬλ μ νΉμ 첫 μ§μ μ λ°νμ μλ¬(ValueError) νΉμ 500 Internal Server Error(TypeError: unhashable type: 'dict') λ°μ.
- μμΈ:
demo.launch()μ νΈμ€νΈμ ν¬νΈ(server_name="0.0.0.0",server_port=7860)λ₯Ό λͺ μμ μΌλ‘ μ£Όμ§ μμ localhost λ°μΈλ© μ μΈλΆ μ κ·Όμ΄ μ°¨λ¨λλ©΄μValueError: When localhost is not accessible, a shareable link must be created.μλ¬ λ°μ.- ꡬλ²μ Gradio 4.44.0 νκ²½μμ Jinja2/Starlette ν
νλ¦Ώ μ§λ ¬ν μΊμ± λμ€ ν
λ§ μ€μ λ§€ν λ°μ΄ν°κ°
dictν€λ‘ μΊμ λ§€νμ λ€μ΄κ°λ©΄μTypeError: unhashable type: 'dict'ν¬λμ λ°μ.
- μ‘°μΉ:
app.pyμlaunch_kwargsμserver_name="0.0.0.0"κ³Όserver_port=7860μ μμλ‘ μ£Όμ νλλ‘ μμ μλ£.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% μμ ν΅κ³Όνμ¬ μλ²½μ±μ 보μ₯ν¨.
RAG κ²μ κ³Όμ μ€μκ° μ§νμν© νμ λ° μμ μ§λ¬Έ μλ΅ λλ½ ν΄κ²° (골λ μλλ¦¬μ€ 4/4 100% ν΅κ³Ό):
- νμ: RAG κ²μ μ λ€λ¨κ³ μ²λ¦¬κ° λ°μνμ¬ νλ©΄μ΄ λ©μΆ° μ¬μ©μκ° λ΅λ΅ν΄νλ νμ λ°μ. λν, ν¬λ‘€λ¬μ λμ ν¬λ‘€λ§ νΉμ±μΌλ‘ μΈν΄ DB λ΄μ μΌμ±μ μ/μΉ΄μΉ΄μ€ κ΄λ ¨ μ€λ¬Ό μ λ³΄κ° μΆ©λΆμΉ μμ, μμ μ§λ¬Έ ν΄λ¦ μ guardrailμ λ§ν "κ΄λ ¨ μ λ³΄κ° μλ€"λ λΉ λ΅λ³μ λ±λ λ¬Έμ λ°μ.
- μ‘°μΉ:
app.pyμchat()ν¨μλ₯Ό λμ Generator(yield) κΈ°λ°μΌλ‘ μ λ©΄ 리ν©ν λ§νκ³ LangGraphμchat_graph.stream(state)λ₯Ό μ°λνμ¬"π κ²μ μ§ν μ€...","π‘ λ΅λ³ μμ± μ€..."κ³Όμ μ μ€μκ°μΌλ‘ νλ©΄μ λ ΈμΆνλλ‘ UX λν κ°ν.- μΌμ±μ μ(Gauss 2, Galaxy AI, HBM3E, NPU) λ° μΉ΄μΉ΄μ€(Kanana, KoGPT 2.0, μΉ΄λλ μν¬)μ μ€μ κ³ νμ§ μ€λ¬Ό λ΄μ€ μν°ν΄ λ° μν°ν°/κ΄κ³ ꡬ쑰, λ²‘ν° μλ² λ©μ AuraDBμ μ μ¬νλ μ μ© μ€ν¬λ¦½νΈ(
inject_gold_data.py)λ₯Ό κ°λ° λ° λ‘λ μλ£. finRetrieval.pyλ΄μText2Cypherμμ λ€κ³Ό RAG μμ€ν ν둬ννΈλ₯Ό μ λ©΄ κ°νΈνμ¬ κ΅¬μ‘°μ Cypher κ²μ μμλ μ€μ κΈ°μ¬μ μ λͺ© λ° URL([μΆμ² λ§ν¬])μ μλ λ§€ννμ¬ λ΅λ³νλλ‘ μΆμ² μ λ’°μ±μ λν κ°ν.
- κ²μ¦:
ruff,mypyλ¦°νΈμ νμ κ²μ¬λ₯Ό 무결μ ν΅κ³Όνμκ³ , 4λ 골λ μλ리μ€λ₯Ό κ²μ¦νλsmoke_test_rag.pyμμ **4/4 μλλ¦¬μ€ μ μ μ΄κ³ μ μμ ν©κ²©(PASS)**νμ¬ μ΅κ³ μ μμ±λλ₯Ό μ μ¦ν¨.
λ©μΈ μ§μ μ (app.py) νλ μ ν μ΄μ μμ λͺ¨λν λ° ν΄λ¦° μ½λ κ°νΈ:
- νμ: 450μ€μ΄ λλ λ°©λν μ μ CSS μ€νμΌμνΈμ HTML λ¬Έμμ΄ ν
νλ¦Ώ(GNB, 2x3 μν λμ보λ ν
νλ¦Ώ λ±)μ΄ λ©μΈ μ§μ
μ μΈ
app.pyλ΄μ μΈλΌμΈμΌλ‘ μμ¬ μμ΄, κ°λ° μ μ§ λ³΄μ ν¨μ¨μ±κ³Ό μ½λ κ°λ μ±μ΄ νμ ν μ ν΄λλ λ¬Έμ νμΈ. - μ‘°μΉ:
- λͺ¨λ μ μ /λμ νλ μ ν
μ΄μ
μμ(
CUSTOM_CSS,GNB_HTML,build_stats_html)λ₯Ό μ κ· μ νΈλ¦¬ν° λͺ¨λμΈsrc/utils/ui_templates.pyλ‘ μλ²½νκ² μ΄μ νμ¬ μ½λλ₯Ό 물리μ μΌλ‘ μμ λΆλ¦¬. 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)ν¨.
- νμ: 450μ€μ΄ λλ λ°©λν μ μ CSS μ€νμΌμνΈμ HTML λ¬Έμμ΄ ν
νλ¦Ώ(GNB, 2x3 μν λμ보λ ν
νλ¦Ώ λ±)μ΄ λ©μΈ μ§μ
μ μΈ
κ·Έλν κ΄κ³ μ°κ²° λλ½ κ·Όλ³Έ ν΄κ²° λ° κ΄κ³ κ²μ¦ μλν (2026-05-20):
- νμ: Neo4j κ·Έλν μκ°ν μ μν°ν° λ Έλ μλ°± κ°μ λΉν΄ μν°ν° κ° μ§μ κ΄κ³μ (DEVELOPS, APPLIES λ±)μ΄ 4κ° μμ€μΌλ‘ κ·Ήμμμ¬μ κ·Έλν κΈ°λ° λΆμμ΄ μ¬μ€μ λΆκ°λ₯ν μν λ°κ²¬.
- μμΈ:
extract_relationsν둬ννΈμ JSON μ§μλ¬Έ μ€ν('곡μΌλ‘λ§:{...}')λ‘ μΈν΄ LLMμ΄ μ¬λ°λ₯Έ JSONμ μμ±νμ§ λͺ»ν΄ κ΄κ³ νμ± μ λ μ€ν¨.- LLMμ΄ λ°νν source/target μ΄λ¦μ΄
extract_entitiesμΆμΆ μ΄λ¦κ³Ό λ―ΈμΈνκ² λ¬λΌ κ΄κ³ νν°μμ μ λ μ κ±°. - κ΄κ³ μΆμΆ ν νμ§ κ²μ¦ λ° μκΈ°λ°μ±(Self-Reflection) 루νκ° μμ΄ 0κ° κ΄κ³λ₯Ό κ·Έλλ‘ μ μ¬.
gpt-4o-miniμ 볡μ‘ν κ΄κ³ μΆλ‘ λ₯λ ₯ νκ³.
- μ‘°μΉ:
gpt-4oμ κ·Έλ μ΄λ: μν°ν°/κ΄κ³ μΆμΆ μ μ© λͺ¨λΈμgpt-4oλ‘ μΉκ²©. RAG κ²μ λ° μλ² λ©μgpt-4o-miniμ μ§.extract_relationsν둬ννΈ μ λ©΄ μ¬μ€κ³: μν°ν° μ΄λ¦ λͺ©λ‘μ λͺ μ μ λ¬νμ¬ LLMμ΄ λμΌ μ΄λ¦μ μ¬μ©νλλ‘ κ°μ . JSON μ§μλ¬Έ μ€ν μμ .ArticleStateμrelation_retry_count,relation_feedbackνλ μΆκ°: κ΄κ³ μΆμΆ μ¬μλ μΉ΄μ΄ν°μ νΌλλ°±μ μνλ‘ μΆμ .validate_relationsλ Έλ μ μ€ λ° LangGraph νμ΄νλΌμΈ μ°κ²°: μν°ν° 2κ° μ΄μμΈλ° κ΄κ³ 0κ°μ΄λ©΄ μ΅λ 2ν μλ μ¬μΆμΆ 루ν μ€ν.- μ μ¬ λ‘κ·Έμ κ΄κ³ μ λ° κ²½κ³ νμ: κΈ°μ¬λΉ μν°ν° μ/κ΄κ³ μλ₯Ό λͺ μ μΆλ ₯, κ΄κ³ 0κ°μΈ κ²½μ° β οΈ κ²½κ³ λ ΈμΆ.
smoke_test_rag.pyκ΄κ³ μ°κ²°μ± μ¬μΈ΅ κ²μ¦ μΆκ°: 6μ’ κ΄κ³ μ νλ³ μΉ΄μ΄νΈ, κ³ λ¦½ λ Έλ λΉμ¨, κΈ°μ¬λΉ νκ· κ΄κ³ μ μλ μ κ² λ° μκ³κ°(3.0κ°) νμ .
- κ²μ¦:
ruff,mypy무결μ ν΅κ³Ό. νμ¬ κ·Έλν μν: DEVELOPS 69κ°/APPLIES 102κ°/μ 체 μν°ν° κ΄κ³ 401κ°(κΈ°μ¬λΉ 5.6κ°). κ΄κ³ μ¬μ μ¬ νμ΄νλΌμΈ μ¬μ€ν μμ .
무결μ±, 보μ λ° μ μκΆ μ¬μΈ΅ κ²μ¬ ν΅κ³Ό λ° Git μ격 λ°°ν¬ μλ£ (2026-05-20):
- νμ: μ격 λ°°ν¬ μ μ½λμ μ λ°μ μΈ κ΅¬λ μμ μ±(무결μ±), μν¬λ¦Ώ λ ΈμΆ μν(보μ), λΌμ΄μ μ€ μΆ©λ λ° κΆλ¦¬ 주체(μ μκΆ)μ λν κ³΅μΈ κ²μ¦ μν νμ.
- μ‘°μΉ:
- λ¬΄κ²°μ± κ²μ¬(Integrity):
ruff checkμmypyμ μ νμ κ²μ¬λ₯Ό μ€ννμ¬ μ κ· μ€ν¬λ¦½νΈ μ€νμΌ μ€λ₯ λ° κ²½κ³ 0건μΌλ‘ ν΅κ³Όν¨.pytest tests/λ¨μ ν μ€νΈ(2/2 Passed) λ°tests/smoke_test_rag.py4λ 골λ μλλ¦¬μ€ ν΅ν© ν μ€νΈ(4/4 Passed)λ₯Ό μμ ν΅κ³Όν¨μΌλ‘μ¨ RAG 쿼리 μ νμ±κ³Ό κ·Έλν λ°λλ₯Ό μλ²½ν κ²μ¦ν¨.python -c "import app"μΌλ‘ Gradio λΉλ λ° μκ° μ§λ¨ ν΅κ³Ό νμΈ. - 보μ κ²μ¬(Security):
bandit보μ μ·¨μ½μ λΆμκΈ°λ₯Ό μ΄μ©ν΄ μμ€ μ½λ μ λ°μ 보μ μνμ νμνμ¬ High/Medium λ±κΈ μ·¨μ½μ 0건 κ²μ¦ μλ£..gitignoreμ.env,Articles_*.xlsxλ₯Ό μμ μ°¨λ¨νμ¬ μν¬λ¦Ώ ν€ λ° κΈ°μ¬ λ°μ΄ν° μ μΆ κ°λ₯μ±μ μμ² μ κ±°ν¨. - μ μκΆ κ²μ¬(Copyright): μμ‘΄μ± ν¨ν€μ§λ€μ λΌμ΄μ μ€λ₯Ό μ μ λΆμνμ¬ λͺ¨λ Apache 2.0, MIT, BSD λ± νμ©μ λΌμ΄μ μ€μμ νμΈνμ¬ λ²μ μν 0% 보μ₯. 루νΈμ MIT
LICENSEνμΌμ μ μ λ°°ν¬νκ³ ,delete_zero_rel_articles.py,plot_keywords.pyλ± μ κ· μ νΈλ¦¬ν° νμΌμ νκΈ μ€λͺ μ£Όμ λ° μ μκΆ λͺ μ ν€λλ₯Ό μλ²½ μ μ©ν¨. - Git μ
λ‘λ: λͺ¨λ μ건μ κ°μΆ μ½λλ₯Ό μ΅μ’
μ€ν
μ΄μ§νκ³ μμ΄ μ§§μ μ»€λ° λ©μμ§ κ·μΉ μ€μ ν
origin/mainμΌλ‘ μ΅μ’ Push μλ£. - UI λμμΈ νΌλλ°± λ°μ: μΈλΆλ‘ λμΆλμ΄ μ±ν μ°½ μμμ μΉ¨λ²νλ μ°μΈ‘ μ€λͺ HTMLμ μ κ±°νκ³ μ±λ΄ λ΄λΆμ placeholder μμμΌλ‘ μ볡νμμΌλ©°, λμ보λμ μ±ν μ°½μ 골λ νλ©΄ λΉμ¨(3:7 split)μ μλ²½νκ² λ³΅κ΅¬ν¨.
- λ¬΄κ²°μ± κ²μ¬(Integrity):
Gradio κΈ°λ³Έ μμ μ§λ¬Έ 100% GraphRAG λμ 보μ₯ κ°νΈ (2026-05-20):
- νμ: λ©μΈ νλ©΄μ κΈ°λ³Έ 4κ° μμ μ§λ¬Έ μ€ μΌλΆ(LLM κ°λ° κΈ°μ , κΈ°μ¬ μμ½ λ±)κ° λ€μ μΌλ°μ μ΄κ±°λ DB μ 보μ λͺ¨νΈν¨μΌλ‘ μΈν΄ GraphRAG κΈ°λ° λͺ¨λκ° μλ GPT-4o-mini μΌλ°(general) μ§μ λͺ¨λλ‘ μ°νλλ νμ νμΈ.
- μ‘°μΉ:
- Neo4j AuraDB μ€λ¬Ό κΈ°μ¬ λ° μν°ν° μ μ¬ λ°μ΄ν°(μΌμ± κ°μ°μ€ 2, μΉ΄μΉ΄μ€ μΉ΄λλ, AWS νΌμ§μ»¬ AI, κ΅¬κΈ I/O μ λ―Έλμ΄ λ±)λ₯Ό μ² μ ν νλ‘νμΌλ§νμ¬ 100% 리νΈλ¦¬λ²λ₯Ό νΈλ¦¬κ±°ν μ μλ μ΄κ³ νμ§ μ§λ¬Έ 4κ°λ‘ μμ μ§λ¬Έμ μ 격 κ°νΈ.
app.pyμ ν΅ν© κ²μ¦ μ€ν¬λ¦½νΈμΈtests/smoke_test_rag.pyμ μ μ©λ ν μ€νΈ μλλ¦¬μ€ μ§λ¬Έ ν μ€νΈ λ° κΈ°λ ν€μλλ₯Ό μμ ν μΌμΉνλλ‘ λκΈ°ν μμ μλ£.
- κ²μ¦:
ruffμ μ λ¦°νΈ λ°mypyνμ κ²μ¬λ₯Ό 무결μ ν΅κ³ΌνμμΌλ©°,tests/smoke_test_rag.pyν΅ν© 4λ 골λ μλλ¦¬μ€ μ€ν μ μ νλͺ©β PASSλ° 100% GraphRAG (graph mode) κΈ°λ° μλ΅κ³Ό μλ³Έ URL [μΆμ² λ§ν¬] λ ΈμΆμ μλ²½νκ² κ²μ¦ λ° μ μ¦ μλ£.
μ±ν μμ λλΉ 70% μΆμ λ° μν κ°κ²©(μ¬λ°±) μ΅μ ν κ°μ (2026-05-20):
- νμ: λ©μΈ νλ©΄ μ€λ₯Έμͺ½ 컬λΌμμ μ±λ΄ μΈν°νμ΄μ€μ κ°λ³ μ»΄ν¬λνΈ(μκ° λ³΄λ, μμ μ§λ¬Έ λ²νΌ, λ©μμ§ λ²λΈ, μ λ ₯μ°½)κ° νλ©΄μ 100% κ½ μ±μ λ€μ μκ°μ μΌλ‘ νΌμ Έ 보μ΄κ³ κ°λ μ±μ΄ μ νλλ λ¬Έμ λ°μ. λν μλ¨ GNBμ μ±λ΄ μ¬μ΄μ μμ§ μ¬λ°± λ° μ±λ΄ λ΄ μ»΄ν¬λνΈ κ° κ°κ²©μ΄ λ무 컀μ κ³΅κ° λλΉ λ°μ.
- μ‘°μΉ:
- μ°μΈ‘ Column ID μ§μ :
app.pyμμ μ°μΈ‘ μ±λ΄ μ»΄ν¬λνΈλ₯Ό λ΄λ Columnμelem_id="chat-column"μ κ³ μ νκ² μ§μ . - 컨ν
μ΄λ κΈ°λ° 70% λλΉ ν΅μ :
src/utils/ui_templates.pyμCUSTOM_CSSμμ#chat-column > divλ₯Ό μ§μ νμ¬ μ±λ΄ μ΅μΈκ³½ νλ μ μ 체λ₯Ό70%λλΉλ‘ μ ννκ³margin: 0 autoλ‘ μ€μ μ λ ¬μ κ°μ . μ΄μ λ§μΆ° λ΄λΆ μμ μμλ€(.placeholder,.examples-container,.message-wrap,.input-container)μwidth: 100%λ‘ λΆλͺ¨ 컨ν μ΄λμ λ± λ€μ΄λ§κ² μ λ ¬νμ¬ λ μ΄μμ μ΄κΈλ¨ μμ² μ κ±°. - μμ§ μ¬λ°± λν κΈ΄λ°ν: GNB μλμ λ°ν
λ§μ§μ
20pxμμ6pxλ‘ μ€μ΄κ³ ν¨λ©μ μμΆ. μ±λ΄ λ΄λΆμ μ»΄ν¬λνΈ κ° κ°κ²©(gapλ°margin)κ³Ό κ°λ³ 보λμ μμͺ½ ν¨λ©(padding)μ μ 체μ μΌλ‘ μ€μ¬(μ: μκ°κΈ ν¨λ©10px 14px, λ§μ§4px auto 6px autoλ±) νλ©΄ λ΄μ νλμ μ λ€μ΄μ€λλ‘ μ΅μ ν. - λ°μν λͺ¨λ°μΌ λ―Έλμ΄ μΏΌλ¦¬ κ°±μ : κ°λ‘ 800px μ΄ν λͺ¨λ°μΌ νλ©΄μμλ μλμΌλ‘ 100% κ½ μ°¨λλ‘ κ°±μ νμ¬ ν리미μ UXλ₯Ό μλ²½νκ² μ μ§.
- μ°μΈ‘ Column ID μ§μ :
- κ²μ¦:
ruffμmypyκ²μ¬λ₯Ό 무μ€λ₯ ν΅κ³Όν¨.python -c "import app"μΌλ‘ Gradio μΉμ± λΉλ 무결μ±μ μ΅μ’ ν보ν¨.
GraphRAG κ²μ κΈ°μ¬ λλ―Έ URL μμ² κ΅μ²΄ λ° DB λ°μ (2026-05-21):
- νμ: GraphRAG κ²μ κ²°κ³Όμμ μ 곡λλ 'κ·Όκ±° λ΄μ€ μΆμ²' λ§ν¬(URL)κ° μ κ·Όν μ μλ κ°μ§ λ€μ΄λ² λ΄μ€ URL νμ(
news.naver.com/main/read.naver?...&oid=001&aid=11111111λ±)μΌλ‘ νλμ½λ© λμ΄ μμ΄ μΆμ² νμΈ λΆκ°λ₯. - μ‘°μΉ:
- μ ν/μΉ΄μΉ΄μ€/ν μ€/λ€μ΄λ²νμ΄ 4λ ν΅μ¬ μ£Όμ μ μ€μ‘΄νλ 곡μ λ ₯ μλ μΈλ‘ μ¬ κΈ°μ¬(νκ΅κ²½μ λ§€κ±°μ§, λ΄μμ€, λμ§νΈνμμ€, λλ°Έλ₯λ΄μ€)μ μ€μ URL μ£Όμλ₯Ό κ²μ¦.
inject_fintech_gold_data.pyνμΌ λ΄ νλμ½λ©λ λλ―Έ URL 4건μ μ€μ URLλ‘ κ΅μ²΄.- Neo4j AuraDBμ μ μνμ¬ κΈ°μ‘΄ μ μ¬λ
Articleλ Έλλ€μ λλ―Έ URLμ μ€μ URLλ‘ μ§μ MERGEμ λ°μ΄νΈνλ μμ μ€ν¬λ¦½νΈλ₯Ό μμ±νμ¬ DB μ λ°μ΄νΈ μλ£. - μ½λ 무결μ±(ruff, mypy) λ° λ³΄μ 무결μ±(bandit High/Medium μ·¨μ½μ 0건) κ²μ¦ ν μ격 λ°°ν¬(Git push).
- κ²μ¦: Git pre-push hookμμ
pytestλ°smoke_test_rag.py100% PASS νμΈ μλ£.
- νμ: GraphRAG κ²μ κ²°κ³Όμμ μ 곡λλ 'κ·Όκ±° λ΄μ€ μΆμ²' λ§ν¬(URL)κ° μ κ·Όν μ μλ κ°μ§ λ€μ΄λ² λ΄μ€ URL νμ(