Chrunos commited on
Commit
4c57dd0
·
verified ·
1 Parent(s): a85a4dc

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -0
app.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from fastapi import FastAPI, HTTPException, Body
4
+ from fastapi.responses import StreamingResponse
5
+ from pydantic import BaseModel, Field
6
+ import openai # For your custom API
7
+ import google.generativeai as genai # For Gemini API
8
+
9
+ # --- Configuration ---
10
+ # API keys are now expected to be set as Hugging Face Space secrets.
11
+ # GEMINI_API_KEY for Gemini
12
+ # CUSTOM_API_SECRET_KEY for your custom API
13
+ # CUSTOM_API_BASE_URL for your custom API (can also be a secret if it changes)
14
+ # CUSTOM_API_MODEL for your custom API (can also be a secret)
15
+
16
+ CUSTOM_API_BASE_URL_DEFAULT = "https://api-q3ieh5raqfuad9o8.aistudio-app.com/v1"
17
+ CUSTOM_API_MODEL_DEFAULT = "gemma3:27b"
18
+
19
+ # --- Pydantic Models for Request Body ---
20
+ class ChatRequest(BaseModel):
21
+ message: str
22
+ api_key: str = Field(None, description="Gemini API Key (optional; will use Space secret 'GEMINI_API_KEY' if not provided)")
23
+ url: str = Field(None, description="URL (optional, if it's a YouTube or googleusercontent video URL, Gemini will be used)")
24
+
25
+ # --- FastAPI App Initialization ---
26
+ app = FastAPI(
27
+ title="Universal Chat API (Secrets Optimized)",
28
+ description="A Hugging Face Space that routes chat requests to a custom API or Gemini API (for video URLs) using Space secrets for API keys.",
29
+ version="1.1.0"
30
+ )
31
+
32
+ # --- Helper Functions ---
33
+ def is_video_url_for_gemini(url: str) -> bool:
34
+ """
35
+ Checks if the given URL is a YouTube video URL or a googleusercontent URI
36
+ suitable for Gemini multimodal processing.
37
+ """
38
+ if not url:
39
+ return False
40
+ # Regex to match standard YouTube video URLs
41
+ youtube_regex = (
42
+ r'(https_?://)?(www\.)?'
43
+ '(youtube|youtu|youtube-nocookie)\.(com|be)/'
44
+ '(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
45
+
46
+ # Check for googleusercontent YouTube URIs
47
+ googleusercontent_youtube_regex = r'https_?://googleusercontent\.com/youtube\.com/\w+'
48
+
49
+ return re.match(youtube_regex, url) is not None or \
50
+ re.match(googleusercontent_youtube_regex, url) is not None
51
+
52
+ # --- API Endpoint ---
53
+ @app.post("/chat")
54
+ async def chat_endpoint(request: ChatRequest):
55
+ """
56
+ Chat endpoint that routes to Gemini for video URLs or a custom API otherwise.
57
+ API keys are primarily sourced from Hugging Face Space secrets.
58
+ """
59
+ user_message = request.message
60
+ gemini_api_key_from_request = request.api_key
61
+ input_url = request.url
62
+
63
+ # Determine API keys from secrets, allowing override from request for Gemini
64
+ gemini_api_key_secret = os.getenv("GEMINI_API_KEY")
65
+ custom_api_key_secret = os.getenv("CUSTOM_API_SECRET_KEY")
66
+
67
+ # Determine custom API base URL and model from secrets or use defaults
68
+ custom_api_base_url = os.getenv("CUSTOM_API_BASE_URL", CUSTOM_API_BASE_URL_DEFAULT)
69
+ custom_api_model = os.getenv("CUSTOM_API_MODEL", CUSTOM_API_MODEL_DEFAULT)
70
+
71
+ if input_url and is_video_url_for_gemini(input_url):
72
+ # --- Gemini API Logic for Video URLs ---
73
+ gemini_key_to_use = gemini_api_key_from_request or gemini_api_key_secret
74
+
75
+ if not gemini_key_to_use:
76
+ raise HTTPException(
77
+ status_code=400,
78
+ detail="Gemini API Key is required for video URL processing. "
79
+ "Provide it in the request or set 'GEMINI_API_KEY' as a Space secret."
80
+ )
81
+
82
+ try:
83
+ genai.configure(api_key=gemini_key_to_use)
84
+ except Exception as e:
85
+ raise HTTPException(status_code=500, detail=f"Failed to configure Gemini API: {e}")
86
+
87
+ # Prepare the content structure for Gemini multimodal input
88
+ gemini_contents = [{
89
+ "parts": [
90
+ {"text": user_message}, # User's prompt/question
91
+ {
92
+ "file_data": {
93
+ "mime_type": "video/youtube", # Common practice, though Gemini might infer
94
+ "file_uri": input_url
95
+ }
96
+ }
97
+ ]
98
+ }]
99
+
100
+ async def gemini_streamer():
101
+ try:
102
+ # Model supporting video input, e.g., 'gemini-1.5-flash' or 'gemini-1.5-pro'
103
+ model = genai.GenerativeModel(model_name="gemini-1.5-flash")
104
+ response = await model.generate_content_async(gemini_contents, stream=True)
105
+ async for chunk in response:
106
+ if chunk.text:
107
+ yield chunk.text
108
+ except Exception as e:
109
+ error_message = f"Error during Gemini API call: {str(e)}"
110
+ print(error_message)
111
+ # Check for specific Gemini API errors if needed, e.g., content filtering
112
+ if "response.prompt_feedback" in str(e).lower(): # Crude check for safety feedback
113
+ yield "The response was blocked due to safety settings by the Gemini API."
114
+ else:
115
+ yield error_message
116
+ return StreamingResponse(gemini_streamer(), media_type="text/plain")
117
+
118
+ else:
119
+ # --- Custom API Logic ---
120
+ if not custom_api_key_secret:
121
+ raise HTTPException(
122
+ status_code=500,
123
+ detail="Custom API key ('CUSTOM_API_SECRET_KEY') is not configured in Space secrets."
124
+ )
125
+ if not custom_api_base_url:
126
+ raise HTTPException(
127
+ status_code=500,
128
+ detail="Custom API base URL ('CUSTOM_API_BASE_URL') is not configured in Space secrets or defaults."
129
+ )
130
+
131
+ client = openai.OpenAI(
132
+ api_key=custom_api_key_secret,
133
+ base_url=custom_api_base_url
134
+ )
135
+
136
+ async def custom_api_streamer():
137
+ try:
138
+ stream = client.chat.completions.create(
139
+ model=custom_api_model,
140
+ temperature=0.6,
141
+ messages=[
142
+ {"role": "user", "content": user_message}
143
+ ],
144
+ stream=True
145
+ )
146
+ for chunk in stream:
147
+ if hasattr(chunk.choices[0].delta, "reasoning_content") and chunk.choices[0].delta.reasoning_content:
148
+ yield chunk.choices[0].delta.reasoning_content
149
+ elif chunk.choices[0].delta and chunk.choices[0].delta.content:
150
+ yield chunk.choices[0].delta.content
151
+ except Exception as e:
152
+ error_message = f"Error during Custom API call: {str(e)}"
153
+ print(error_message)
154
+ yield error_message
155
+ return StreamingResponse(custom_api_streamer(), media_type="text/plain")
156
+
157
+ # --- Root Endpoint for Health Check (Optional) ---
158
+ @app.get("/")
159
+ async def read_root():
160
+ return {"message": "Chat API (Secrets Optimized) is running. Use the /chat endpoint to interact."}
161
+
162
+ # --- To run locally (optional, Hugging Face Spaces handles this) ---
163
+ # if __name__ == "__main__":
164
+ # import uvicorn
165
+ # # For local testing, you might want to load .env files if you use them
166
+ # # from dotenv import load_dotenv
167
+ # # load_dotenv()
168
+ # uvicorn.run(app, host="0.0.0.0", port=8000)