internationalscholarsprogram commited on
Commit
a6785cc
·
1 Parent(s): b2d838a

Final stable FastAPI Gemini WS bridge for HF

Browse files
Files changed (1) hide show
  1. app.py +34 -12
app.py CHANGED
@@ -2,29 +2,41 @@
2
  import json
3
  import os
4
  import base64
 
 
 
 
 
 
 
 
 
 
5
 
6
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
7
  from google import genai
8
 
9
  MODEL = os.environ.get("MODEL", "gemini-2.0-flash-exp")
10
 
11
- # IMPORTANT: don't hardcode keys in code.
12
- # Set GOOGLE_API_KEY as a Hugging Face Secret.
13
  client = genai.Client(http_options={"api_version": "v1alpha"})
14
 
15
  app = FastAPI()
16
 
 
 
17
  @app.get("/")
18
  def health():
19
- # Hugging Face health check hits this as plain HTTP.
20
  return {"status": "ok"}
21
 
 
 
22
  @app.websocket("/ws")
23
  async def gemini_ws_bridge(ws: WebSocket):
24
  await ws.accept()
25
 
26
  try:
27
- # First message must be config (same as your old code)
28
  config_message = await ws.receive_text()
29
  config_data = json.loads(config_message)
30
 
@@ -51,11 +63,13 @@ async def gemini_ws_bridge(ws: WebSocket):
51
  mt = chunk.get("mime_type")
52
  payload = chunk.get("data")
53
  if mt in ("audio/pcm", "image/jpeg") and payload:
54
- await session.send({"mime_type": mt, "data": payload})
 
 
55
  except WebSocketDisconnect:
56
  pass
57
  except Exception as e:
58
- print(f"send_to_gemini error: {e}")
59
 
60
  async def receive_from_gemini():
61
  try:
@@ -67,24 +81,32 @@ async def gemini_ws_bridge(ws: WebSocket):
67
  if model_turn:
68
  for part in model_turn.parts:
69
  if getattr(part, "text", None):
70
- await ws.send_text(json.dumps({"text": part.text}))
 
 
71
  elif getattr(part, "inline_data", None):
72
- b64_audio = base64.b64encode(part.inline_data.data).decode("utf-8")
73
- await ws.send_text(json.dumps({"audio": b64_audio}))
 
 
 
 
74
 
75
  if response.server_content.turn_complete:
76
- await ws.send_text(json.dumps({"turn_complete": True}))
 
 
77
  except WebSocketDisconnect:
78
  pass
79
  except Exception as e:
80
- print(f"receive_from_gemini error: {e}")
81
 
82
  await asyncio.gather(send_to_gemini(), receive_from_gemini())
83
 
84
  except WebSocketDisconnect:
85
  pass
86
  except Exception as e:
87
- print(f"gemini_ws_bridge error: {e}")
88
  try:
89
  await ws.send_text(json.dumps({"error": str(e)}))
90
  except Exception:
 
2
  import json
3
  import os
4
  import base64
5
+ import logging
6
+
7
+ # ---- Logging: reduce HF probe noise ----
8
+ logging.basicConfig(level=logging.INFO)
9
+ logging.getLogger("websockets").setLevel(logging.ERROR)
10
+ logging.getLogger("websockets.server").setLevel(logging.ERROR)
11
+ logging.getLogger("uvicorn.error").setLevel(logging.INFO)
12
+ logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
13
+
14
+ print("APP STARTED ✅ FastAPI + Gemini WebSocket Bridge")
15
 
16
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
17
  from google import genai
18
 
19
  MODEL = os.environ.get("MODEL", "gemini-2.0-flash-exp")
20
 
21
+ # IMPORTANT: Set GOOGLE_API_KEY as a Hugging Face Secret
 
22
  client = genai.Client(http_options={"api_version": "v1alpha"})
23
 
24
  app = FastAPI()
25
 
26
+
27
+ # ---- HTTP health check (required by Hugging Face) ----
28
  @app.get("/")
29
  def health():
 
30
  return {"status": "ok"}
31
 
32
+
33
+ # ---- WebSocket endpoint ----
34
  @app.websocket("/ws")
35
  async def gemini_ws_bridge(ws: WebSocket):
36
  await ws.accept()
37
 
38
  try:
39
+ # First message must be setup/config
40
  config_message = await ws.receive_text()
41
  config_data = json.loads(config_message)
42
 
 
63
  mt = chunk.get("mime_type")
64
  payload = chunk.get("data")
65
  if mt in ("audio/pcm", "image/jpeg") and payload:
66
+ await session.send(
67
+ {"mime_type": mt, "data": payload}
68
+ )
69
  except WebSocketDisconnect:
70
  pass
71
  except Exception as e:
72
+ logging.error(f"send_to_gemini error: {e}")
73
 
74
  async def receive_from_gemini():
75
  try:
 
81
  if model_turn:
82
  for part in model_turn.parts:
83
  if getattr(part, "text", None):
84
+ await ws.send_text(
85
+ json.dumps({"text": part.text})
86
+ )
87
  elif getattr(part, "inline_data", None):
88
+ b64_audio = base64.b64encode(
89
+ part.inline_data.data
90
+ ).decode("utf-8")
91
+ await ws.send_text(
92
+ json.dumps({"audio": b64_audio})
93
+ )
94
 
95
  if response.server_content.turn_complete:
96
+ await ws.send_text(
97
+ json.dumps({"turn_complete": True})
98
+ )
99
  except WebSocketDisconnect:
100
  pass
101
  except Exception as e:
102
+ logging.error(f"receive_from_gemini error: {e}")
103
 
104
  await asyncio.gather(send_to_gemini(), receive_from_gemini())
105
 
106
  except WebSocketDisconnect:
107
  pass
108
  except Exception as e:
109
+ logging.error(f"gemini_ws_bridge error: {e}")
110
  try:
111
  await ws.send_text(json.dumps({"error": str(e)}))
112
  except Exception: