ํ์ฌ ๋ฐ ์ฌ๋ ์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ์ ํํ ๋ต๋ณ์ ์ ๊ณตํ๋ RAG(Retrieval-Augmented Generation) ์์คํ ์ ๋๋ค.
Google Gemini ๋ชจ๋ธ๊ณผ LangChain/LangGraph ํ๋ ์์ํฌ๋ฅผ ํ์ฉํ์ฌ ์์ ์ง์์ ๊ฒ์ ์ฆ๊ฐ ์์ฑ์ ๊ตฌํํ์ต๋๋ค.
- ์ฃผ์ ๊ธฐ๋ฅ
- ๋ฐ์ดํฐ ์ถ์ฒ
- ๊ธฐ์ ์คํ
- ํ๋ก์ ํธ ๊ตฌ์กฐ
- ๋น ๋ฅธ ์์
- API ์ฌ์ฉ ๊ฐ์ด๋
- ์ํคํ ์ฒ ์์ธ
- PDF ํ
์คํธ ์ถ์ถ:
pdfplumber๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์ ๋ด์ฉ ์ถ์ถ - OCR ์ฒ๋ฆฌ: Google Cloud Vision API๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง ๊ธฐ๋ฐ PDF ํ ์คํธ ์ถ์ถ
- ์๋ฏธ ๊ธฐ๋ฐ ๋ถํ (Semantic Chunking): ๋ฌธ๋งฅ์ ๊ณ ๋ คํ ์ง๋ฅํ ๋ฌธ์ ๋ถํ
- ์ปจํ ์คํธ ๋ณด๊ฐ: Anthropic์ Contextual Retrieval ๊ธฐ๋ฒ ์ ์ฉ
- ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์: ์๋ฏธ ๊ฒ์(70%) + ํค์๋ ๊ฒ์(30%) ๊ฒฐํฉ
- ์ฌ์์(Re-ranking): Vertex AI Reranker๋ฅผ ํตํ ๊ฒ์ ๊ฒฐ๊ณผ ์ ๋ฐํ
- ๋ค์ค ๋ฒกํฐ ์คํ ์ด: ์์ ๋ฌธ์, ๋ ์จ ์ฌ๊ณ ์ฌ๋ก, ํ์ฌ ์ฌ๊ณ ์ฌ๋ก ๋ณ๋ ๊ด๋ฆฌ
- ์ผ๋ฐ ์ฑํ : ์์ ์ง์์ ๋ํ ๋ํํ ์ง์์๋ต
- ์์ ๊ธฐ๋ฐ ์์ ์๋ด๋ฌธ: ํ์ฌ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๋ฉด ๋ง์ถคํ ์์ ๊ฐ์ด๋ ์์ฑ
- ๋ ์จ ๊ธฐ๋ฐ ๋ถ์: ์ค์๊ฐ ๋ ์จ ์ ๋ณด๋ฅผ ํ์ฉํ ๋ง์ถคํ ์์ ์๋ด๋ฌธ ์์ฑ
- ํ์ ์ง๋ฌธ ์ฒ๋ฆฌ: ์์ฑ๋ ์๋ด๋ฌธ์ ๋ํ ์ถ๊ฐ ์ง๋ฌธ ์ง์
- Docker & Docker Compose ์ง์
- FastAPI ๊ธฐ๋ฐ REST API
- ์ธ์ ๊ธฐ๋ฐ ๋ํ ๊ธฐ์ต ๊ด๋ฆฌ
ํ์ ๋ฐ์ดํฐ: ์ด ํ๋ก์ ํธ๋ฅผ ์คํํ๋ ค๋ฉด ๋ค์ Hugging Face ๋ฐ์ดํฐ์ ์์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ํด์ผ ํฉ๋๋ค.
https://huggingface.co/datasets/bong9513/safety_rag_data
๊ด๋ จ ํ๋ก์ ํธ: ๋ณธ ํ๋ก์ ํธ๋ safety-navigator์ ์ฐ๋๋ฉ๋๋ค.
| ์นดํ ๊ณ ๋ฆฌ | ๊ธฐ์ |
|---|---|
| ์ธ์ด | Python 3.11 |
| ํ๋ ์์ํฌ | FastAPI, Uvicorn |
| AI/ML | Google Gemini, Vertex AI, LangChain, LangGraph |
| ๊ฒ์ | FAISS, BM25Retriever, Vertex AIRank |
| ๋ฐ์ดํฐ ์ฒ๋ฆฌ | pdfplumber, pandas, numpy |
| ๋ฐฐํฌ | Docker, Docker Compose |
| ์ธ๋ถ API | Google Cloud Vision, Google Geocoding, Open-Meteo Weather |
.
โโโ .env # ํ๊ฒฝ ๋ณ์ ์ค์
โโโ .gitignore # Git ๋ฌด์ ํ์ผ
โโโ docker-compose.yml # Docker Compose ์ค์
โโโ Dockerfile # Docker ์ด๋ฏธ์ง ์ค์
โโโ fire_app.py # FastAPI ๋ฉ์ธ ์๋ฒ
โโโ requirements.txt # Python ํจํค์ง ์์กด์ฑ
โโโ README.md # ์ด ๋ฌธ์
โโโ vector/ # ๋ฐ์ดํฐ ๋ฐ ์ ์ ํด๋
โโโ data/ # ๋ฐ์ดํฐ ์ ์ฅ์
โ โโโ case_data/ # ์ฌ๊ณ ์ฌ๋ก ๋ฐ์ดํฐ
โ โ โโโ original_data/ # ์๋ณธ ์ฌ๊ณ ์ฌ๋ก
โ โ โ โโโ case_climate.pkl # ๋ ์จ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก
โ โ โ โโโ case_festival.pkl # ํ์ฌ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก
โ โ โโโ contextual_content_docs/ # ์ปจํ
์คํธ ๋ณด๊ฐ๋ ๋ฌธ์
โ โโโ chunked_docs/ # ์๋ฏธ ๊ธฐ๋ฐ์ผ๋ก ๋ถํ ๋ ๋ฌธ์
โ โโโ original_full_text/ # PDF ์๋ณธ ํ
์คํธ
โ โโโ original_pdf/ # ๋ถ์ํ PDF ์๋ณธ
โ โโโ vector_store/ # FAISS ๋ฒกํฐ DB
โโโ definition/ # RAG ํ์ดํ๋ผ์ธ ์ ์
โโโ chain_for_chat.py # ์ผ๋ฐ ์ฑํ
์ฒด์ธ
โโโ chain_for_form.py # ์์ ๊ธฐ๋ฐ ์์ ์๋ด๋ฌธ ์ฒด์ธ
โโโ chain_for_form_chat.py # ํ์ ์ง๋ฌธ ์ฒ๋ฆฌ ์ฒด์ธ
โโโ custom.py # ๋ ์จ ๊ธฐ๋ฐ ์์ ๋ถ์ ์ฒด์ธ
โโโ get_weather.py # ๋ ์จ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
โโโ ocr.py # OCR ์ฒ๋ฆฌ
โโโ make_context.py # ์ปจํ
์คํธ ์์ฑ
โโโ make_contextual_content_with_caching.py # ์บ์ฑ ์ ์ฉ ์ปจํ
์คํธ ์์ฑ
โโโ make_docs.py # ๋ฌธ์ ๋ถํ
โโโ make_full_text.py # ํ
์คํธ ์ถ์ถ
โโโ make_vector_store.py # ๋ฒกํฐ ์คํ ์ด ์์ฑ
โโโ vector_store.py # ๋ฒกํฐ DB ๊ด๋ฆฌ
โโโ case_vector_store.py # ์ฌ๊ณ ์ฌ๋ก ๋ฒกํฐ ์คํ ์ด
โโโ semantic_split_genai.py # GenAI ์๋ฏธ ๋ถํ
โโโ semantic_split_vertex.py # Vertex AI ์๋ฏธ ๋ถํ
- Docker & Docker Compose
- Google Cloud ํ๋ก์ ํธ
- Google Cloud ์๋น์ค ๊ณ์ ์ธ์ฆ ํค
git clone https://github.com/singbong/safety_rag.git
cd safety_rag.env ํ์ผ ์์ฑ:
GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
PROJECT_ID="YOUR_PROJECT_ID"
GEOCODING_API="YOUR_GOOGLE_GEOCODING_API_KEY"์๋น์ค ๊ณ์ ์ธ์ฆ ํค๋ฅผ vector/definition/ ๋๋ ํ ๋ฆฌ์ ๋ฐฐ์นํ์ธ์.
docker-compose up --build -dAPI๋ http://localhost:8000์์ ์ ๊ทผ ๊ฐ๋ฅํฉ๋๋ค.
Hugging Face์์ ๋ฐ์ดํฐ์
์ ๋ค์ด๋ก๋ํ์ฌ vector/data/ ๋๋ ํ ๋ฆฌ์ ๋ฐฐ์นํ์ธ์.
์์ ์ง์์ ๋ํ ์ผ๋ฐ์ ์ธ ์ง๋ฌธ์ ๋ต๋ณํฉ๋๋ค.
import requests
response = requests.post("http://localhost:8000/api/chat", json={
"question": "์ง์ง ๋ฐ์ ์ ํ๋ ์๋ น ์๋ ค์ค",
"session_id": "user123"
})
print(response.json()['final_answer'])ํ์ฌ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๋ฉด ๋ง์ถคํ ์์ ์๋ด๋ฌธ์ ์์ฑํฉ๋๋ค.
response = requests.post("http://localhost:8000/api/generate_form", json={
"place_name": "2025 ํ๊ฐ ์ฌ๋ฆ ๋ฎค์ง ํ์คํฐ๋ฒ",
"type": "๋๊ท๋ชจ ์ผ์ธ ๊ณต์ฐ",
"region": "์์ธ, ์ฌ์๋ ํ๊ฐ๊ณต์",
"period": "2025๋
8์ 8์ผ ~ 2025๋
8์ 10์ผ",
"description": "๋ค์ํ ์ฅ๋ฅด์ ์ํฐ์คํธ๋ค๊ณผ ํจ๊ปํ๋ 3์ผ๊ฐ์ ์ถ์ ",
"category": "์์
/ํ์คํฐ๋ฒ",
"related_documents": "festival_guide.pdf", # ํ์ฌ ๊ด๋ จ ๋ฌธ์ (OCR ์ฒ๋ฆฌ๋จ)
"emergency_contact_name": "์ข
ํฉ์ํฉ์ค ์์ ๊ด๋ฆฌํ",
"emergency_contact_phone": "02-123-4567"
})
print(response.json()['final_answer'])์์ฑ๋ ์๋ด๋ฌธ์ ๋ํด ์ถ๊ฐ ์ง๋ฌธ์ ํฉ๋๋ค.
# ๋จผ์ /api/generate_form์ผ๋ก ์์ฑ๋ ์๋ด๋ฌธ ์ฌ์ฉ
generated_form = "..." # ์ด์ API ์๋ต์ final_answer
response = requests.post("http://localhost:8000/api/form_chat", json={
"generated_form": generated_form,
"query": "์จ์ด์งํ ์ฆ์์ผ๋ก๋ ๊ตฌ์ฒด์ ์ผ๋ก ์ด๋ค ๊ฒ๋ค์ด ์๋์?",
"session_id": "user123"
})
print(response.json()['final_answer'])์ค์๊ฐ ๋ ์จ ์ ๋ณด๋ฅผ ํ์ฉํ ๊ณ ๊ธ ์์ ๋ถ์์ ์ ๊ณตํฉ๋๋ค.
response = requests.post("http://localhost:8000/api/custom_form", json={
"place_name": "2025 ํ๊ฐ ์ฌ๋ฆ ๋ฎค์ง ํ์คํฐ๋ฒ",
"type": "๋๊ท๋ชจ ์ผ์ธ ๊ณต์ฐ",
"location": "์์ธ, ์ฌ์๋ ํ๊ฐ๊ณต์",
"period": "2025๋
8์ 8์ผ ~ 2025๋
8์ 10์ผ",
"description": "๋ค์ํ ์ฅ๋ฅด์ ์ํฐ์คํธ๋ค๊ณผ ํจ๊ปํ๋ 3์ผ๊ฐ์ ์ถ์ ",
"category": "์์
/ํ์คํฐ๋ฒ",
"emergency_contact_name": "์ข
ํฉ์ํฉ์ค ์์ ๊ด๋ฆฌํ",
"emergency_contact_phone": "02-123-4567",
"expected_attendees": "50000",
"related_documents": "festival_guide.pdf" # OCR ์ฒ๋ฆฌ๋จ
})
result = response.json()
print("์์ฑ๋ ์๋ด๋ฌธ:", result['generation'])
print("๋ ์จ ์์ฝ:", result['weather_summary']){
"generation": "์์ฑ๋ ๋ง์ถคํ ์์ ์๋ด๋ฌธ",
"hallu_check": {
"answer_with_citations": "Grounding ๊ฒ์ฆ ์๋ฃ๋ ์๋ด๋ฌธ"
},
"weather_summary": "๋ถ์๋ ๋ ์จ ์์ฝ",
"festival_query_list": ["ํ์ฌ ๊ด๋ จ ๊ฒ์์ด๋ค"],
"weather_query_list": ["๋ ์จ ๊ด๋ จ ๊ฒ์์ด๋ค"],
"safety_query_list": ["์์ ๊ด๋ จ ๊ฒ์์ด๋ค"]
}- ํ
์คํธ ์ถ์ถ: PDF โ
pdfplumberํ ์คํธ ์ถ์ถ - OCR ์ฒ๋ฆฌ: ์ด๋ฏธ์ง PDF โ Google Cloud Vision API โ ํ ์คํธ
- ์๋ฏธ ๊ธฐ๋ฐ ๋ถํ : LangChain
SemanticChunker+gemini-embedding-001 - ์ปจํ
์คํธ ๋ณด๊ฐ:
gemini-2.5-flash-lite๋ก ๋ฌธ์ ๋งฅ๋ฝ ์์ฝ โ ํค๋ ์์ฑ - ๋ฒกํฐํ: ์ปจํ
์คํธ ๋ณด๊ฐ ํ
์คํธ โ
gemini-embedding-001โ FAISS ์ ์ฅ
-
ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์:
- ์๋ฏธ ๊ฒ์(FAISS, 70%) + ํค์๋ ๊ฒ์(BM25, 30%)
- ์ด๊ธฐ ๊ฒฐ๊ณผ: 150๊ฐ ๋ฌธ์
-
์ฌ์์(Re-ranking):
- Vertex AI Reranker(
semantic-ranker-default-004) - ์ต์ข ๊ฒฐ๊ณผ: ์์ 20๊ฐ ๋ฌธ์
- Vertex AI Reranker(
์ง๋ฌธ ์
๋ ฅ
โ
[Re-writer] ์ง๋ฌธ ์ฌ์์ฑ (๋๋ช
์ฌ โ ๊ตฌ์ฒด์ ์ฉ์ด)
โ
[Question Decomposer] ๋ณต์กํ ์ง๋ฌธ โ ํ์ ์ง๋ฌธ ๋ถํด
โ
[Search Document] ๋ฒกํฐ ์คํ ์ด ๊ฒ์ โ Re-ranking
โ
[Generator] ๋ต๋ณ ์์ฑ ('์์ ์งํค๋ฏธ AI' ์ญํ )
โ
[Hallucination Checker] Grounding ํ์ธ + ์ธ์ฉ
โ
[Answer Beautifier] ๋งํฌ๋ค์ด ํ์ ์ ์
โ
์ต์ข
๋ต๋ณ
ํ์ฌ ์ ๋ณด ์
๋ ฅ
โ
[Query Generator] ๋ค๊ฐ์ ๊ฒ์์ด ์์ฑ (์ํ ์๋๋ฆฌ์ค ๊ธฐ๋ฐ)
โ
[Search Document] ์์ ๊ด๋ จ ๋ฌธ์ ๊ฒ์
โ
[Generator] ๋ง์ถคํ ์์ ์๋ด๋ฌธ ์์ฑ ('์์ ์ ๋ฌธ๊ฐ' ์ญํ )
โ
[Grounding & Beautify] ์ ๋ขฐ๋ ํ์ธ + ๋งํฌ๋ค์ด ์ ์
โ
์ต์ข
์๋ด๋ฌธ
ํ์ฌ ์ ๋ณด ์
๋ ฅ
โ
[Geocoding] ํ์ฌ ์ฅ์๋ช
โ ์๋/๊ฒฝ๋ ๋ณํ
โ
[Weather API] Open-Meteo 24์๊ฐ ๋ ์จ ์๋ณด ์์ง
โ
[๋ ์จ ๋ถ์] Gemini 2.5 Flash Lite๋ก ์ํ ์์ ์ถ์ถ
โ
๋ค์ค ๊ฒ์์ด ์์ฑ:
- festival_query_generator (ํ์ฌ ๊ด๋ จ)
- weather_query_generator (๋ ์จ ๊ด๋ จ)
- safety_query_generator (์ผ๋ฐ ์์ )
โ
๋ค์ค ๋ฒกํฐ ์คํ ์ด ๊ฒ์:
- search_festival_document (ํ์ฌ ์ฌ๊ณ ์ฌ๋ก)
- search_weather_document (๋ ์จ ์ฌ๊ณ ์ฌ๋ก)
- search_safety_document (์์ ๋ฌธ์)
โ
[Generator] ์ข
ํฉ ์์ ์๋ด๋ฌธ ์์ฑ
โ
[Hallu Checker] Vertex AI Grounding ๊ฒ์ฆ
โ
์ต์ข
๊ฒฐ๊ณผ: ์๋ด๋ฌธ + ๋ ์จ ์์ฝ + ๊ฒ์์ด ๋ชฉ๋ก
| ๋ณ์ | ์ค๋ช | ํ์ ์ฌ๋ถ |
|---|---|---|
GOOGLE_API_KEY |
Google Generative AI API ํค | ํ์ |
PROJECT_ID |
Google Cloud ํ๋ก์ ํธ ID | ํ์ |
GEOCODING_API |
Google Geocoding API ํค | ๋ ์จ ๊ธฐ๋ฐ ๊ธฐ๋ฅ ์ ํ์ |
์ด ํ๋ก์ ํธ์ ๋ผ์ด์ ์ค ์ ๋ณด๋ฅผ ํ์ธํ์ธ์.
์ด ํ๋ก์ ํธ์ ๊ธฐ์ฌํ๊ณ ์ถ์ผ์๋ค๋ฉด Pull Request๋ฅผ ์ ์ถํด์ฃผ์ธ์.
- ๋ฌธ์ ์ฒ๋ฆฌ: PDF ํ์ผ์ ํ ์คํธ๋ก ๋ณํํ๊ณ ์๋ฏธ ๊ธฐ๋ฐ์ผ๋ก ๋ถํ (Semantic Chunking)ํฉ๋๋ค.
- ๋ฒกํฐ ์๋ฒ ๋ฉ: ์ฒ๋ฆฌ๋ ํ ์คํธ๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ์ฌ FAISS ๋ฒกํฐ ์คํ ์ด์ ์ ์ฅํฉ๋๋ค.
- ์ง์์๋ต: ์ ์ฅ๋ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ๊ฐ์ฅ ๊ด๋ จ์ฑ ๋์ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
- ๋ค์ํ ์ธํฐํ์ด์ค: ์ผ๋ฐ ์ฑํ , ์์(Form) ๊ธฐ๋ฐ ์ฑํ ๋ฑ ์ฌ๋ฌ ์ข ๋ฅ์ ๋ํ ์ฒด์ธ์ ์ ๊ณตํฉ๋๋ค.
- ์ปจํ ์ด๋ ๊ธฐ๋ฐ: Docker ๋ฐ Docker Compose๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ํธ ํ๊ฒฝ์ ์ฝ๊ฒ ๊ตฌ์ฑํ๊ณ ์คํํ ์ ์์ต๋๋ค.
- ์ธ์ด: Python 3
- ํต์ฌ ํ๋ ์์ํฌ:
LangChain&LangGraph: RAG ํ์ดํ๋ผ์ธ ๋ฐ ๋ณต์กํ Agent ๋ก์ง ๊ตฌ์ฑFastAPI: API ์๋ฒ ๊ตฌ์ถ
- AI:
google-generativeai&google-cloud-aiplatform: Google Gemini ๋ชจ๋ธ API ํ์ฉlangchain-google-vertexai: LangChain๊ณผ Vertex AI ํตํฉgoogle-cloud-vision: Google Cloud Vision API (OCR ์ฒ๋ฆฌ)FAISS: ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค (์ ์ฌ๋ ๊ฒ์)transformers&sentence-transformers: ์์ฐ์ด ์ฒ๋ฆฌ ๋ฐ ์๋ฒ ๋ฉ
- ๋ฐ์ดํฐ ์ฒ๋ฆฌ:
pdfplumber: PDF ํ ์คํธ ์ถ์ถpandas,numpy: ๋ฐ์ดํฐ ์กฐ์ ๋ฐ ๋ถ์
- ๋ฐฐํฌ:
Docker&docker-compose: ์ปจํ ์ด๋ ๊ธฐ๋ฐ ๋ฐฐํฌ ๋ฐ ์คํ ํ๊ฒฝuvicorn: ASGI ์๋ฒ
.
โโโ .env # API ํค, ํ๋ก์ ํธ ID ๋ฑ ํ๊ฒฝ ๋ณ์ ์ค์ ํ์ผ
โโโ .git/ # Git ๋ฒ์ ๊ด๋ฆฌ ์์คํ
๋๋ ํ ๋ฆฌ
โโโ .gitignore # Git ์ถ์ ์ ์ธ ๋ชฉ๋ก ํ์ผ
โโโ api_example.ipynb # API ์ฌ์ฉ๋ฒ ์์๋ฅผ ๋ด์ Jupyter Notebook
โโโ docker-compose.yml # Docker ๋ค์ค ์ปจํ
์ด๋ ์คํ์ ์ํ ์ค์ ํ์ผ
โโโ Dockerfile # ์ ํ๋ฆฌ์ผ์ด์
Docker ์ด๋ฏธ์ง ๋น๋ ์ค์ ํ์ผ
โโโ fire_app.py # FastAPI ์๋ฒ ์คํ ๋ฐ CLI ๋ช
๋ น์ด ์ฒ๋ฆฌ๋ฅผ ์ํ ๋ฉ์ธ ์คํฌ๋ฆฝํธ
โโโ README.md # ํ๋ก์ ํธ ์ค๋ช
๋ฌธ์ (ํ์ฌ ํ์ผ)
โโโ requirements.txt # Python ํจํค์ง ์์กด์ฑ ๋ชฉ๋ก
โโโ vector/ # ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐ RAG ๋ก์ง ๊ด๋ จ ๋๋ ํ ๋ฆฌ
โโโ data/ # ๋ฐ์ดํฐ ์ ์ฅ ๋๋ ํ ๋ฆฌ
โ โโโ chunked_docs/ # ์๋ฏธ ๊ธฐ๋ฐ์ผ๋ก ๋ถํ ๋ ๋ฌธ์ ์กฐ๊ฐ(.pkl) ์ ์ฅ ์์น
โ โโโ contextual_content_docs/ # ์ปจํ
์คํธ ์์ฝ์ด ์ถ๊ฐ๋ ๋ฌธ์ ์กฐ๊ฐ(.pkl) ์ ์ฅ ์์น
โ โโโ original_full_text/ # PDF์์ ์ถ์ถ๋ ์๋ณธ ํ
์คํธ(.txt) ์ ์ฅ ์์น
โ โโโ original_pdf/ # ๋ถ์ํ ์๋ณธ PDF ๋ฌธ์ ์ ์ฅ ์์น
โ โโโ vector_store/ # ์์ฑ๋ FAISS ๋ฒกํฐ DB(.index, .pkl) ์ ์ฅ ์์น
โโโ definition/ # RAG ํ์ดํ๋ผ์ธ์ ํต์ฌ ๋ก์ง ์ ์ ๋๋ ํ ๋ฆฌ
โโโ __pycache__/ # Python ์ปดํ์ผ ์บ์ ํ์ผ ๋๋ ํ ๋ฆฌ
โโโ arcane-footing-464017-v9-a73a60318d02.json # Google Cloud ์๋น์ค ๊ณ์ ์ธ์ฆ ํค
โโโ chain_for_chat.py # ์ผ๋ฐ ์ฑํ
RAG ์ฒด์ธ ์ ์
โโโ chain_for_form.py # ์์(Form) ๊ธฐ๋ฐ ์์ ์๋ด๋ฌธ ์์ฑ ์ฒด์ธ ์ ์
โโโ chain_for_form_chat.py # ์์ฑ๋ ์๋ด๋ฌธ์ ๋ํ ํ์ ์ง๋ฌธ ์ฒ๋ฆฌ ์ฒด์ธ ์ ์
โโโ make_context.py # ๋ฌธ์ ์กฐ๊ฐ์ ๋ํ ์ปจํ
์คํธ ์์ฝ ์์ฑ ์คํฌ๋ฆฝํธ
โโโ make_contextual_content_with_caching.py # ์บ์ฑ์ ์ ์ฉํ์ฌ ์ปจํ
์คํธ ์์ฝ์ ์์ฑํ๋ ์คํฌ๋ฆฝํธ
โโโ make_docs.py # ํ
์คํธ๋ฅผ ์๋ฏธ ๊ธฐ๋ฐ์ผ๋ก ๋ถํ ํ๋ ์คํฌ๋ฆฝํธ
โโโ make_full_text.py # PDF์์ ์ ์ฒด ํ
์คํธ๋ฅผ ์ถ์ถํ๋ ์คํฌ๋ฆฝํธ
โโโ semantic_split_genai.py # GenAI ๋ชจ๋ธ์ ์ฌ์ฉํ ์๋ฏธ ๊ธฐ๋ฐ ๋ถํ ์ ํธ๋ฆฌํฐ
โโโ semantic_split_vertex.py# Vertex AI ๋ชจ๋ธ์ ์ฌ์ฉํ ์๋ฏธ ๊ธฐ๋ฐ ๋ถํ ์ ํธ๋ฆฌํฐ
โโโ custom.py # ๋ ์จ ๊ธฐ๋ฐ ์์ ๋ถ์ ๋ฐ ๋ง์ถคํ ์์ ์๋ด๋ฌธ ์์ฑ ์ฒด์ธ
โโโ vector_store.py # FAISS ๋ฒกํฐ DB ์์ฑ, ์ ์ฅ, ๋ก๋ ๋ฐ ๊ฒ์/์ฌ์์ ๋ก์ง ๊ด๋ฆฌ
- Docker์ Docker Compose๊ฐ ์ค์น๋์ด ์์ด์ผ ํฉ๋๋ค.
- Google Cloud API ํค ๋ฐ ์๋น์ค ๊ณ์ ์ธ์ฆ ์ ๋ณด๊ฐ ํ์ํฉ๋๋ค.
-
ํ๋ก์ ํธ ๋ณต์
git clone https://github.com/singbong/safety_rag.git cd safety_rag -
ํ๊ฒฝ ๋ณ์ ์ค์ ํ๋ก์ ํธ ๋ฃจํธ ๋๋ ํฐ๋ฆฌ์
.envํ์ผ์ ์์ฑํ๊ณ ๋ค์๊ณผ ๊ฐ์ด Google ๊ด๋ จ ์ ๋ณด๋ฅผ ์ถ๊ฐํฉ๋๋ค.docker-compose.ymlํ์ผ์์ ์ด.envํ์ผ์ ์ฐธ์กฐํ์ฌ ์ปจํ ์ด๋ ๋ด์ ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํฉ๋๋ค.GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY" PROJECT_ID="YOUR_PROJECT_ID" GOOGLE_APPLICATION_CREDENTIALS="PATH/TO/YOUR/CREDENTIALS.json"
GOOGLE_APPLICATION_CREDENTIALS์ ์ง์ ๋.json์ธ์ฆ ํค ํ์ผ์ ํ๋ก์ ํธ ๋ด๋ถ์ ์์นํด์ผ ํฉ๋๋ค. (์:vector/definition/your-credentials.json) -
๋ฐ์ดํฐ ์ค๋น
vector/data/original_pdf/๋๋ ํฐ๋ฆฌ์ ๋ถ์ํ PDF ํ์ผ๋ค์ ์ถ๊ฐํฉ๋๋ค.์ฐธ๊ณ : ํ์ฌ ๊ด๋ จ PDF ํ์ผ(ํ์ฌ์ฅ ์๋ด๋, ํ์ํ ์ด๋ธ, ์ ํ๋ฒ์ค ์ดํ ์ ๋ณด ๋ฑ)์ API๋ก ์ ๋ก๋ํ๋ฉด, ์์คํ ์ด Google Cloud Vision API๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก OCR ์ฒ๋ฆฌํ์ฌ ํ ์คํธ๋ฅผ ์ถ์ถํฉ๋๋ค. ์ด๋ฅผ ํตํด AI๊ฐ ํ์ฌ์ ๊ตฌ์ฒด์ ์ธ ์ธ๋ถ์ฌํญ์ ํ์ ํ์ฌ ๋ ์ ํํ ์์ ์๋ด๋ฌธ์ ์์ฑํ ์ ์์ต๋๋ค.
-
๋ฌธ์ ์ฒ๋ฆฌ ๋ฐ ๋ฒกํฐ DB ์์ฑ ์๋ ์คํฌ๋ฆฝํธ๋ค์ ์์๋๋ก ์คํํ์ฌ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ๋ํฉ๋๋ค. ๊ฐ ๋จ๊ณ๋ Docker ์ปจํ ์ด๋ ๋ด์์ ์คํ๋ฉ๋๋ค.
- 1. ํ
์คํธ ์ถ์ถ:
pdfplumber๋ฅผ ์ฌ์ฉํ์ฌ PDF ๋ฌธ์์ ํ ์คํธ๋ฅผ ์ถ์ถํฉ๋๋ค.docker-compose run --rm app python vector/definition/make_full_text.py
- 2. ์๋ฏธ ๊ธฐ๋ฐ ๋ถํ (Chunking): ์ถ์ถ๋ ํ
์คํธ๋ฅผ ๋ฌธ์ ์กฐ๊ฐ(Chunk)์ผ๋ก ๋ถํ ํฉ๋๋ค.
docker-compose run --rm app python vector/definition/make_docs.py
- 3. ์ปจํ
์คํธ ๋ณด๊ฐ (Context Enrichment): ๊ฐ ๋ฌธ์ ์กฐ๊ฐ์ ์์ฝ ์ปจํ
์คํธ๋ฅผ ์ถ๊ฐํ์ฌ ๊ฒ์ ์ ํ๋๋ฅผ ๋์
๋๋ค.
docker-compose run --rm app python vector/definition/make_context.py
- 4. ๋ฒกํฐํ ๋ฐ ์ ์ฅ: ์ต์ข
ํ
์คํธ๋ฅผ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ณํํ๊ณ
FAISS๋ฒกํฐ ์คํ ์ด๋ฅผ ์์ฑํฉ๋๋ค. ์ด ๋จ๊ณ๋vector_store.py์create_vector_storeํจ์๋ฅผ ์ง์ ํธ์ถํด์ผ ํฉ๋๋ค.docker-compose run --rm app python -c "from vector.definition.vector_store import store_vector_db; db = store_vector_db(); db.create_vector_store(save_path='../data/vector_store/faiss_vector_db')"
- 1. ํ
์คํธ ์ถ์ถ:
-
์ ํ๋ฆฌ์ผ์ด์ ์คํ
-
API ์๋ฒ ์คํ: ์๋ ๋ช ๋ น์ด๋ก API ์๋ฒ๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํํฉ๋๋ค.
docker-compose up --build -d
API๋
http://127.0.0.1:8000์์ ์ ๊ทผํ ์ ์์ต๋๋ค. -
CLI ์ฑํ ์คํ (๋ก์ปฌ): ์ฐธ๊ณ : ํ์ฌ CLI ์ฑํ ๊ธฐ๋ฅ์ Docker ํ๊ฒฝ์์ ์ง์ ์คํํ๋ ๊ฒ์ ์ง์ํ์ง ์์ต๋๋ค. ๋ก์ปฌ Python ํ๊ฒฝ์์ ์คํํด์ผ ํฉ๋๋ค.
# ๊ฐ์ํ๊ฒฝ ํ์ฑํ # source venv/bin/activate python fire_app.py chat
-
- ํ
์คํธ ์ถ์ถ:
pdfplumber๋ฅผ ์ฌ์ฉํ์ฌ ์๋ณธ PDF์์ ํ ์คํธ์ ํ์ด์ง ๋ฒํธ ๋ฑ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํฉ๋๋ค. - OCR ์ฒ๋ฆฌ: ์ฌ์ฉ์๊ฐ API๋ก ์ ๋ก๋ํ ํ์ฌ ๊ด๋ จ PDF ํ์ผ(ํ์ฌ์ฅ ์๋ด๋, ํ์ํ ์ด๋ธ, ์ ํ๋ฒ์ค ์ดํ ์ ๋ณด ๋ฑ)์ Google Cloud Vision API๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก OCR ์ฒ๋ฆฌํ์ฌ ํ ์คํธ๋ฅผ ์ถ์ถํ๊ณ , ์ด๋ฅผ AI ๋ถ์์ ํ์ฉํฉ๋๋ค.
- ์๋ฏธ ๊ธฐ๋ฐ ๋ถํ (Semantic Chunking):
Langchain์SemanticChunker์ Google์gemini-embedding-001๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ๋ฌธ์๋ฅผ ์๋ฏธ์ ๊ฒฝ๊ณ์ ๋ฐ๋ผ 1์ฐจ์ ์ผ๋ก ๋ถํ ํฉ๋๋ค.- ๋ถํ ๋ ์ฒญํฌ๊ฐ Gemini ๋ชจ๋ธ์ ํ ํฐ ์ ํ(2048 ํ ํฐ)์ ์ด๊ณผํ ๊ฒฝ์ฐ,
RecursiveCharacterTextSplitter์ ์ ์ฌํ ๋ฐฉ์์ผ๋ก ์ถ๊ฐ ๋ถํ ํ์ฌ ๋ชจ๋ ์ฒญํฌ๊ฐ ํ ํฐ ์ ํ์ ์ค์ํ๋๋ก ํฉ๋๋ค.
- ์ปจํ
์คํธ ๋ณด๊ฐ (Contextual Enrichment) - Powered by Anthropic's Technique:
- ๋ณธ ์์คํ ์ ๊ฒ์ ์ ํ๋๋ฅผ ๊ทน๋ํํ๊ธฐ ์ํด Anthropic์ "Contextual Retrieval" ๋ ผ๋ฌธ์์ ์ ์๋ ์์ด๋์ด์ ์ฐฉ์ํ์ฌ ๊ฐ ๋ฌธ์ ์กฐ๊ฐ(Chunk)์ ํ๋ถํ ์ปจํ ์คํธ๋ฅผ ๋ถ์ฌํฉ๋๋ค.
gemini-2.5-flash-lite๋ชจ๋ธ์ด ์ ์ฒด ๋ฌธ์์ ๋งฅ๋ฝ์ ํ์ ํ์ฌ, ๊ฐ ์กฐ๊ฐ์ ํต์ฌ ๋ด์ฉ์ ์์ฝํ๋ ํค๋(Header)๋ฅผ ์์ฑํฉ๋๋ค.- ์์ฑ๋ ํค๋๋ ์๋ณธ ๋ฌธ์ ์กฐ๊ฐ์ ๋ด์ฉ๊ณผ ๊ฒฐํฉ๋์ด (์:
ํค๋: [์์ฝ ๋ด์ฉ]\n\n๋ด์ฉ: [์๋ณธ ๋ฌธ์ ์กฐ๊ฐ]) ํ๋์ ์์ฑ๋ ํ ์คํธ๋ก ๋ง๋ค์ด์ง๋๋ค. - ๋ฐ๋ก ์ด ๊ฒฐํฉ๋ ํ ์คํธ๊ฐ ์๋ฒ ๋ฉ๋์ด ๋ฒกํฐ ์คํ ์ด์ ์ ์ฅ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ๋จ์ ํค์๋ ๋งค์นญ์ ๋์ด์ ๊น์ด ์๋ ์๋ฏธ ๊ธฐ๋ฐ ๊ฒ์์ด ๊ฐ๋ฅํด์ง๋ฉฐ, ์ฌ์ฉ์์ ์ง๋ฌธ ์๋์ ๊ฐ์ฅ ๋ถํฉํ๋ ์ ๋ณด๋ฅผ ์ ํํ๊ฒ ์ฐพ์๋ผ ์ ์์ต๋๋ค.
- ์ด ๊ณผ์ ์์ Google GenAI์ Caching API๋ฅผ ํ์ฉํ์ฌ ์ ์ฒด ๋ฌธ์ ํ ์คํธ๋ฅผ ์บ์์ ์ ์ฅํจ์ผ๋ก์จ, ๋ฐ๋ณต์ ์ธ API ํธ์ถ ๋น์ฉ๊ณผ ์๊ฐ์ ์ ์ฝํฉ๋๋ค.
- ๋ฒกํฐํ:
- ์ปจํ
์คํธ๊ฐ ๋ณด๊ฐ๋ ํ
์คํธ(
"์ปจํ ์คํธ ์์ฝ : ์๋ณธ ์ฒญํฌ ๋ด์ฉ")๋ฅผgemini-embedding-001๋ชจ๋ธ์ ํตํด ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ณํํฉ๋๋ค. - ์์ฑ๋ ๋ฒกํฐ๋
FAISS(IndexFlatL2)๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ฉ๋๋ค.
- ์ปจํ
์คํธ๊ฐ ๋ณด๊ฐ๋ ํ
์คํธ(
- ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์ (Hybrid Search):
Langchain์EnsembleRetriever๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๊ฐ์ง ๊ฒ์ ๋ฐฉ์์ ๊ฒฐํฉํฉ๋๋ค.- ์๋ฏธ ๊ฒ์ (Semantic Search): FAISS ๋ฒกํฐ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ๊ณผ ์๋ฏธ์ ์ผ๋ก ์ ์ฌํ ๋ฌธ์๋ฅผ ์ฐพ์ต๋๋ค. (๊ฐ์ค์น 70%)
- ํค์๋ ๊ฒ์ (Keyword Search):
BM25Retriever๋ฅผ ์ฌ์ฉํ์ฌ ์ง๋ฌธ์ ํฌํจ๋ ํต์ฌ ํค์๋์ ์ผ์นํ๋ ๋ฌธ์๋ฅผ ์ฐพ์ต๋๋ค. (๊ฐ์ค์น 30%) - ์ด ๋ ๊ฐ์ง ๋ฐฉ์์ ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐํฉํ์ฌ ์ด๊ธฐ ๊ฒ์ ๊ฒฐ๊ณผ(150๊ฐ)๋ฅผ ์์ฑํฉ๋๋ค.
- ์ฌ์์ (Reranking):
- ์ด๊ธฐ ๊ฒ์๋ 150๊ฐ์ ๋ฌธ์๋ฅผ
VertexAIRank๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ง๋ฌธ๊ณผ์ ๊ด๋ จ์ฑ์ด ๋์ ์์ผ๋ก ์ฌ์ ๋ ฌํฉ๋๋ค. VertexAIRank๋ Google Cloud Vertex AI์์ ์ ๊ณตํ๋ ์๋ฏธ ๊ธฐ๋ฐ ๋ญํน ์๋น์ค๋ก,semantic-ranker-default-004๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ์ ๋ฌธ์ ๊ฐ์ ์๋ฏธ์ ์ ์ฌ๋๋ฅผ ์ ๋ฐํ๊ฒ ํ๊ฐํฉ๋๋ค.- ์ต์ข ์ ์ผ๋ก ๊ฐ์ฅ ๊ด๋ จ์ฑ์ด ๋์ ์์ 20๊ฐ์ ๋ฌธ์๋ฅผ ๋ต๋ณ ์์ฑ์ ์ฌ์ฉํฉ๋๋ค.
- ์ด๊ธฐ ๊ฒ์๋ 150๊ฐ์ ๋ฌธ์๋ฅผ
์ด ํ๋ก์ ํธ๋ LangGraph๋ฅผ ์ฌ์ฉํ์ฌ ์ธ ๊ฐ์ง ๋ค๋ฅธ ๋ชฉ์ ์ RAG(๊ฒ์ ์ฆ๊ฐ ์์ฑ) ์ฒด์ธ์ ๊ตฌํํฉ๋๋ค. ๊ฐ ์ฒด์ธ์ ํน์ ์๋๋ฆฌ์ค์ ๋ง์ถฐ์ง ๋ ธ๋(Node)๋ค์ ๊ทธ๋ํ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ํ๊ฐ(Hallucination) ํ์์ ์ต์ํํ๊ธฐ ์ํด Vertex AI์ Grounding ๊ธฐ๋ฅ์ ํ์ฉํฉ๋๋ค.
์ผ๋ฐ์ ์ธ ๋ํํ ์ง์์๋ต์ ์ฒ๋ฆฌํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ RAG ์ฒด์ธ์ ๋๋ค.
- ์ฃผ์ ์ญํ : ์ฌ์ฉ์์ ์ง๋ฌธ ์๋๋ฅผ ๋ช ํํ ํ๊ณ , ๊ด๋ จ ๋ฌธ์๋ฅผ ์ฐพ์ ์ ๋ขฐ๋ ๋์ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
- ์๋ ํ๋ฆ:
- ์ง๋ฌธ ์ฌ์์ฑ (Re-writer): ๋ํ ๊ธฐ๋ก์ ์ฐธ๊ณ ํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ์ ํฌํจ๋ ๋๋ช ์ฌ(์: '๊ทธ๊ฒ')๋ฅผ ๊ตฌ์ฒด์ ์ธ ์ฉ์ด๋ก ๋ฐ๊พธ์ด ๋ช ํํ๊ฒ ๋ง๋ญ๋๋ค.
- ์ง๋ฌธ ๋ถํด (Question Decomposer): ๋ณต์กํ ์ง๋ฌธ์ ์ฌ๋ฌ ๊ฐ์ ๋จ์ํ ํ์ ์ง๋ฌธ์ผ๋ก ๋ถํดํ์ฌ ๊ฒ์ ์ ํ๋๋ฅผ ๋์ ๋๋ค.
- ๋ฌธ์ ๊ฒ์ (Search Document): ๋ถํด๋ ์ง๋ฌธ๋ค์ ์ฌ์ฉํ์ฌ ๋ฒกํฐ ์คํ ์ด์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ๊ฒ์ํ๊ณ , Reranker๋ฅผ ํตํด ์ต์ข ๋ต๋ณ์ ์ฌ์ฉํ ๋ฌธ์์ ์์๋ฅผ ์ฌ์กฐ์ ํฉ๋๋ค.
- ๋ต๋ณ ์์ฑ (Generator): ๊ฒ์๋ ๋ฌธ์๋ฅผ ๋ฐํ์ผ๋ก '์์ ์งํค๋ฏธ AI' ์ญํ ์ ์ํํ๋ฉฐ, ์ง๋ฌธ์ ๋ํ ์ง์ ์ ์ธ ๋ต๋ณ๊ณผ ํจ๊ป ์์๋๋ฉด ์ข์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์์ฑํฉ๋๋ค.
- Grounding ํ์ธ (Hallucination Checker): ์์ฑ๋ ๋ต๋ณ์ด ๊ฒ์๋ ๋ฌธ์์ ๊ทผ๊ฑฐํ๋์ง ํ์ธํ๊ณ , ์ธ์ฉ(Citation)์ ์ถ๊ฐํฉ๋๋ค.
- ๋ต๋ณ ์ ์ (Answer Beautifier): ์ต์ข ๋ต๋ณ์ ์ฌ์ฉ์๊ฐ ์ฝ๊ธฐ ์ฝ๋๋ก ๋งํฌ๋ค์ด ํ์์ผ๋ก ์ ๋ฆฌํฉ๋๋ค.
์ฌ์ฉ์๊ฐ ์น ์์์ ํตํด ์ ๋ ฅํ ํ์ฌ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๋ง์ถคํ ์์ ์๋ด๋ฌธ์ ์์ฑํ๋ ์ฒด์ธ์ ๋๋ค.
- ์ฃผ์ ์ญํ : ํ์ฌ ์ ๋ณด์ ์ ์ฌ์ ์ํ ์์๋ฅผ ๋ถ์ํ์ฌ, ๋ฐฉ๋ฌธ๊ฐ์ ์ํ ์์ธํ๊ณ ์ค์ฉ์ ์ธ ์์ ๊ฐ์ด๋๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
- ์๋ ํ๋ฆ:
- ๋ค๊ฐ์ ๊ฒ์์ด ์์ฑ (Query Generator): ํ์ฌ๋ช , ์ ํ, ๊ธฐ๊ฐ, ์ฅ์, OCR ์ฒ๋ฆฌ๋ ๊ด๋ จ ๋ฌธ์ ๋ด์ฉ ๋ฑ์ ์กฐํฉํ์ฌ ๋ฐ์ ๊ฐ๋ฅํ ๋ชจ๋ ์ํ ์๋๋ฆฌ์ค(์: "์ฌ๋ฆ์ฒ ์ผ์ธ ํ์ฌ ์์ค๋ ์๋ฐฉ", "๊ณต์ฐ์ฅ ์์ฌ ์ฌ๊ณ ์๋ฐฉ")์ ๋ํ ๊ฒ์์ด๋ฅผ ์์ฑํฉ๋๋ค.
- ๋ฌธ์ ๊ฒ์ (Search Document): ์์ฑ๋ ๊ฒ์์ด๋ค์ ์ฌ์ฉํ์ฌ ์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ุฌุงู ุน์ ์ผ๋ก ๊ฒ์ํฉ๋๋ค.
- ์๋ด๋ฌธ ์์ฑ (Generator): '์์ ์ ๋ฌธ๊ฐ' ์ญํ ์ ์ํํ๋ฉฐ, ๊ฒ์๋ ๋ฌธ์์ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ํ์ฌ ์ ๋ณด๋ฅผ ์ข ํฉํ์ฌ ์ฒด๊ณ์ ์ธ ๊ตฌ์กฐ์ ๋ง์ถคํ ์์ ์๋ด๋ฌธ์ ์์ฑํฉ๋๋ค.
- Grounding ํ์ธ ๋ฐ ์ ์ : ์์ฑ๋ ์๋ด๋ฌธ์ ์ ๋ขฐ๋๋ฅผ ํ์ธํ๊ณ ์ต์ข ๋ณธ์ ๋งํฌ๋ค์ด ํ์์ผ๋ก ๋ค๋ฌ์ต๋๋ค.
chain_for_form.py์ ์ํด ์์ฑ๋ ์์ ์๋ด๋ฌธ์ ๋ํด ์ฌ์ฉ์๊ฐ ์ถ๊ฐ๋ก ์ง๋ฌธํ ๊ฒฝ์ฐ, ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ํนํ๋ ์ฑํ
์ฒด์ธ์
๋๋ค.
- ์ฃผ์ ์ญํ : ๊ธฐ์กด์ ์์ฑ๋ ์๋ด๋ฌธ๊ณผ ๋ํ์ ๋งฅ๋ฝ์ ์ดํดํ๊ณ ํ์ ์ง๋ฌธ์ ์ ํํ๊ฒ ๋ต๋ณํฉ๋๋ค.
- ์๋ ํ๋ฆ:
- ์ง๋ฌธ ์ฌ์์ฑ (Re-writer): ์ฌ์ฉ์์ ํ์ ์ง๋ฌธ(์: "๊ฑฐ๊ธฐ์ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ด ์ ์ค์ํ๊ฐ์?")์ ์ด์ ์ ์์ฑ๋ ์๋ด๋ฌธ๊ณผ ๋ํ ๊ธฐ๋ก์ ๋ฐํ์ผ๋ก "ํ์ฌ ๋ฐ์ ์ ์ ์ํ ๋ํผ๊ฐ ์ ๊ฐ์ฅ ์ค์ํ๊ฐ์?"์ ๊ฐ์ด ๋ช ํํ ์ง๋ฌธ์ผ๋ก ์ฌ์์ฑํฉ๋๋ค.
- ์๋ธ ์ฟผ๋ฆฌ ์์ฑ (Query Generator): ์ฌ์์ฑ๋ ์ง๋ฌธ์ ๋ฐํ์ผ๋ก, ๋ต๋ณ์ ํ์ํ ๋ฐฐ๊ฒฝ ์ ๋ณด, ์๋ฐฉ๋ฒ, ๊ด๋ จ ์ฌ๋ก ๋ฑ์ ์ฐพ๊ธฐ ์ํ ์ถ๊ฐ ๊ฒ์์ด๋ค์ ์์ฑํฉ๋๋ค.
- ๋ฌธ์ ๊ฒ์, ๋ต๋ณ ์์ฑ, Grounding ๋ฐ ์ ์ :
chain_for_chat๊ณผ ์ ์ฌํ ๊ณผ์ ์ ๊ฑฐ์ณ ์ฌ์ฉ์์ ํ์ ์ง๋ฌธ์ ๋ํ ์์ธํ๊ณ ์ ํํ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
custom.py๋ ์ด ์์คํ
์ ํต์ฌ ๊ธฐ๋ฅ ์ค ํ๋๋ก, Google Geocoding API์ Open-Meteo ๋ ์จ API๋ฅผ ํ์ฉํ์ฌ ํ์ฌ ์ฅ์์ ์ค์๊ฐ ๋ ์จ ์ ๋ณด๋ฅผ ๋ถ์ํ๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ ์จ ๊ด๋ จ ์์ ์ํ ์์๋ฅผ ์์ธกํ๋ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
- LangGraph ๊ธฐ๋ฐ ๋ณตํฉ ์ฒด์ธ: ์ฌ๋ฌ ๋จ๊ณ์ ๋ ธ๋๋ค์ด ์์ฐจ์ ์ผ๋ก ์ฐ๊ฒฐ๋์ด ๋ณต์กํ ์์ ๋ถ์์ ์ํํฉ๋๋ค.
- ์ค์๊ฐ ๋ ์จ ๋ฐ์ดํฐ ํตํฉ: ํ์ฌ ์ฅ์์ 24์๊ฐ ๋ ์จ ์๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ์์งํ์ฌ ๋ถ์์ ํ์ฉํฉ๋๋ค.
- ๋ค์ค ๋ฒกํฐ ์คํ ์ด ํ์ฉ: ์์ ๋ฌธ์, ๋ ์จ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก, ํ์ฌ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก๋ฅผ ๊ฐ๊ฐ ๋ณ๋์ ๋ฒกํฐ ์คํ ์ด์์ ๊ฒ์ํฉ๋๋ค.
- Vertex AI Grounding: ์์ฑ๋ ์์ ์๋ด๋ฌธ์ ์ ๋ขฐ๋๋ฅผ Vertex AI์ Grounding ๊ธฐ๋ฅ์ผ๋ก ๊ฒ์ฆํฉ๋๋ค.
1. **ํ์ฌ ์ ๋ณด ๊ธฐ๋ฐ ๊ฒ์์ด ์์ฑ**:
- `festival_query_generator`: ํ์ฌ ์ ํ, ์ฅ์, ๊ท๋ชจ, OCR ์ฒ๋ฆฌ๋ ๊ด๋ จ ๋ฌธ์ ๋ด์ฉ ๋ฑ์ ๋ถ์ํ์ฌ ํ์ฌ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก ๊ฒ์์ด ์์ฑ
- `weather_query_generator`: ํ์ฌ ์ฅ์์ ์ค์๊ฐ ๋ ์จ ์ ๋ณด๋ฅผ ๋ถ์ํ์ฌ ๋ ์จ ๊ด๋ จ ์ํ ์์ ๊ฒ์์ด ์์ฑ
- `safety_query_generator`: ํ์ฌ ์ ๋ณด์ OCR ์ฒ๋ฆฌ๋ ๋ฌธ์ ๋ด์ฉ์ ์ข
ํฉํ์ฌ ์ผ๋ฐ์ ์ธ ์์ ์ํ ์์ ๊ฒ์์ด ์์ฑ
-
๋ค์ค ๋ฒกํฐ ์คํ ์ด ๊ฒ์:
search_festival_document: ํ์ฌ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก ๋ฒกํฐ ์คํ ์ด์์ ์ ์ฌ ์ฌ๋ก ๊ฒ์search_weather_document: ๋ ์จ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก ๋ฒกํฐ ์คํ ์ด์์ ์ ์ฌ ๊ธฐ์ ์กฐ๊ฑด์ ์ฌ๊ณ ์ฌ๋ก ๊ฒ์search_safety_document: ์ผ๋ฐ ์์ ๋ฌธ์ ๋ฒกํฐ ์คํ ์ด์์ ๊ด๋ จ ์์ ์ ๋ณด ๊ฒ์
-
๋ง์ถคํ ์์ ์๋ด๋ฌธ ์์ฑ:
generator: ๊ฒ์๋ ๋ชจ๋ ์ ๋ณด๋ฅผ ์ข ํฉํ์ฌ ํ์ฌ ํน์ฑ๊ณผ ํ์ฌ ๋ ์จ์ ์ต์ ํ๋ ์์ ์๋ด๋ฌธ ์์ฑhallu_checker: Vertex AI Grounding์ ํตํด ์์ฑ๋ ์๋ด๋ฌธ์ ์ ๋ขฐ๋ ๊ฒ์ฆ ๋ฐ ์ธ์ฉ ์ถ๊ฐ
- ์ง๋ฆฌ ์ขํ ๋ณํ: Google Geocoding API๋ฅผ ์ฌ์ฉํ์ฌ ํ์ฌ ์ฅ์๋ช ์ ์๋/๊ฒฝ๋ ์ขํ๋ก ๋ณํ
- 24์๊ฐ ๋ ์จ ์๋ณด ์์ง: Open-Meteo API๋ฅผ ํตํด ์์ธํ ๋ ์จ ์ ๋ณด ์์ง (๊ธฐ์จ, ๊ฐ์๋, ํ์, ์ต๋, UV ์ง์ ๋ฑ)
- LLM ๊ธฐ๋ฐ ๋ ์จ ๋ถ์: Gemini 2.5 Flash Lite ๋ชจ๋ธ์ด ๋ ์จ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌธ์ ์ผ๋ก ๋ถ์ํ์ฌ ์ํ ์์ ์ถ์ถ
- ๋ ์จ ๊ธฐ๋ฐ ๊ฒ์์ด ์์ฑ: ๋ถ์๋ ๋ ์จ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ ์ฌํ ๊ธฐ์ ์กฐ๊ฑด์ ์ฌ๊ณ ์ฌ๋ก๋ฅผ ์ฐพ๊ธฐ ์ํ ๊ฒ์์ด ์๋ ์์ฑ
GEOCODING_API="YOUR_GOOGLE_GEOCODING_API_KEY"- ์ฌ๋ฆ์ฒ ์ผ์ธ ํ์ฌ: ํญ์ผ, ๊ตญ์ง์ฑ ํธ์ฐ, ๋์ UV ์ง์ ๋ฑ์ ๋ํ ์ฌ์ ๊ฒฝ๊ณ ๋ฐ ๋๋น ๋ฐฉ์ ์ ๊ณต
- ๊ฒจ์ธ์ฒ ์ค์ธ ํ์ฌ: ํํ, ๊ฐ์ค, ๋นํ๊ธธ ๋ฑ์ ๋ํ ์์ ์์น ์๋ด
- ๋ด/๊ฐ์ ํ์ฌ: ์ผ๊ต์ฐจ, ๊ฐํ, ๋ฏธ์ธ๋จผ์ง ๋ฑ ๊ณ์ ๋ณ ํน์ ๊ธฐ์ ์กฐ๊ฑด์ ๋ํ ์ฃผ์์ฌํญ ์ ๊ณต
- ์ค์๊ฐ ๊ธฐ์ ๋ณํ ๋์: ํ์ฌ ๋น์ผ ๊ธ๋ณํ๋ ๋ ์จ์ ๋ํ ์ฆ๊ฐ์ ์ธ ์์ ์ ๋ณด ์ ๋ฐ์ดํธ
์ด ๊ธฐ๋ฅ์ ํตํด ํ์ฌ ์ฃผ์ต์๋ ๋จ์ํ ํ์ฌ ์ ๋ณด๋ง์ผ๋ก๋ ๋ ์จ์ ํนํ๋ ๋ง์ถคํ ์์ ์๋ด๋ฌธ์ ์๋์ผ๋ก ์์ฑํ ์ ์์ผ๋ฉฐ, ๋ฐฉ๋ฌธ๊ฐ๋ค์ ํ์ฌ ๊ธฐ์ ์กฐ๊ฑด์ ์ต์ ํ๋ ์์ ์์น์ ๋ฐ์๋ณผ ์ ์์ต๋๋ค.
์ด ์์คํ
์ FastAPI๋ฅผ ํตํด 4๊ฐ์ ์ฃผ์ API ์๋ํฌ์ธํธ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ API๋ http://<YOUR_SERVER_IP>:<PORT> ์ฃผ์๋ก ์์ฒญํ ์ ์์ต๋๋ค.
์ผ๋ฐ์ ์ธ ์ง์์๋ต์ ์ํ API์ ๋๋ค.
- Python ์ฝ๋ ์์ :
import requests chat_url = "http://127.0.0.1:8000/api/chat" # ์ค์ ์๋ฒ ์ฃผ์๋ก ๋ณ๊ฒฝ ํ์ chat_payload = { "question": "์ง์ง ๋ฐ์ ์ ํ๋ ์๋ น ์๋ ค์ค", "session_id": "user123_session_abc" } try: response = requests.post(chat_url, json=chat_payload) response.raise_for_status() # 200๋ฒ๋ ์๋ต์ด ์๋๋ฉด ์๋ฌ ๋ฐ์ chat_result = response.json() print("--- ์ต์ข ๋ต๋ณ ---") print(chat_result.get('final_answer')) except requests.exceptions.RequestException as e: print(f"API ์์ฒญ ์คํจ: {e}")
์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์์(Form) ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ง์ถคํ ์์ ์๋ด๋ฌธ์ ์์ฑํฉ๋๋ค.
์ฐธ๊ณ : related_documents ํ๋์๋ ํ์ฌ ๊ด๋ จ PDF ํ์ผ(ํ์ฌ์ฅ ์๋ด๋, ํ์ํ
์ด๋ธ, ์
ํ๋ฒ์ค ์ดํ ์ ๋ณด ๋ฑ)์ ์
๋ก๋ํ๋ฉด, ์์คํ
์ด Google Cloud Vision API๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก OCR ์ฒ๋ฆฌํ์ฌ ํ
์คํธ๋ฅผ ์ถ์ถํฉ๋๋ค. ์ด ์ ๋ณด๋ AI๊ฐ ํ์ฌ์ ๊ตฌ์ฒด์ ์ธ ์ธ๋ถ์ฌํญ์ ํ์
ํ์ฌ ๋ ์ ํํ ์์ ์๋ด๋ฌธ์ ์์ฑํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค.
- Python ์ฝ๋ ์์ :
import requests form_url = "http://127.0.0.1:8000/api/generate_form" # ์ค์ ์๋ฒ ์ฃผ์๋ก ๋ณ๊ฒฝ ํ์ form_payload = { "place_name": "2025 ํ๊ฐ ์ฌ๋ฆ ๋ฎค์ง ํ์คํฐ๋ฒ", "type": "๋๊ท๋ชจ ์ผ์ธ ๊ณต์ฐ", "region": "์์ธ, ์ฌ์๋ ํ๊ฐ๊ณต์", "period": "2025๋ 8์ 8์ผ ~ 2025๋ 8์ 10์ผ", "description": "๋จ๊ฑฐ์ด ์ฌ๋ฆ๋ฐค์ ์ํ์ค ๋ํ๋ฏผ๊ตญ ์ต๊ณ ์ ๋ฎค์ง ํ์คํฐ๋ฒ! ๋ค์ํ ์ฅ๋ฅด์ ์ํฐ์คํธ๋ค๊ณผ ํจ๊ปํ๋ 3์ผ๊ฐ์ ์ถ์ . ํธ๋ํธ๋ญ ์กด๊ณผ ์ฒดํ ์ด๋ฒคํธ๋ ์ค๋น๋์ด ์์ต๋๋ค.", "category": "์์ /ํ์คํฐ๋ฒ", "related_documents": "festival_guide.pdf", # PDF ํ์ผ ์ ๋ก๋ "emergency_contact_name": "์ข ํฉ์ํฉ์ค ์์ ๊ด๋ฆฌํ", "emergency_contact_phone": "02-123-4567" } try: response = requests.post(form_url, json=form_payload) response.raise_for_status() form_result = response.json() print("--- ์์ฑ๋ ์์ ์๋ด๋ฌธ ---") print(form_result.get('final_answer')) # ํ์ ์ง๋ฌธ์ ์ํด ์์ฑ๋ ์๋ด๋ฌธ์ ๋ณ์์ ์ ์ฅ generated_form_content = form_result.get('final_answer') except requests.exceptions.RequestException as e: print(f"API ์์ฒญ ์คํจ: {e}")
generate_form์ผ๋ก ์์ฑ๋ ์์ ์๋ด๋ฌธ์ ๋ํ ํ์ ์ง๋ฌธ์ ์ฒ๋ฆฌํฉ๋๋ค.
- Python ์ฝ๋ ์์ :
import requests # 'generate_form' API ํธ์ถ ํ ๋ฐํ๋ 'final_answer' ๊ฐ์ ์ฌ์ฉํฉ๋๋ค. # ์์์์๋ ์ ์ฝ๋ ๋ธ๋ก์ 'generated_form_content' ๋ณ์๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. # generated_form_content = "..." # ์ค์ ๋ก๋ ์ด์ API ํธ์ถ ๊ฒฐ๊ณผ if 'generated_form_content' in locals(): form_chat_url = "http://127.0.0.1:8000/api/form_chat" # ์ค์ ์๋ฒ ์ฃผ์๋ก ๋ณ๊ฒฝ ํ์ form_chat_payload = { "generated_form": generated_form_content, "query": "์จ์ด์งํ ์ฆ์์ผ๋ก๋ ๊ตฌ์ฒด์ ์ผ๋ก ์ด๋ค ๊ฒ๋ค์ด ์๋์?", "session_id": "user123_session_abc" } try: response = requests.post(form_chat_url, json=form_chat_payload) response.raise_for_status() form_chat_result = response.json() print("--- ํ์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ ---") print(form_chat_result.get('final_answer')) except requests.exceptions.RequestException as e: print(f"API ์์ฒญ ์คํจ: {e}") else: print("๋จผ์ /api/generate_form ์ ํธ์ถํ์ฌ 'generated_form_content'๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.")
custom.py์ ๊ตฌํ๋ ๋ ์จ ๊ธฐ๋ฐ ์์ ๋ถ์ ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ํ์ฌ ์ ๋ณด์ ์ค์๊ฐ ๋ ์จ ๋ฐ์ดํฐ๋ฅผ ์ข
ํฉํ ๋ง์ถคํ ์์ ์๋ด๋ฌธ์ ์์ฑํฉ๋๋ค. ์ด API๋ Google Geocoding API์ Open-Meteo ๋ ์จ API๋ฅผ ์๋์ผ๋ก ํธ์ถํ์ฌ ํ์ฌ ์ฅ์์ 24์๊ฐ ๋ ์จ ์๋ณด๋ฅผ ๋ถ์ํ๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ ์จ ๊ด๋ จ ์ํ ์์๋ฅผ ์์ธกํฉ๋๋ค.
์ฐธ๊ณ : related_documents ํ๋์๋ ํ์ฌ ๊ด๋ จ PDF ํ์ผ(ํ์ฌ์ฅ ์๋ด๋, ํ์ํ
์ด๋ธ, ์
ํ๋ฒ์ค ์ดํ ์ ๋ณด ๋ฑ)์ ์
๋ก๋ํ๋ฉด, ์์คํ
์ด Google Cloud Vision API๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ผ๋ก OCR ์ฒ๋ฆฌํ์ฌ ํ
์คํธ๋ฅผ ์ถ์ถํฉ๋๋ค. ์ด ์ ๋ณด๋ AI๊ฐ ํ์ฌ์ ๊ตฌ์ฒด์ ์ธ ์ธ๋ถ์ฌํญ์ ํ์
ํ์ฌ ๋ ์ ํํ ์์ ์๋ด๋ฌธ์ ์์ฑํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค.
-
Python ์ฝ๋ ์์ :
import requests custom_url = "http://127.0.0.1:8000/api/custom" # ์ค์ ์๋ฒ ์ฃผ์๋ก ๋ณ๊ฒฝ ํ์ custom_payload = { "place_name": "2025 ํ๊ฐ ์ฌ๋ฆ ๋ฎค์ง ํ์คํฐ๋ฒ", "type": "๋๊ท๋ชจ ์ผ์ธ ๊ณต์ฐ", "location": "์์ธ, ์ฌ์๋ ํ๊ฐ๊ณต์", "period": "2025๋ 8์ 8์ผ ~ 2025๋ 8์ 10์ผ", "description": "๋จ๊ฑฐ์ด ์ฌ๋ฆ๋ฐค์ ์ํ์ค ๋ํ๋ฏผ๊ตญ ์ต๊ณ ์ ๋ฎค์ง ํ์คํฐ๋ฒ! ๋ค์ํ ์ฅ๋ฅด์ ์ํฐ์คํธ๋ค๊ณผ ํจ๊ปํ๋ 3์ผ๊ฐ์ ์ถ์ . ํธ๋ํธ๋ญ ์กด๊ณผ ์ฒดํ ์ด๋ฒคํธ๋ ์ค๋น๋์ด ์์ต๋๋ค.", "category": "์์ /ํ์คํฐ๋ฒ", "related_documents": "festival_guide.pdf", # PDF ํ์ผ ์ ๋ก๋ "emergency_contact_name": "์ข ํฉ์ํฉ์ค ์์ ๊ด๋ฆฌํ", "emergency_contact_phone": "02-123-4567", "expected_attendees": "50000" } try: response = requests.post(custom_url, json=custom_payload) response.raise_for_status() custom_result = response.json() print("--- ๋ ์จ ๊ธฐ๋ฐ ๋ง์ถคํ ์์ ์๋ด๋ฌธ ---") print(custom_result.get('generation')) # Grounding ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ ํ์ธ ๊ฐ๋ฅ if 'hallu_check' in custom_result: print("\n--- Grounding ๊ฒ์ฆ ๊ฒฐ๊ณผ ---") print(f"์ ๋ขฐ๋: {custom_result['hallu_check'].get('answer_with_citations', 'N/A')}") except requests.exceptions.RequestException as e: print(f"API ์์ฒญ ์คํจ: {e}")
-
์ฃผ์ ํน์ง:
- ์ค์๊ฐ ๋ ์จ ๋ถ์: ํ์ฌ ์ฅ์์ 24์๊ฐ ๋ ์จ ์๋ณด๋ฅผ ์๋์ผ๋ก ์์งํ๊ณ ๋ถ์
- ๋ค์ค ๋ฒกํฐ ์คํ ์ด ๊ฒ์: ์์ ๋ฌธ์, ๋ ์จ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก, ํ์ฌ ๊ด๋ จ ์ฌ๊ณ ์ฌ๋ก๋ฅผ ๊ฐ๊ฐ ๋ณ๋ ๊ฒ์
- Vertex AI Grounding: ์์ฑ๋ ์๋ด๋ฌธ์ ์ ๋ขฐ๋๋ฅผ ์๋์ผ๋ก ๊ฒ์ฆ
- ๋ง์ถคํ ์ํ ์์ ์์ธก: ํ์ฌ ํน์ฑ๊ณผ ํ์ฌ ๋ ์จ๋ฅผ ์ข ํฉํ์ฌ ๊ตฌ์ฒด์ ์ธ ์ํ ์์ ๋ถ์
-
ํ์ ํ๊ฒฝ ๋ณ์:
GEOCODING_API="YOUR_GOOGLE_GEOCODING_API_KEY"
-
์๋ต ํ์:
{ "generation": "์์ฑ๋ ๋ ์จ ๊ธฐ๋ฐ ๋ง์ถคํ ์์ ์๋ด๋ฌธ", "hallu_check": { "answer_with_citations": "Grounding ๊ฒ์ฆ์ด ์๋ฃ๋ ์๋ด๋ฌธ (์ธ์ฉ ํฌํจ)" }, "weather_summary": "๋ถ์๋ ๋ ์จ ์์ฝ ์ ๋ณด", "festival_query_list": ["์์ฑ๋ ํ์ฌ ๊ด๋ จ ๊ฒ์์ด ๋ชฉ๋ก"], "weather_query_list": ["์์ฑ๋ ๋ ์จ ๊ด๋ จ ๊ฒ์์ด ๋ชฉ๋ก"], "safety_query_list": ["์์ฑ๋ ์์ ๊ด๋ จ ๊ฒ์์ด ๋ชฉ๋ก"] }