GitHub Action commited on
Commit
52b3cb8
·
1 Parent(s): db58ad1

Auto deploy from GitHub 2026-05-11 14:26:59

Browse files
Files changed (1) hide show
  1. src/openai_fallback_proxy.py +95 -0
src/openai_fallback_proxy.py CHANGED
@@ -22,6 +22,8 @@ FALLBACK_API_KEY = os.environ.get("FALLBACK_API_KEY", "")
22
  FALLBACK_MODEL = os.environ.get("FALLBACK_MODEL", "openrouter/free")
23
  FALLBACK_REFERER = os.environ.get("OPENROUTER_HTTP_REFERER", "https://huggingface.co")
24
  FALLBACK_TITLE = os.environ.get("OPENROUTER_X_TITLE", "Hermes HF Fallback")
 
 
25
 
26
 
27
  def is_retryable(status_code: int) -> bool:
@@ -106,6 +108,61 @@ def normalize_messages(messages: Any) -> Any:
106
  return normalized_messages
107
 
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  def create_upstream_response(
110
  upstream_base: str,
111
  payload: Dict[str, Any],
@@ -116,6 +173,17 @@ def create_upstream_response(
116
  request_payload = dict(payload)
117
  request_payload["messages"] = normalize_messages(request_payload.get("messages"))
118
  request_payload["model"] = model_override
 
 
 
 
 
 
 
 
 
 
 
119
  return requests.post(
120
  f"{upstream_base}/chat/completions",
121
  headers=build_headers(api_key, extra_headers),
@@ -240,6 +308,17 @@ class Handler(BaseHTTPRequestHandler):
240
  if payload is None:
241
  return
242
 
 
 
 
 
 
 
 
 
 
 
 
243
  if not PRIMARY_BASE_URL or not PRIMARY_MODEL:
244
  self._send_json(500, {"error": {"message": "Primary model not configured"}})
245
  return
@@ -253,6 +332,14 @@ class Handler(BaseHTTPRequestHandler):
253
  PRIMARY_API_KEY,
254
  PRIMARY_MODEL,
255
  )
 
 
 
 
 
 
 
 
256
  if primary_response.status_code < 400:
257
  self._relay_response(primary_response, stream)
258
  return
@@ -291,6 +378,14 @@ class Handler(BaseHTTPRequestHandler):
291
  "X-Title": FALLBACK_TITLE,
292
  },
293
  )
 
 
 
 
 
 
 
 
294
  self._relay_response(fallback_response, stream)
295
  except requests.RequestException as error:
296
  self._send_json(502, {"error": {"message": f"Fallback upstream request failed: {error}"}})
 
22
  FALLBACK_MODEL = os.environ.get("FALLBACK_MODEL", "openrouter/free")
23
  FALLBACK_REFERER = os.environ.get("OPENROUTER_HTTP_REFERER", "https://huggingface.co")
24
  FALLBACK_TITLE = os.environ.get("OPENROUTER_X_TITLE", "Hermes HF Fallback")
25
+ VERBOSE_LOGGING = os.environ.get("FALLBACK_PROXY_VERBOSE", "true").lower() in {"1", "true", "yes", "on"}
26
+ MAX_LOG_CHARS = int(os.environ.get("FALLBACK_PROXY_MAX_LOG_CHARS", "6000"))
27
 
28
 
29
  def is_retryable(status_code: int) -> bool:
 
108
  return normalized_messages
109
 
110
 
111
+ def clip_text(value: str, limit: int = MAX_LOG_CHARS) -> str:
112
+ if len(value) <= limit:
113
+ return value
114
+ return value[:limit] + f"... [truncated {len(value) - limit} chars]"
115
+
116
+
117
+ def dump_json(data: Any) -> str:
118
+ try:
119
+ return clip_text(json.dumps(data, ensure_ascii=False, indent=2))
120
+ except Exception as exc:
121
+ return f"<json-dump-error: {exc}>"
122
+
123
+
124
+ def summarize_messages(messages: Any) -> Any:
125
+ if not isinstance(messages, list):
126
+ return messages
127
+ summary = []
128
+ for idx, message in enumerate(messages):
129
+ if not isinstance(message, dict):
130
+ summary.append({"index": idx, "type": type(message).__name__, "value": str(message)[:200]})
131
+ continue
132
+ content = message.get("content")
133
+ if isinstance(content, str):
134
+ content_preview = clip_text(content, 400)
135
+ content_type = "str"
136
+ elif isinstance(content, list):
137
+ content_preview = clip_text(json.dumps(content, ensure_ascii=False), 400)
138
+ content_type = "list"
139
+ elif isinstance(content, dict):
140
+ content_preview = clip_text(json.dumps(content, ensure_ascii=False), 400)
141
+ content_type = "dict"
142
+ else:
143
+ content_preview = clip_text(str(content), 400)
144
+ content_type = type(content).__name__
145
+ summary.append(
146
+ {
147
+ "index": idx,
148
+ "role": message.get("role"),
149
+ "content_type": content_type,
150
+ "content_preview": content_preview,
151
+ "has_tool_calls": bool(message.get("tool_calls")),
152
+ "tool_call_id": message.get("tool_call_id"),
153
+ "name": message.get("name"),
154
+ "keys": sorted(message.keys()),
155
+ }
156
+ )
157
+ return summary
158
+
159
+
160
+ def log_debug(title: str, data: Any) -> None:
161
+ if not VERBOSE_LOGGING:
162
+ return
163
+ print(f"[fallback-proxy] {title}:\n{dump_json(data)}")
164
+
165
+
166
  def create_upstream_response(
167
  upstream_base: str,
168
  payload: Dict[str, Any],
 
173
  request_payload = dict(payload)
174
  request_payload["messages"] = normalize_messages(request_payload.get("messages"))
175
  request_payload["model"] = model_override
176
+ log_debug(
177
+ "outbound_request",
178
+ {
179
+ "upstream_base": upstream_base,
180
+ "model_override": model_override,
181
+ "stream": bool(request_payload.get("stream")),
182
+ "keys": sorted(request_payload.keys()),
183
+ "message_summary": summarize_messages(request_payload.get("messages")),
184
+ "payload": request_payload,
185
+ },
186
+ )
187
  return requests.post(
188
  f"{upstream_base}/chat/completions",
189
  headers=build_headers(api_key, extra_headers),
 
308
  if payload is None:
309
  return
310
 
311
+ log_debug(
312
+ "incoming_request",
313
+ {
314
+ "path": self.path,
315
+ "keys": sorted(payload.keys()),
316
+ "stream": bool(payload.get("stream")),
317
+ "message_summary": summarize_messages(payload.get("messages")),
318
+ "payload": payload,
319
+ },
320
+ )
321
+
322
  if not PRIMARY_BASE_URL or not PRIMARY_MODEL:
323
  self._send_json(500, {"error": {"message": "Primary model not configured"}})
324
  return
 
332
  PRIMARY_API_KEY,
333
  PRIMARY_MODEL,
334
  )
335
+ log_debug(
336
+ "primary_response",
337
+ {
338
+ "status_code": primary_response.status_code,
339
+ "headers": dict(primary_response.headers),
340
+ "body_preview": clip_text(primary_response.text if not stream else "<stream-response>"),
341
+ },
342
+ )
343
  if primary_response.status_code < 400:
344
  self._relay_response(primary_response, stream)
345
  return
 
378
  "X-Title": FALLBACK_TITLE,
379
  },
380
  )
381
+ log_debug(
382
+ "fallback_response",
383
+ {
384
+ "status_code": fallback_response.status_code,
385
+ "headers": dict(fallback_response.headers),
386
+ "body_preview": clip_text(fallback_response.text if not stream else "<stream-response>"),
387
+ },
388
+ )
389
  self._relay_response(fallback_response, stream)
390
  except requests.RequestException as error:
391
  self._send_json(502, {"error": {"message": f"Fallback upstream request failed: {error}"}})