deeme commited on
Commit
2789f98
·
verified ·
1 Parent(s): 3c00b29

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +16 -0
  2. README.md +10 -10
  3. main.py +349 -0
  4. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install dependencies
6
+ COPY requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ # Copy application code
10
+ COPY main.py .
11
+
12
+ # Expose the port the app runs on
13
+ EXPOSE 7860
14
+
15
+ # Command to run the application
16
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,10 @@
1
- ---
2
- title: Google
3
- emoji: 👁
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: google
3
+ emoji: 🌍
4
+ colorFrom: blue
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
main.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ from datetime import datetime, timezone
4
+ import os
5
+
6
+ from fastapi import FastAPI, HTTPException, Request
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+ from fastapi.responses import JSONResponse
9
+ from fastapi.responses import StreamingResponse
10
+ from pydantic import BaseModel
11
+ from typing import List, Optional
12
+ import time
13
+ import uuid
14
+ import logging
15
+
16
+ from gemini_webapi import GeminiClient, set_log_level
17
+ from gemini_webapi.constants import Model
18
+
19
+ # Configure logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+ set_log_level("INFO")
23
+
24
+ app = FastAPI(title="Gemini API FastAPI Server")
25
+
26
+ # Add CORS middleware
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=["*"],
30
+ allow_credentials=True,
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
+
35
+ # Global client
36
+ gemini_client = None
37
+
38
+ # Authentication credentials
39
+ SECURE_1PSID = os.environ.get("SECURE_1PSID", "")
40
+ SECURE_1PSIDTS = os.environ.get("SECURE_1PSIDTS", "")
41
+
42
+ # Print debug info at startup
43
+ if not SECURE_1PSID or not SECURE_1PSIDTS:
44
+ logger.warning("⚠️ Gemini API credentials are not set or empty! Please check your environment variables.")
45
+ else:
46
+ # Only log the first few characters for security
47
+ logger.info(f"Credentials found. SECURE_1PSID starts with: {SECURE_1PSID[:5]}...")
48
+ logger.info(f"Credentials found. SECURE_1PSIDTS starts with: {SECURE_1PSIDTS[:5]}...")
49
+
50
+ # Pydantic models for API requests and responses
51
+ class Message(BaseModel):
52
+ role: str
53
+ content: str
54
+ name: Optional[str] = None
55
+
56
+
57
+ class ChatCompletionRequest(BaseModel):
58
+ model: str
59
+ messages: List[Message]
60
+ temperature: Optional[float] = 0.7
61
+ top_p: Optional[float] = 1.0
62
+ n: Optional[int] = 1
63
+ stream: Optional[bool] = False
64
+ max_tokens: Optional[int] = None
65
+ presence_penalty: Optional[float] = 0
66
+ frequency_penalty: Optional[float] = 0
67
+ user: Optional[str] = None
68
+
69
+
70
+ class Choice(BaseModel):
71
+ index: int
72
+ message: Message
73
+ finish_reason: str
74
+
75
+
76
+ class Usage(BaseModel):
77
+ prompt_tokens: int
78
+ completion_tokens: int
79
+ total_tokens: int
80
+
81
+
82
+ class ChatCompletionResponse(BaseModel):
83
+ id: str
84
+ object: str = "chat.completion"
85
+ created: int
86
+ model: str
87
+ choices: List[Choice]
88
+ usage: Usage
89
+
90
+
91
+ class ModelData(BaseModel):
92
+ id: str
93
+ object: str = "model"
94
+ created: int
95
+ owned_by: str = "google"
96
+
97
+
98
+ class ModelList(BaseModel):
99
+ object: str = "list"
100
+ data: List[ModelData]
101
+
102
+
103
+ # Simple error handler middleware
104
+ @app.middleware("http")
105
+ async def error_handling(request: Request, call_next):
106
+ try:
107
+ return await call_next(request)
108
+ except Exception as e:
109
+ logger.error(f"Request failed: {str(e)}")
110
+ return JSONResponse(
111
+ status_code=500,
112
+ content={ "error": { "message": str(e), "type": "internal_server_error" } }
113
+ )
114
+
115
+
116
+ # Get list of available models
117
+ @app.get("/v1/models")
118
+ async def list_models():
119
+ """返回 gemini_webapi 中声明的模型列表"""
120
+ now = int(datetime.now(tz=timezone.utc).timestamp())
121
+ data = [
122
+ {
123
+ "id": m.model_name, # 如 "gemini-2.0-flash"
124
+ "object": "model",
125
+ "created": now,
126
+ "owned_by": "google-gemini-web"
127
+ }
128
+ for m in Model
129
+ ]
130
+ print(data)
131
+ return {"object": "list", "data": data}
132
+
133
+
134
+ # Helper to convert between Gemini and OpenAI model names
135
+ def map_model_name(openai_model_name: str) -> Model:
136
+ """根据模型名称字符串查找匹配的 Model 枚举值"""
137
+ # 打印所有可用模型以便调试
138
+ all_models = [m.model_name if hasattr(m, "model_name") else str(m) for m in Model]
139
+ logger.info(f"Available models: {all_models}")
140
+
141
+ # 首先尝试直接查找匹配的模型名称
142
+ for m in Model:
143
+ model_name = m.model_name if hasattr(m, "model_name") else str(m)
144
+ if openai_model_name.lower() in model_name.lower():
145
+ return m
146
+
147
+ # 如果找不到匹配项,使用默认映射
148
+ model_keywords = {
149
+ "gemini-pro": ["pro", "2.0"],
150
+ "gemini-pro-vision": ["vision", "pro"],
151
+ "gemini-flash": ["flash", "2.0"],
152
+ "gemini-1.5-pro": ["1.5", "pro"],
153
+ "gemini-1.5-flash": ["1.5", "flash"],
154
+ }
155
+
156
+ # 根据关键词匹配
157
+ keywords = model_keywords.get(openai_model_name, ["pro"]) # 默认使用pro模型
158
+
159
+ for m in Model:
160
+ model_name = m.model_name if hasattr(m, "model_name") else str(m)
161
+ if all(kw.lower() in model_name.lower() for kw in keywords):
162
+ return m
163
+
164
+ # 如果还是找不到,返回第一个模型
165
+ return next(iter(Model))
166
+
167
+
168
+ # Prepare conversation history from OpenAI messages format
169
+ def prepare_conversation(messages: List[Message]) -> str:
170
+ conversation = ""
171
+
172
+ for msg in messages:
173
+ if msg.role == "system":
174
+ conversation += f"System: {msg.content}\n\n"
175
+ elif msg.role == "user":
176
+ conversation += f"Human: {msg.content}\n\n"
177
+ elif msg.role == "assistant":
178
+ conversation += f"Assistant: {msg.content}\n\n"
179
+
180
+ # Add a final prompt for the assistant to respond to
181
+ conversation += "Assistant: "
182
+
183
+ return conversation
184
+
185
+
186
+ # Dependency to get the initialized Gemini client
187
+ async def get_gemini_client():
188
+ global gemini_client
189
+ if gemini_client is None:
190
+ try:
191
+ gemini_client = GeminiClient(SECURE_1PSID, SECURE_1PSIDTS)
192
+ await gemini_client.init(timeout=30)
193
+ except Exception as e:
194
+ logger.error(f"Failed to initialize Gemini client: {str(e)}")
195
+ raise HTTPException(
196
+ status_code=500,
197
+ detail=f"Failed to initialize Gemini client: {str(e)}"
198
+ )
199
+ return gemini_client
200
+
201
+
202
+ @app.post("/v1/chat/completions")
203
+ async def create_chat_completion(request: ChatCompletionRequest):
204
+ try:
205
+ # 确保客户端已初始化
206
+ global gemini_client
207
+ if gemini_client is None:
208
+ gemini_client = GeminiClient(SECURE_1PSID, SECURE_1PSIDTS)
209
+ await gemini_client.init(timeout=30)
210
+ logger.info("Gemini client initialized successfully")
211
+
212
+ # 转换消息为对话格式
213
+ conversation = prepare_conversation(request.messages)
214
+ logger.info(f"Prepared conversation: {conversation}")
215
+
216
+ # 获取适当的模型
217
+ model = map_model_name(request.model)
218
+ logger.info(f"Using model: {model}")
219
+
220
+ # 生成响应
221
+ logger.info("Sending request to Gemini...")
222
+ response = await gemini_client.generate_content(conversation, model=model)
223
+
224
+ # 提取文本响应
225
+ reply_text = ""
226
+ if hasattr(response, "text"):
227
+ reply_text = response.text
228
+ else:
229
+ reply_text = str(response)
230
+
231
+ logger.info(f"Response: {reply_text}")
232
+
233
+ if not reply_text or reply_text.strip() == "":
234
+ logger.warning("Empty response received from Gemini")
235
+ reply_text = "服务器返回了空响应。请检查 Gemini API 凭据是否有效。"
236
+
237
+ # 创建响应对象
238
+ completion_id = f"chatcmpl-{uuid.uuid4()}"
239
+ created_time = int(time.time())
240
+
241
+ # 检查客户端是否请求流式响应
242
+ if request.stream:
243
+ # 实现流式响应
244
+ async def generate_stream():
245
+ # 创建 SSE 格式的流式响应
246
+ # 先发送开始事件
247
+ data = {
248
+ "id": completion_id,
249
+ "object": "chat.completion.chunk",
250
+ "created": created_time,
251
+ "model": request.model,
252
+ "choices": [
253
+ {
254
+ "index": 0,
255
+ "delta": {
256
+ "role": "assistant"
257
+ },
258
+ "finish_reason": None
259
+ }
260
+ ]
261
+ }
262
+ yield f"data: {json.dumps(data)}\n\n"
263
+
264
+ # 模拟流式输出 - 将文本按字符分割发送
265
+ for char in reply_text:
266
+ data = {
267
+ "id": completion_id,
268
+ "object": "chat.completion.chunk",
269
+ "created": created_time,
270
+ "model": request.model,
271
+ "choices": [
272
+ {
273
+ "index": 0,
274
+ "delta": {
275
+ "content": char
276
+ },
277
+ "finish_reason": None
278
+ }
279
+ ]
280
+ }
281
+ yield f"data: {json.dumps(data)}\n\n"
282
+ # 可选:添加短暂延迟以模拟真实的流式输出
283
+ await asyncio.sleep(0.01)
284
+
285
+ # 发送结束事件
286
+ data = {
287
+ "id": completion_id,
288
+ "object": "chat.completion.chunk",
289
+ "created": created_time,
290
+ "model": request.model,
291
+ "choices": [
292
+ {
293
+ "index": 0,
294
+ "delta": { },
295
+ "finish_reason": "stop"
296
+ }
297
+ ]
298
+ }
299
+ yield f"data: {json.dumps(data)}\n\n"
300
+ yield "data: [DONE]\n\n"
301
+
302
+ return StreamingResponse(
303
+ generate_stream(),
304
+ media_type="text/event-stream"
305
+ )
306
+ else:
307
+ # 非流式响应(原来的逻辑)
308
+ result = {
309
+ "id": completion_id,
310
+ "object": "chat.completion",
311
+ "created": created_time,
312
+ "model": request.model,
313
+ "choices": [
314
+ {
315
+ "index": 0,
316
+ "message": {
317
+ "role": "assistant",
318
+ "content": reply_text
319
+ },
320
+ "finish_reason": "stop"
321
+ }
322
+ ],
323
+ "usage": {
324
+ "prompt_tokens": len(conversation.split()),
325
+ "completion_tokens": len(reply_text.split()),
326
+ "total_tokens": len(conversation.split()) + len(reply_text.split())
327
+ }
328
+ }
329
+
330
+ logger.info(f"Returning response: {result}")
331
+ return result
332
+
333
+ except Exception as e:
334
+ logger.error(f"Error generating completion: {str(e)}", exc_info=True)
335
+ raise HTTPException(
336
+ status_code=500,
337
+ detail=f"Error generating completion: {str(e)}"
338
+ )
339
+
340
+
341
+ @app.get("/")
342
+ async def root():
343
+ return { "status": "online", "message": "Gemini API FastAPI Server is running" }
344
+
345
+
346
+ if __name__ == "__main__":
347
+ import uvicorn
348
+
349
+ uvicorn.run("main:app", host="0.0.0.0", port=7860, log_level="info")
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ browser-cookie3>=0.20.1
2
+ fastapi>=0.115.12
3
+ gemini-webapi>=1.11.0
4
+ uvicorn[standard]>=0.34.1