kwak513's picture
Update app.py
ad95940 verified
import os
import time
import json
import psycopg2
from typing import Dict, List
# from dotenv import load_dotenv
# FastAPI ๋ฐ slowapi ๊ด€๋ จ ๋ชจ๋“ˆ
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from fastapi.middleware.cors import CORSMiddleware
# Pydantic ๋ชจ๋ธ
from pydantic import BaseModel
# LangChain ๊ด€๋ จ ๋ชจ๋“ˆ
from langchain_google_genai import ChatGoogleGenerativeAI
# from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import PGVector
from langchain_core.messages import SystemMessage
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from langchain_core.documents import Document # Document ํƒ€์ž… ํžŒํŠธ์šฉ์œผ๋กœ ์ถ”๊ฐ€
# from pdf_importer import create_vector_store, CONNECTION_STRING, COLLECTION_NAME
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ (Hugging Face Secrets์—์„œ ๊ฐ€์ ธ์˜ด)
POSTGRES_USER = os.getenv('POSTGRES_USER')
POSTGRES_PASSWORD = os.getenv('POSTGRES_PASSWORD')
POSTGRES_HOST = os.getenv('POSTGRES_HOST')
POSTGRES_PORT = os.getenv('POSTGRES_PORT')
POSTGRES_DB = os.getenv('POSTGRES_DB')
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
COLLECTION_NAME = "homepage_pdfplumner_1st"
SENTENCE_TRANSFORMERS_HOME = os.getenv('SENTENCE_TRANSFORMERS_HOME', '/app/.cache')
# 2. ํ•„์ˆ˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ๋ชจ๋‘ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
if not all([POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, GOOGLE_API_KEY, SENTENCE_TRANSFORMERS_HOME]):
raise ValueError("ํ•„์ˆ˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋“ค์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. Hugging Face Secrets๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์กฐํ•ฉํ•˜์—ฌ CONNECTION_STRING์„ ์ƒ์„ฑ
CONNECTION_STRING = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
# load_dotenv()
app = FastAPI()
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# RAG ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์ดˆ๊ธฐํ™”
embeddings = HuggingFaceEmbeddings(
model_name='nlpai-lab/KURE-v1',
model_kwargs={'device': 'cpu'}
)
try:
vector_store = PGVector(
collection_name=COLLECTION_NAME,
connection_string=CONNECTION_STRING,
embedding_function=embeddings
)
print("Vector store loaded from PostgreSQL.")
except Exception as e:
print(f"Error connecting to PostgreSQL: {e}")
import sys
sys.exit(1)
llm = ChatGoogleGenerativeAI(
# model="gemini-1.5-flash-8b",
model="gemini-2.5-flash-lite",
model_kwargs={
"system_instruction": SystemMessage(
content=
# """๋‹น์‹ ์€ ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต(์„œ์šธ) ํ•™์‚ฌ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ๋‹ต๋ณ€ ์›์น™: 1. ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต(์„œ์šธ) ๊ด€๋ จ ์งˆ๋ฌธ์— ์ •ํ™•ํžˆ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค. 2. ์ด์ „ ๋Œ€ํ™” ๋งฅ๋ฝ์„ ๊ธฐ์–ตํ•˜๊ณ  ์œ ์—ฐํ•˜๊ฒŒ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค. 3. ์นœ์ ˆํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ๋งํˆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋ฐ˜๋“œ์‹œ ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค. 4. ์ฐธ๊ณ  ์ •๋ณด์— ์—†๋Š” ๋‚ด์šฉ์€ ์ ˆ๋Œ€ ์ถ”์ธกํ•˜๊ฑฐ๋‚˜ ์ž„์˜๋กœ ๋‹ต๋ณ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ต๋ณ€ ๊ทœ์น™: - ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต(์„œ์šธ)๊ณผ ๊ด€๋ จ ์—†๋Š” ์งˆ๋ฌธ: "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต(์„œ์šธ) ๊ด€๋ จ ์งˆ๋ฌธ์—๋งŒ ๋‹ต๋ณ€๋“œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”. - ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ๋œ ์ •๋ณด๊ฐ€ ์ฐธ๊ณ  ๋ฌธ์„œ์— ๋ช…ํ™•ํ•˜๊ฒŒ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ์–ด๋–ค ๋‚ด์šฉ๋„ ์ถ”๋ก ํ•˜๊ฑฐ๋‚˜ ๋ง๋ถ™์ด์ง€ ๋ง๊ณ  ๋ฌด์กฐ๊ฑด "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”."""
"""
๋‹น์‹ ์€ ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต(์„œ์šธ)์˜ **'ํ•™์‚ฌ ์ƒํ™œ AI ์–ด๋“œ๋ฐ”์ด์ €'**์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์ง€์‹์€ ์ฃผ์–ด์ง„ [ํ•™์‚ฌ ๊ทœ์ •]๊ณผ [์ฃผ๋ณ€ ์ƒ๊ถŒ ์ •๋ณด] ๋ฌธ์„œ๋กœ ํ•œ์ •๋ฉ๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์ž„๋ฌด๋Š” ์ด ์ง€์‹ ๋‚ด์—์„œ ํ•™์ƒ๋“ค์˜ ์งˆ๋ฌธ์— ๋ช…ํ™•ํ•˜๊ณ  ์นœ์ ˆํ•œ ์ „๋ฌธ๊ฐ€์˜ ์–ด์กฐ๋กœ ๋‹ต๋ณ€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
[๋‹ต๋ณ€ ์›์น™]
1. ์ •ํ™•์„ฑ: ๋ฐ˜๋“œ์‹œ ์ฃผ์–ด์ง„ ์ฐธ๊ณ  ๋ฌธ์„œ์˜ ๋‚ด์šฉ์—๋งŒ ๊ทผ๊ฑฐํ•˜์—ฌ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค.
2. ์นœ์ ˆํ•จ: ํ•ญ์ƒ ์นœ์ ˆํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค.
3. ๋งฅ๋ฝ ์ดํ•ด: ์ด์ „ ๋Œ€ํ™” ๋‚ด์šฉ์„ ๊ธฐ์–ตํ•˜์—ฌ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋Œ€ํ™”๋ฅผ ์ด์–ด๊ฐ‘๋‹ˆ๋‹ค.
4. ์ง€์‹ ๋‚ด์žฌํ™”: ๋‹น์‹ ์€ ๋ฌธ์„œ๋ฅผ ๋‹จ์ˆœํžˆ ์ „๋‹ฌํ•˜๋Š” ๋กœ๋ด‡์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์ฐธ๊ณ  ๋ฌธ์„œ๋Š” ๋‹น์‹ ์˜ '์ง€์‹'์ž…๋‹ˆ๋‹ค. ๋‹ต๋ณ€ ์‹œ, '์ œ๊ณต๋œ ์ •๋ณด', '์ฐธ๊ณ  ๋ฌธ์„œ', '์ฃผ์–ด์ง„ ํ…์ŠคํŠธ', 'ํ‘œ', '๋ฌธ๋‹จ' ๋“ฑ ๋‹น์‹ ์ด ์ •๋ณด๋ฅผ ์–ด๋–ป๊ฒŒ ์–ป์—ˆ๋Š”์ง€ ์•”์‹œํ•˜๋Š” ๊ทธ ์–ด๋–ค ๋‹จ์–ด๋„ ์ ˆ๋Œ€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ๊ฒ€์ƒ‰๋œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ์™„์ „ํžˆ ์ž์‹ ์˜ ์ง€์‹์ธ ๊ฒƒ์ฒ˜๋Ÿผ ์ข…ํ•ฉํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žฌ๊ตฌ์„ฑํ•˜์—ฌ, ๋งˆ์น˜ ์›๋ž˜๋ถ€ํ„ฐ ์•Œ๊ณ  ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ง์ ‘ ์„ค๋ช…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
5. ํ•œ๊ตญ์–ด ์‚ฌ์šฉ: ๋ชจ๋“  ๋‹ต๋ณ€์€ ๋ฐ˜๋“œ์‹œ ์™„๋ฒฝํ•œ ํ•œ๊ตญ์–ด๋กœ๋งŒ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
[๋‹ต๋ณ€ ๊ทœ์น™]
1. ์ž๊ธฐ์†Œ๊ฐœ: ๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹น์‹ ์˜ ์ •์ฒด์„ฑ์— ๋Œ€ํ•ด ๋ฌป๋Š”๋‹ค๋ฉด(์˜ˆ: "๋„ˆ๋Š” ๋ˆ„๊ตฌ์•ผ?", "์ด๋ฆ„์ด ๋ญ์•ผ?"), "์•ˆ๋…•ํ•˜์„ธ์š”! ์ €๋Š” ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต ํ•™์ƒ๋“ค์˜ ์บ ํผ์Šค ์ƒํ™œ์„ ๋•๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ 'ํ•™์‚ฌ ์ƒํ™œ AI ์–ด๋“œ๋ฐ”์ด์ €'์ž…๋‹ˆ๋‹ค. ํ•™์‚ฌ ์ •๋ณด๋‚˜ ํ•™๊ต ์ƒํ™œ์— ๋Œ€ํ•ด ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ๋ฌด์—‡์ด๋“  ๋ฌผ์–ด๋ณด์„ธ์š”." ๋ผ๊ณ  ์ •ํ™•ํžˆ ์†Œ๊ฐœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ ˆ๋Œ€๋กœ 'Google์˜ ์–ธ์–ด ๋ชจ๋ธ'์ด๋‚˜ ๋งˆ์Šค์ฝ”ํŠธ '๋ถ€(Boo)'๋ผ๊ณ  ์ž์‹ ์„ ์†Œ๊ฐœํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
1. ๋ฒ”์œ„ ์™ธ ์งˆ๋ฌธ ํŒ๋‹จ: ๋‹น์‹ ์˜ ์ง€์‹ ๋ฒ”์œ„(ํ•™์‚ฌ, ์ฃผ๋ณ€ ๋ง›์ง‘)์™€ ๋ช…๋ฐฑํžˆ ๊ด€๋ จ ์—†๋Š” ์งˆ๋ฌธ(์˜ˆ: ๊ธˆ์œต, ์Šคํฌ์ธ )์—๋Š” "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต ํ•™์‚ฌ ๋ฐ ์บ ํผ์Šค ์ƒํ™œ ์ •๋ณด์— ๋Œ€ํ•ด์„œ๋งŒ ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." ๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”. '์ œ๊ณต๋œ ์ •๋ณด์— ์—†๋‹ค'๋Š” ์‹์˜ ๋ถ€์—ฐ ์„ค๋ช…์€ ์ ˆ๋Œ€ ๋ง๋ถ™์ด์ง€ ๋งˆ์„ธ์š”.
2. ์ •๋ณด ์šฐ์„ ์ˆœ์œ„ ํŒ๋ณ„: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฐธ๊ณ  ๋ฌธ์„œ๊ฐ€ ์ฃผ์–ด์ง€๋ฉด, ๊ทธ์ค‘์—์„œ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๊ฐ€์žฅ ์ง์ ‘์ ์œผ๋กœ ๋‹ตํ•  ์ˆ˜ ์žˆ๋Š” ํ•ต์‹ฌ ์ •๋ณด๋ฅผ ๋จผ์ € ์‹๋ณ„ํ•˜์„ธ์š”. ๊ด€๋ จ์„ฑ์ด ๋–จ์–ด์ง€๊ฑฐ๋‚˜ ๋ถ€์ฐจ์ ์ธ ์ •๋ณด๋Š” ๋‹ต๋ณ€์— ํฌํ•จํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ„๋žตํ•˜๊ฒŒ ๋ง๋ถ™์—ฌ ์„ค๋ช…ํ•˜์„ธ์š”.
3. ํ‘œ(Table) ๋ถ„์„: ์ฐธ๊ณ  ๋ฌธ์„œ์— ํ‘œ๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ, ๋‹น์‹ ์€ ํ‘œ ๋ถ„์„ ์ „๋ฌธ๊ฐ€๋กœ์„œ ํ–‰๊ณผ ์—ด์˜ ๊ด€๊ณ„๋ฅผ ์ •ํ™•ํžˆ ํ•ด์„ํ•˜์—ฌ ๋‹ต๋ณ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
4. ์กฐ๊ฑด๋ถ€ ๋‹ต๋ณ€: ๋งŒ์•ฝ ํ‘œ๋‚˜ ํ…์ŠคํŠธ์— ํ•™๊ณผ, ํ•™๋ฒˆ ๋“ฑ ์„ธ๋ถ€ ์กฐ๊ฑด์ด ๋ช…์‹œ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด, "์ œ์‹œ๋œ ์ž๋ฃŒ์— ๋”ฐ๋ฅด๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ" ๋˜๋Š” "2025ํ•™๋…„๋„ ๊ธฐ์ค€์œผ๋กœ๋Š”" ๊ณผ ๊ฐ™์ด ์ •๋ณด์˜ ์ถœ์ฒ˜๋‚˜ ๊ธฐ์ค€์„ ๋ช…ํ™•ํžˆ ๋ฐํžˆ๋ฉฐ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
5. ๋‹ค์ค‘ ์ •๋ณด ์ฒ˜๋ฆฌ: ๋งŒ์•ฝ ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ๋ฌธ์„œ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ์ •๋ณด๊ฐ€ ๊ฒ€์ƒ‰๋  ๊ฒฝ์šฐ, ํ•˜๋‚˜์˜ ์ •๋ณด๋งŒ ์„ ํƒํ•˜์ง€ ๋งˆ์„ธ์š”. ๋Œ€์‹ , ๊ฐ๊ฐ์˜ ์กฐ๊ฑด๊ณผ ๋‚ด์šฉ์„ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋ชจ๋“  ์ •๋ณด๋ฅผ ์ข…ํ•ฉ์ ์œผ๋กœ ์•ˆ๋‚ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
6. ์˜ˆ์™ธ ๊ฐ€๋Šฅ์„ฑ ์ธ์ง€: ํ•™์‚ฌ ๊ทœ์ •์€ ๋‹จ๊ณผ๋Œ€ํ•™, ํ•™๊ณผ, ํ•™๋ฒˆ๋ณ„๋กœ ์˜ˆ์™ธ ๊ทœ์น™์ด ์กด์žฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ํ•ญ์ƒ ์ธ์ง€ํ•˜์„ธ์š”. ๋งŒ์•ฝ ์ผ๋ฐ˜์ ์ธ ๊ทœ์น™์„ ์ฐพ์•˜๋”๋ผ๋„, "์ผ๋ฐ˜์ ์œผ๋กœ๋Š” OOํ•™์ ์ด ํ•„์š”ํ•˜์ง€๋งŒ, ์†Œ์† ๋‹จ๊ณผ๋Œ€ํ•™์ด๋‚˜ ํ•™๊ณผ์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ •ํ™•ํ•œ ์ •๋ณด๋Š” ํ•™๊ต ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์‹œ๊ฑฐ๋‚˜ ํ•™๊ณผ ์‚ฌ๋ฌด์‹ค์— ๋ฌธ์˜ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค" ์™€ ๊ฐ™์ด ๋‹ต๋ณ€์— '์ฃผ์˜์‚ฌํ•ญ'๊ณผ 'ํ•œ๊ณ„'๋ฅผ ๋ช…์‹œํ•˜์„ธ์š”.
7. ์ •๋ณด ๋ถ€์žฌ ์‹œ: ์œ„์˜ ๋ชจ๋“  ๋…ธ๋ ฅ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ฐธ๊ณ  ๋ฌธ์„œ์—์„œ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ, "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์˜ํ•˜์‹  ๋‚ด์šฉ์— ๋Œ€ํ•œ ์ •๋ณด๋Š” ์ œ๊ฐ€ ๊ฐ€์ง„ ์ž๋ฃŒ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”.
"""
),
}
)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# retriever = MultiQueryRetriever.from_llm(
# retriever=vector_store.as_retriever(search_kwargs={"k": 5}),
# llm=llm
# )
# ์‚ฌ์šฉ์ž ์„ธ์…˜๋ณ„ ๋Œ€ํ™” ์ฒด์ธ์„ ์ €์žฅํ•  ๋”•์…”๋„ˆ๋ฆฌ
chat_sessions: Dict[str, ConversationalRetrievalChain] = {}
def get_or_create_chain(session_id: str) -> ConversationalRetrievalChain:
if session_id not in chat_sessions:
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True,
input_key="question", # <-- ์ถ”๊ฐ€
output_key="answer" )
new_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
memory=memory,
return_source_documents=True, # ์ฐธ๊ณ  ๋ฌธ์„œ ๋ฐ˜ํ™˜ ํ™œ์„ฑํ™”
output_key="answer"
)
chat_sessions[session_id] = new_chain
print(f"์ƒˆ๋กœ์šด ์„ธ์…˜ ID ์ƒ์„ฑ: {session_id}")
return chat_sessions[session_id]
class ChatMessage(BaseModel):
message: str
session_id: str
user_id: str # ์‚ฌ์šฉ์ž ์‹๋ณ„์„ ์œ„ํ•ด ์ถ”๊ฐ€
class ChatResponse(BaseModel):
response: str
success: bool
# source_documents ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ”„๋ก ํŠธ์—”๋“œ๋กœ๋„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ์ค€๋น„
source_documents: List[Dict[str, str]] = [] # ๋ฌธ์„œ ๋‚ด์šฉ๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ
@app.post("/api/chat", response_model=ChatResponse)
@limiter.limit("15/minute")
async def chat_with_gemini(request: Request):
start_time = time.time()
try:
# JSON body๋ฅผ ์ง์ ‘ ํŒŒ์‹ฑ
body = await request.json()
chat_message = ChatMessage(**body)
# qa_chain = get_or_create_chain(request.session_id)
# result = qa_chain.invoke({"question": request.message})
qa_chain = get_or_create_chain(chat_message.session_id)
result = qa_chain.invoke({"question": chat_message.message})
# ์ฐธ๊ณ  ๋ฌธ์„œ ์ถ”์ถœ ๋ฐ ๋กœ๊ทธ ์ถœ๋ ฅ
source_documents_for_response: List[Dict[str, str]] = []
if 'source_documents' in result and result['source_documents']:
print("\n--- ์ฐธ๊ณ  ๋ฌธ์„œ ---")
for i, doc in enumerate(result['source_documents']):
print(f"๋ฌธ์„œ {i+1}:")
print(f" ์†Œ์Šค: {doc.metadata.get('source', '์•Œ ์ˆ˜ ์—†์Œ')}")
print(f" ๋‚ด์šฉ (์ผ๋ถ€): {doc.page_content[:200]}...") # ๋‚ด์šฉ์˜ ์ผ๋ถ€๋งŒ ์ถœ๋ ฅ
# ํ”„๋ก ํŠธ์—”๋“œ ์‘๋‹ต์„ ์œ„ํ•ด ์ €์žฅ
source_documents_for_response.append({
"source": doc.metadata.get('source', '์•Œ ์ˆ˜ ์—†์Œ'),
"content": doc.page_content # ์ „์ฒด ๋‚ด์šฉ์„ ๋ณด๋‚ผ ์ˆ˜๋„ ์žˆ์Œ
})
print("---------------\n")
# ==========================================================
# โ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผ ์ด ๋ถ€๋ถ„๋งŒ ์ถ”๊ฐ€ โ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผโ–ผ
# ==========================================================
response_time_ms = int((time.time() - start_time) * 1000)
# DB์— ๋กœ๊ทธ ์ €์žฅ
try:
db_conn_str = CONNECTION_STRING.replace("postgresql+psycopg2", "postgresql")
conn = psycopg2.connect(db_conn_str)
cur = conn.cursor()
cur.execute(
"""
INSERT INTO chat_logs (session_id, user_id, user_question, bot_answer, retrieved_sources, response_time_ms)
VALUES (%s, %s, %s, %s, %s, %s);
""",
(chat_message.session_id, chat_message.user_id, chat_message.message, result['answer'], json.dumps(source_documents_for_response), response_time_ms)
)
conn.commit()
cur.close()
conn.close()
except Exception as db_error:
print(f"DB ๋กœ๊ทธ ์ €์žฅ ์‹คํŒจ: {db_error}")
# ==========================================================
# โ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒ ์ด ๋ถ€๋ถ„๋งŒ ์ถ”๊ฐ€ โ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒโ–ฒ
# ==========================================================
return ChatResponse(
response=result['answer'],
success=True,
source_documents=source_documents_for_response # ์‘๋‹ต์— ์ฐธ๊ณ  ๋ฌธ์„œ ์ถ”๊ฐ€
)
except Exception as e:
print(f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
return ChatResponse(
response=f"์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}",
success=False,
source_documents=[]
)
@app.get("/")
async def root():
return {"message": "ํ•œ๊ตญ์™ธ๊ตญ์–ด๋Œ€ํ•™๊ต(์„œ์šธ) ํ•™์‚ฌ ์ฑ—๋ด‡ API"}