AI κΈ°λ° μΉ΄μΉ΄μ€ μλ¦Όν‘ ν νλ¦Ώ μλ μμ± μμ€ν
- κΈ°μ‘΄ μΉμΈ ν νλ¦Ώ: predata ν¨ν΄ κΈ°λ° κ²μ (μκ³κ° 0.85)
- κ³΅μ© ν νλ¦Ώ: 168κ° κ³΅μ© ν νλ¦Ώ κ²μ (μκ³κ° 0.8)
- μ ν νλ¦Ώ μμ±: AI κΈ°λ° μ κ· μμ± + 4-Tool λ³λ ¬ κ²μ¦
- Google Gemini 2.5 Pro + OpenAI GPT-4: LLM Fallback μμ€ν
- 7κ° λ Έλ μν¬νλ‘μ°: μ‘°κ±΄λΆ λΌμ°ν + λ³λ ¬ μ²λ¦¬
- conversationContext: μ¬μ§λ¬Έ κΈ°λ₯ μ§μ
- Agent1 λ³λ ¬ν: 3κ° κ²μ¦ μμ λμ μ€ν (60% λ¨μΆ)
- Agent2 λ³λ ¬ν: 4-Tool λμ μ²λ¦¬ (8μ΄ β 2-3μ΄)
- μΈμ κΈ°λ° λ³μ μ λ°μ΄νΈ: μ€μκ° ν νλ¦Ώ μμ
- ν νλ¦Ώ 미리보기: λ¨κ³λ³ μμ±λ νμΈ
- WebSocket μ€λΉ: λ°±μλ μ°λ μ§μ
- 28κ° μλ΅ μ€ν€λ§: μμ ν νμ μμ μ±
- νκ·Έλ³ λΆλ₯: Template Generation, Real-time Chat, Session Management, System
- Swagger UI: μλ μμ±λ API λ¬Έμ
μ¬μ©μ μ
λ ₯ (conversationContext μ§μ)
β
validate_input_node: λ³λ ¬ κ²μ¦ (3κ° μμ
λμ μ€ν)
β
extract_variables_node: Agent1 λ³μ μΆμΆ + μλ λΆμ
β
check_policy_node: μ’
ν©μ μΈ μ μ±
κ²μ¬
β
select_template_node: 3λ¨κ³ μ ν μμ€ν
1οΈβ£ BasicTemplateMatcher (κΈ°μ‘΄ μΉμΈ ν
νλ¦Ώ)
2οΈβ£ PublicTemplateManager (168κ° κ³΅μ© ν
νλ¦Ώ)
3οΈβ£ LangGraph β generate_template_node
β
generate_template_node: Agent2 λ³λ ¬ μ²λ¦¬ (4-Tool λμ)
β
validate_compliance_node: μ΅μ’
μ»΄νλΌμ΄μΈμ€ κ²μ¦
β
format_response_node: λ°±μλ API νμ λ³ν
StateGraph μν¬νλ‘μ°
- 7κ° λ Έλ + μ‘°κ±΄λΆ λΌμ°ν
- JoberState κΈ°λ° μν κ΄λ¦¬
- λ°±μλ API 100% νΈνμ± μ μ§
λ³λ ¬ μ²λ¦¬ λ Έλλ€
- validate_input_node: 3κ° κ²μ¦ λ³λ ¬ μ€ν
- generate_template_node: 4-Tool λμ μ²λ¦¬
- 60-85% μ±λ₯ ν₯μ λ¬μ±
Template Selector
- 3λ¨κ³ μ ν λ‘μ§ κ΄λ¦¬
- μ μ¬λ μκ³κ° μ€μ λ° μ΅μ ν
- LangGraph μν¬νλ‘μ° ν΅ν©
Public Template Manager
- 168κ° κ³΅μ© ν νλ¦Ώ κ΄λ¦¬ (Chroma DB)
- ν€μλ + λ²‘ν° νμ΄λΈλ¦¬λ κ²μ
- μλ λ³μ λ§€ν μμ€ν
LLM Provider Manager
- Gemini β OpenAI μλ Fallback
- μ€ν¨ νμ μΆμ λ° μ°μ μμ κ΄λ¦¬
- λͺ¨λΈλ³ retry λ‘μ§
Session Manager
- Thread-safe μΈμ κ΄λ¦¬
- μ€μκ° λ³μ μ λ°μ΄νΈ
- μλ λ§λ£ λ° μ 리
# λ ν¬μ§ν 리 ν΄λ‘
git clone https://github.com/your-username/Jober_ai.git
cd Jober_ai
# Python 3.11.13 μ€μΉ (κΆμ₯)
pyenv install 3.11.13
pyenv local 3.11.13
# Poetryλ‘ μμ‘΄μ± μ€μΉ
poetry install# .env νμΌ μμ± (νμ)
cp .env.example .env
# .env νμΌ νΈμ§
cat > .env << EOF
# LLM API ν€ (λ μ€ νλλ νμ)
GEMINI_API_KEY=your_gemini_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
# 보μ μ€μ (νμ)
SECRET_KEY=your-super-secret-key-32-characters-long
# μ νμ μ€μ
DEBUG=false
HOST=0.0.0.0
PORT=8000
LOG_LEVEL=INFO
EOF# FastAPI μλ² μ€ν
python app/main.py
# λλ Poetryλ‘ μ€ν
poetry run python app/main.py
# λλ uvicorn μ§μ μ€ν
poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload# ν
νλ¦Ώ μμ± μμ²
curl -X POST http://localhost:8000/ai/templates \
-H "Content-Type: application/json" \
-d '{
"userId": 123,
"requestContent": "λ
μλͺ¨μ μλ΄ λ©μμ§λ₯Ό λ§λ€μ΄μ£ΌμΈμ",
"conversationContext": null
}'
# μλ² μν νμΈ
curl http://localhost:8000/ai/health
# Swagger UI μ μ
open http://localhost:8000/docsAI κΈ°λ° ν νλ¦Ώ μμ± (μ¬μ§λ¬Έ μ§μ)
μμ²:
{
"userId": 12345,
"requestContent": "λ
μλͺ¨μ μλ΄ λ©μμ§ λ§λ€μ΄μ£ΌμΈμ",
"conversationContext": null
}μ±κ³΅ μλ΅ (200):
{
"id": 1,
"userId": 12345,
"categoryId": "004001",
"title": "μλ¦Όν‘",
"content": "μλ
νμΈμ #{κ³ κ°λͺ
}λ!\n\n#{μ
체λͺ
}μμ #{μλΉμ€λͺ
} κ΄λ ¨νμ¬ μλ΄λ립λλ€...",
"imageUrl": null,
"type": "MESSAGE",
"buttons": [],
"variables": [
{
"id": 1,
"variableKey": "κ³ κ°λͺ
",
"placeholder": "#{κ³ κ°λͺ
}",
"inputType": "text",
"value": "νκΈΈλ"
}
],
"industry": ["κ΅μ‘", "λ¬Έν"],
"purpose": ["μλ΄", "μμ½νμΈ"]
}μ 보 λΆμ‘± μλ΅ (400):
{
"detail": {
"error": {
"code": "INCOMPLETE_INFORMATION",
"message": "μΆκ° μ λ³΄κ° νμν©λλ€",
"details": {
"confirmed_variables": {"μ
체λͺ
": "ABCμμ "},
"missing_variables": ["μΌμ", "μ₯μ", "μ°λ½μ²"],
"contextual_question": "λ
μλͺ¨μ μΌμμ μ₯μ, λ¬Έμ μ°λ½μ²λ₯Ό μλ €μ£ΌμΈμ.",
"original_input": "λ
μλͺ¨μ μλ΄ λ©μμ§ λ§λ€μ΄μ€"
}
},
"timestamp": "2024-09-22T10:30:00Z"
}
}μΈμ λ³μ μ λ°μ΄νΈ
ν νλ¦Ώ 미리보기 μ‘°ν
ν νλ¦Ώ μ΅μ’ μμ±
μΈμ ν΅κ³ μ‘°ν
μΈμ λͺ©λ‘ μ‘°ν
κΈ°λ³Έ ν¬μ€μ²΄ν¬
μμΈ ν¬μ€μ²΄ν¬
LLM μ 곡μ ν μ€νΈ
# κΈ°λ³Έ ν
νλ¦Ώ μμ±
curl -X POST http://localhost:8000/ai/templates \
-H "Content-Type: application/json" \
-d '{"userId": 123, "requestContent": "μΉ΄ν μ΄λ²€νΈ μλ΄ λ©μμ§ λ§λ€μ΄μ£ΌμΈμ"}'
# μ¬μ§λ¬Έ ν
μ€νΈ
curl -X POST http://localhost:8000/ai/templates \
-H "Content-Type: application/json" \
-d '{
"userId": 123,
"requestContent": "μΌμλ 3μ 15μΌ, μ₯μλ κ°λ¨κ΅¬μ
λλ€",
"conversationContext": "μΉ΄ν μ΄λ²€νΈ μλ΄ λ©μμ§ λ§λ€μ΄μ£ΌμΈμ"
}'
# λΉμμ΄ κ²μΆ ν
μ€νΈ
curl -X POST http://localhost:8000/ai/templates \
-H "Content-Type: application/json" \
-d '{"userId": 123, "requestContent": "λ°λ³΄μΌ"}'# μ½λ ν
μ€νΈ μ€ν
poetry run pytest tests/
# μ½λ νμ§ κ²μ¬
poetry run ruff check app/
poetry run black app/
# νμ
κ²μ¬
poetry run mypy app/Jober_ai/
βββ README.md # νλ‘μ νΈ μ€λͺ
μ
βββ pyproject.toml # Poetry μμ‘΄μ± μ€μ
βββ poetry.lock # Poetry λ½ νμΌ
βββ app/ # λ©μΈ μ ν리μΌμ΄μ
β βββ main.py # FastAPI μ§μ
μ
β βββ api/ # API μλν¬μΈνΈ
β β βββ templates.py # ν
νλ¦Ώ μμ± API
β β βββ sessions.py # μΈμ
κ΄λ¦¬ API
β β βββ health.py # ν¬μ€μ²΄ν¬ API
β βββ agents/ # AI μμ΄μ νΈ
β β βββ agent1.py # λ³μ μΆμΆ + μλ λΆμ
β β βββ agent2.py # 4-Tool λ³λ ¬ κ²μ¦
β βββ core/ # ν΅μ¬ μμ§
β β βββ template_selector.py # 3λ¨κ³ μ ν μμ€ν
β β βββ public_template_manager.py # 168κ° κ³΅μ© ν
νλ¦Ώ κ΄λ¦¬
β β βββ session_manager.py # μΈμ
κ΄λ¦¬
β β βββ template_preview.py # λ―Έμμ±λ ν
νλ¦Ώ μΆλ ₯
β β βββ session_models.py # μΈμ
λͺ¨λΈ
β βββ tools/ # κ²μ¦ λꡬ
β β βββ blacklist_tool.py # κΈμ§μ΄ κ²μ¬
β β βββ whitelist_tool.py # νμ© ν¨ν΄ κ²μ¬
β β βββ info_comm_law_tool.py # μ 보ν΅μ λ§λ² κ²μ¬
β β βββ variable_mapper.py # λ³μ λ§€ν
β βββ utils/ # μ νΈλ¦¬ν°
β βββ llm_provider_manager.py # LLM Fallback μμ€ν
βββ config/ # μ€μ νμΌ
β βββ settings.py # μ ν리μΌμ΄μ
μ€μ
β βββ llm_providers.py # LLM μ 곡μ μ€μ
βββ data/ # λ°μ΄ν°
β βββ templates/ # 168κ° κ³΅μ© ν
νλ¦Ώ
β βββ presets/ # μ μ²λ¦¬λ λ°μ΄ν°
β βββ docs/ # μ μ±
λ¬Έμ
βββ tests/ # ν
μ€νΈ
β βββ test_template_selector.py # ν
νλ¦Ώ μ ν ν
μ€νΈ
β βββ test_template_validator.py # κ²μ¦ μμ€ν
ν
μ€νΈ
β βββ test_integration.py # ν΅ν© ν
μ€νΈ
βββ scripts/ # μ νΈλ¦¬ν° μ€ν¬λ¦½νΈ
βββ debug_agent1_variables.py # Agent1 λλ²κΉ
- LangGraph: μν κΈ°λ° μν¬νλ‘μ° μμ§ (μ±λ₯ 60-85% ν₯μ)
- Google Gemini 2.5 Pro: μ΅μ μμ±ν AI (μ£Ό LLM)
- OpenAI GPT-4: Fallback LLM
- KoNLPy: νκ΅μ΄ μμ°μ΄ μ²λ¦¬
- Chroma DB: λ²‘ν° λ°μ΄ν°λ² μ΄μ€ (μλ² λ© μΊμ)
- Python 3.11.13: μμ μ μΈ νμ΄μ¬ λ²μ
- FastAPI: κ³ μ±λ₯ λΉλκΈ° μΉ νλ μμν¬
- Pydantic: λ°μ΄ν° κ²μ¦ (28κ° μλ΅ μ€ν€λ§)
- Poetry: λͺ¨λ μμ‘΄μ± κ΄λ¦¬
- uvicorn: ASGI μλ²
- 168κ° κ³΅μ© ν νλ¦Ώ: μ μ²λ¦¬λ ν νλ¦Ώ DB
- Chroma DB: λ²‘ν° κ²μ λ° μΊμ±
- 768μ°¨μ μλ² λ©: Gemini μλ² λ© λͺ¨λΈ
- Thread-safe μΈμ : λ©λͺ¨λ¦¬ κΈ°λ° μΈμ κ΄λ¦¬
- SECRET_KEY νμ: νκ²½λ³μ νμ μ€μ
- API ν€ κ²μ¦: LLM μ 곡μ ν€ μ ν¨μ± κ²μ¬
- CORS μ μ± : νλ‘λμ νκ²½ λλ©μΈ μ ν κΆμ₯
- μλ¬ μ 보 μ ν: DEBUG=false μ μμΈ μ 보 μ¨κΉ
- 4-Tool λ³λ ¬ κ²μ¦: BlackList, WhiteList, κ°μ΄λλΌμΈ, μ 보ν΅μ λ§λ²
- μ체 νμ§ κ²μ¦: TemplateValidator μμ€ν
- μ¬μμ± λ‘μ§: νμ§ λ―Έλ¬ μ μλ μ¬μλ
- μμ±λ μΆμ : μΈμ λ³ μ§νλ₯ κ΄λ¦¬
- LLM Fallback: Gemini β OpenAI μλ μ ν
- μ€ν¨ μΉ΄μ΄νΈ: μ°μ μ€ν¨ μ μ°μ μμ μ‘°μ
- μλ 볡ꡬ: μ μ μλ μ μλ μ€μ 볡μ
7κ° λ Έλλ‘ κ΅¬μ±λ μμ ν μν¬νλ‘μ°:
validate_input_node: λ³λ ¬ μ λ ₯ κ²μ¦ (3κ° μμ λμ)extract_variables_node: Agent1 λ³μ μΆμΆ + μλ λΆμcheck_policy_node: μ’ ν©μ μΈ μ μ± κ²μ¬select_template_node: 3λ¨κ³ ν νλ¦Ώ μ ν μμ€ν generate_template_node: Agent2 λ³λ ¬ μ²λ¦¬ (4-Tool λμ)validate_compliance_node: μ΅μ’ μ»΄νλΌμ΄μΈμ€ κ²μ¦format_response_node: λ°±μλ API νμ λ³ν
λ³λ ¬ μ²λ¦¬ μ΅μ ν:
- Agent1 μ²λ¦¬: μμ°¨ λλΉ 60% λ¨μΆ
- Agent2 μ²λ¦¬: κΈ°μ‘΄ 8μ΄ β 2-3μ΄ (60-75% λ¨μΆ)
- μ 체 μν¬νλ‘μ°: 60-85% μ±λ₯ ν₯μ
μν κΈ°λ° λΌμ°ν :
# μ‘°κ±΄λΆ λΌμ°ν
μΌλ‘ ν¨μ¨μ μΈ λΆκΈ° μ²λ¦¬
workflow.add_conditional_edges(
"validate_input",
check_validation_status,
{
"valid": "extract_variables",
"invalid": "format_response" # κ²μ¦ μ€ν¨ μ μ¦μ μλ΅
}
)κΈ°μ‘΄ λ¬Έμ μ ν΄κ²°:
- μμ°¨ μ²λ¦¬ β λ³λ ¬ μ²λ¦¬: Agent1, Agent2 λ΄λΆ λ³λ ¬ν
- 볡μ‘ν μν κ΄λ¦¬: LangGraph StateGraphλ‘ μ²΄κ³ν
- μ€λ₯ μ²λ¦¬: κ° λ Έλλ³ μ²΄κ³μ μΈ μμΈ μ²λ¦¬
- λ°±μλ νΈνμ±: 100% API νΈνμ± μ μ§
LangGraph ν¨ν΄ μ μ©:
- StateGraph κΈ°λ° μν¬νλ‘μ° μ μ
- TypedDict μν κ΄λ¦¬ (JoberState)
- μ‘°κ±΄λΆ λΌμ°ν λ° λΆκΈ° μ²λ¦¬
- λΉλκΈ° λ Έλ μ€ν