File size: 5,495 Bytes
9b9d44e
eda02a7
 
 
 
 
 
9b9d44e
eda02a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b9d44e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eda02a7
 
 
 
9b9d44e
 
eda02a7
9b9d44e
eda02a7
 
 
 
 
 
 
 
9b9d44e
eda02a7
 
 
9b9d44e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eda02a7
 
 
 
 
 
 
9b9d44e
 
 
eda02a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# ./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
@app.post("/api/process_youtube_video")
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)