"""FastAPI 서버"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from contextlib import asynccontextmanager
from .mcp_client import mcp_client
from .redis_init import init_redis_index
class SearchRequest(BaseModel):
query: str
class SearchResultResponse(BaseModel):
title: str
url: str
class SearchResponse(BaseModel):
result: SearchResultResponse
query: str
class SaveDocumentRequest(BaseModel):
title: str
url: str
content: str
class SaveDocumentResponse(BaseModel):
vector_id: str
message: str
class GetDocumentResponse(BaseModel):
vector_id: str
document: str
class QARequest(BaseModel):
vector_id: str
query: str
class QAResponse(BaseModel):
vector_id: str
query: str
answer: str
@asynccontextmanager
async def lifespan(app: FastAPI):
# 시작 시: MCP 클라이언트 연결
print("MCP 서버에 연결 중...")
await mcp_client.connect()
print("MCP 서버 연결 완료!")
yield
# 종료 시: MCP 클라이언트 연결 해제
print("MCP 서버 연결 해제 중...")
await mcp_client.disconnect()
print("MCP 서버 연결 해제 완료!")
app = FastAPI(
title="Naver Search Docs API",
description="네이버 검색 API와 AI를 활용한 공식 문서 검색 서비스",
version="1.0.0",
lifespan=lifespan
)
# CORS 설정
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
"""API 상태 확인"""
return {
"status": "running",
"message": "Naver Search Docs API",
"version": "1.0.0"
}
@app.post("/search", response_model=SearchResponse)
async def search_official_docs(request: SearchRequest):
"""
공식 문서 검색
- **query**: 검색할 질문 또는 키워드
Returns:
- **result**: 검색 결과 (공식 문서 URL과 설명)
- **query**: 검색한 질문
"""
try:
result = await mcp_client.search_official_docs(request.query)
return SearchResponse(
result=result,
query=request.query
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"검색 중 오류가 발생했습니다: {str(e)}")
@app.post("/save_document", response_model=SaveDocumentResponse)
async def save_document(request: SaveDocumentRequest):
"""
문서를 벡터 DB에 저장
- **title**: 문서 제목
- **url**: 문서 URL
- **content**: 문서 내용
Returns:
- **vector_id**: 생성된 vector_id
- **message**: 저장 완료 메시지
"""
try:
result = await mcp_client.save_document(
title=request.title,
url=request.url,
content=request.content
)
# "문서가 저장되었습니다.\nvector_id: xxx" 형식에서 vector_id 추출
vector_id = result.split("vector_id: ")[-1].strip()
return SaveDocumentResponse(
vector_id=vector_id,
message="문서가 성공적으로 저장되었습니다."
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"문서 저장 중 오류가 발생했습니다: {str(e)}")
@app.get("/get_document/{vector_id}", response_model=GetDocumentResponse)
async def get_document(vector_id: str):
"""
vector_id로 문서 조회
- **vector_id**: 조회할 문서의 vector_id
Returns:
- **vector_id**: 문서 ID
- **document**: 문서 정보
"""
try:
result = await mcp_client.get_document(vector_id)
return GetDocumentResponse(
vector_id=vector_id,
document=result
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"문서 조회 중 오류가 발생했습니다: {str(e)}")
@app.post("/qa", response_model=QAResponse)
async def qa_with_redis(request: QARequest):
"""
vector_id에 저장된 문서를 기반으로 질문 답변
- **vector_id**: 참조할 문서의 vector_id
- **query**: 질문
Returns:
- **vector_id**: 참조한 문서 ID
- **query**: 질문
- **answer**: AI가 생성한 답변
"""
try:
answer = await mcp_client.qa_with_redis(
vector_id=request.vector_id,
query=request.query
)
return QAResponse(
vector_id=request.vector_id,
query=request.query,
answer=answer
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"답변 생성 중 오류가 발생했습니다: {str(e)}")
@app.get("/health")
async def health_check():
"""헬스 체크 엔드포인트"""
return {"status": "healthy"}
if __name__ == "__main__":
import uvicorn
init_redis_index()
uvicorn.run(app, host="0.0.0.0", port=8001)