Spaces:
Build error
Build error
| # ./backend/app/main.py | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.staticfiles import StaticFiles # StaticFiles 임포트 | |
| import os | |
| from pydantic import BaseModel | |
| import httpx | |
| from youtube_parser import process_youtube_video_data | |
| from rag_core import perform_rag_query | |
| app = FastAPI() | |
| # CORS 설정: 프론트엔드와 백엔드가 다른 포트에서 실행될 때 필요 | |
| origins = [ | |
| "http://localhost:8080", # Vue 개발 서버 기본 포트 | |
| "http://localhost:5173", # Vue Vite 개발 서버 기본 포트 | |
| "https://sodagraph-po.hf.space", # 여러분의 Hugging Face Space URL | |
| ] | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=origins, | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # 경로 설정 | |
| current_file_dir = os.path.dirname(os.path.abspath(__file__)) | |
| project_root_dir = os.path.join(current_file_dir, "..", "..") | |
| static_files_dir = os.path.join(project_root_dir, "static") | |
| # OLLAMA_API_BASE_URL 환경 변수 설정 | |
| OLLAMA_API_BASE_URL = os.getenv("OLLAMA_API_BASE_URL", "http://127.0.0.1:11434") | |
| async def generate_answer_with_ollama(model_name: str, prompt: str) -> str: | |
| """ | |
| Ollama 서버에 질의하여 답변을 생성합니다. | |
| """ | |
| url = f"{OLLAMA_API_BASE_URL}/api/generate" | |
| headers = {"Content-Type": "application/json"} | |
| data = { | |
| "model": model_name, | |
| "prompt": prompt, | |
| "stream": False # 스트리밍을 사용하지 않고 한 번에 답변을 받습니다. | |
| } | |
| print(f"INFO: Ollama API 호출 시작. 모델: {model_name}") | |
| print(f"INFO: 프롬프트 미리보기: {prompt[:200]}...") | |
| try: | |
| async with httpx.AsyncClient(timeout=600.0) as client: | |
| response = await client.post(url, headers=headers, json=data) | |
| response.raise_for_status() | |
| response_data = response.json() | |
| full_response = response_data.get("response", "").strip() | |
| return full_response | |
| except httpx.HTTPStatusError as e: | |
| print(f"ERROR: Ollama API 호출 실패: {e}") | |
| raise HTTPException(status_code=500, detail="Ollama API 호출 실패") | |
| except httpx.RequestError as e: | |
| print(f"ERROR: 네트워크 오류: {e}") | |
| raise HTTPException(status_code=500, detail="네트워크 오류가 발생했습니다. 잠시 후 다시 시도해주세요.") | |
| except Exception as e: | |
| print(f"ERROR: 알 수 없는 오류: {e}") | |
| raise HTTPException(status_code=500, detail="알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해주세요.") | |
| class VideoProcessRequest(BaseModel): | |
| video_url: str | |
| query: str | |
| # 사용자가 사용할 Ollama 모델 이름을 지정할 수 있도록 추가 | |
| ollama_model_name: str = "hf.co/DevQuasar/naver-hyperclovax.HyperCLOVAX-SEED-Text-Instruct-0.5B-GGUF:F16" | |
| # ✅ 유튜브 영상 처리 API | |
| async def process_youtube_video(request: VideoProcessRequest): | |
| try: | |
| processed_chunks_with_timestamps = await process_youtube_video_data(request.video_url) | |
| if not processed_chunks_with_timestamps: | |
| return {"message": "자막 또는 내용을 추출할 수 없습니다.", "results": []} | |
| # 1. RAG 검색 수행 | |
| rag_results = await perform_rag_query( | |
| chunks_with_timestamps=processed_chunks_with_timestamps, | |
| query=request.query, | |
| top_k=50 | |
| ) | |
| if not rag_results: | |
| return { | |
| "status": "error", | |
| "message": "검색 결과가 없습니다.", | |
| "video_url": request.video_url, | |
| "query": request.query, | |
| "results": [] | |
| } | |
| # 2. 검색 결과를 프롬프트에 추가 | |
| context = "\\n\\n".join([chunk["text"] for chunk in rag_results]) | |
| prompt = f"다음 정보와 대화 내용을 참고하여 사용자의 질문에 답변하세요. 제공된 정보에서 답을 찾을 수 없다면 '정보 부족'이라고 명시하세요.\\n\\n참고 정보:\\n{context}\\n\\n사용자 질문: {request.query}\\n\\n답변:" | |
| # 3. Ollama 모델에 질의하여 답변 생성 | |
| generated_answer = await generate_answer_with_ollama( | |
| model_name=request.ollama_model_name, | |
| prompt=prompt | |
| ) | |
| return { | |
| "status": "success", | |
| "message": "성공적으로 영상을 처리하고 RAG 검색을 수행했습니다.", | |
| "video_url": request.video_url, | |
| "query": request.query, | |
| "ollama_model_used": request.ollama_model_name, | |
| "retrieved_chunks": rag_results, | |
| "generated_answer": generated_answer | |
| } | |
| except Exception as e: | |
| print(f"ERROR: 서버 처리 중 오류 발생: {str(e)}") | |
| raise HTTPException(status_code=500, detail="서버 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.") | |
| # ✅ 정적 파일은 마지막에 mount | |
| app.mount("/", StaticFiles(directory=static_files_dir, html=True), name="static") | |
| # 서버 실행을 위한 메인 진입점 (Docker에서는 Uvicorn이 직접 호출하므로 필수는 아님) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| import os | |
| port = int(os.environ.get("PORT", 7860)) # Hugging Face가 전달하는 포트를 우선 사용 | |
| uvicorn.run(app, host="0.0.0.0", port=port) |