Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -264,13 +264,19 @@ OpenAI-style REST API for [PASITA](https://huggingface.co/OpceanAI/PASITA), an 8
|
|
| 264 |
from-scratch model that turns plain text into faithful Markdown. No API key is required.
|
| 265 |
Works with the OpenAI SDK by pointing `base_url` at `/v1`. The Gradio API and the MCP
|
| 266 |
server stay available at `?view=api`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
""".strip()
|
| 268 |
|
| 269 |
|
| 270 |
-
def _api_error(message: str, error_type: str = "invalid_request_error", status: int = 400, code: str | None = None) -> JSONResponse:
|
| 271 |
return JSONResponse(
|
| 272 |
status_code=status,
|
| 273 |
content={"error": {"message": message, "type": error_type, "param": None, "code": code}},
|
|
|
|
| 274 |
)
|
| 275 |
|
| 276 |
|
|
@@ -278,9 +284,9 @@ def _exception_to_error(exc: Exception) -> JSONResponse:
|
|
| 278 |
message = f"{type(exc).__name__}: {exc}"
|
| 279 |
text = message.lower()
|
| 280 |
if "zerogpu" in text and any(k in text for k in ("quota", "exhausted", "insufficient", "limit")):
|
| 281 |
-
return _api_error(str(exc), "rate_limit_error", status=429, code="gpu_quota_exceeded")
|
| 282 |
if any(k in text for k in ("illegal duration", "task aborted", "queue is full", "queue full")):
|
| 283 |
-
return _api_error(str(exc), "rate_limit_error", status=429, code="gpu_busy")
|
| 284 |
return _api_error(message, "server_error", status=500)
|
| 285 |
|
| 286 |
|
|
@@ -311,6 +317,7 @@ class ChatCompletionRequest(BaseModel):
|
|
| 311 |
model: str | None = "pasita-v1"
|
| 312 |
messages: list[ChatMessage]
|
| 313 |
stream: bool = False
|
|
|
|
| 314 |
max_tokens: int | None = None
|
| 315 |
candidates: int = 3
|
| 316 |
structured_fallback: bool = True
|
|
@@ -370,6 +377,18 @@ class _LegacyConvertBodyMiddleware:
|
|
| 370 |
|
| 371 |
server.add_middleware(_LegacyConvertBodyMiddleware)
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
|
| 374 |
@server.exception_handler(fastapi.exceptions.RequestValidationError)
|
| 375 |
async def validation_error_handler(request: fastapi.Request, exc: fastapi.exceptions.RequestValidationError):
|
|
@@ -486,6 +505,8 @@ def chat_completions(body: ChatCompletionRequest, request: fastapi.Request) -> d
|
|
| 486 |
markdown = payload["markdown"]
|
| 487 |
|
| 488 |
if body.stream:
|
|
|
|
|
|
|
| 489 |
def sse():
|
| 490 |
def chunk(delta, finish=None, usage=None):
|
| 491 |
data = {
|
|
@@ -501,7 +522,7 @@ def chat_completions(body: ChatCompletionRequest, request: fastapi.Request) -> d
|
|
| 501 |
|
| 502 |
yield chunk({"role": "assistant", "content": ""})
|
| 503 |
yield chunk({"content": markdown})
|
| 504 |
-
yield chunk({}, finish="stop", usage=payload["usage"])
|
| 505 |
yield "data: [DONE]\n\n"
|
| 506 |
|
| 507 |
return StreamingResponse(
|
|
|
|
| 264 |
from-scratch model that turns plain text into faithful Markdown. No API key is required.
|
| 265 |
Works with the OpenAI SDK by pointing `base_url` at `/v1`. The Gradio API and the MCP
|
| 266 |
server stay available at `?view=api`.
|
| 267 |
+
|
| 268 |
+
Honored request fields: `model`, `messages`, `stream`, `stream_options.include_usage`,
|
| 269 |
+
`max_tokens` (upper bound), `candidates` (1-3), `structured_fallback`. Sampling
|
| 270 |
+
temperature is fixed at 0.3 because that is the measured recipe; other OpenAI fields
|
| 271 |
+
are accepted but ignored.
|
| 272 |
""".strip()
|
| 273 |
|
| 274 |
|
| 275 |
+
def _api_error(message: str, error_type: str = "invalid_request_error", status: int = 400, code: str | None = None, headers: dict | None = None) -> JSONResponse:
|
| 276 |
return JSONResponse(
|
| 277 |
status_code=status,
|
| 278 |
content={"error": {"message": message, "type": error_type, "param": None, "code": code}},
|
| 279 |
+
headers=headers,
|
| 280 |
)
|
| 281 |
|
| 282 |
|
|
|
|
| 284 |
message = f"{type(exc).__name__}: {exc}"
|
| 285 |
text = message.lower()
|
| 286 |
if "zerogpu" in text and any(k in text for k in ("quota", "exhausted", "insufficient", "limit")):
|
| 287 |
+
return _api_error(str(exc), "rate_limit_error", status=429, code="gpu_quota_exceeded", headers={"Retry-After": "60"})
|
| 288 |
if any(k in text for k in ("illegal duration", "task aborted", "queue is full", "queue full")):
|
| 289 |
+
return _api_error(str(exc), "rate_limit_error", status=429, code="gpu_busy", headers={"Retry-After": "30"})
|
| 290 |
return _api_error(message, "server_error", status=500)
|
| 291 |
|
| 292 |
|
|
|
|
| 317 |
model: str | None = "pasita-v1"
|
| 318 |
messages: list[ChatMessage]
|
| 319 |
stream: bool = False
|
| 320 |
+
stream_options: dict | None = None
|
| 321 |
max_tokens: int | None = None
|
| 322 |
candidates: int = 3
|
| 323 |
structured_fallback: bool = True
|
|
|
|
| 377 |
|
| 378 |
server.add_middleware(_LegacyConvertBodyMiddleware)
|
| 379 |
|
| 380 |
+
try:
|
| 381 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 382 |
+
|
| 383 |
+
server.add_middleware(
|
| 384 |
+
CORSMiddleware,
|
| 385 |
+
allow_origins=["*"],
|
| 386 |
+
allow_methods=["GET", "POST", "OPTIONS"],
|
| 387 |
+
allow_headers=["*"],
|
| 388 |
+
)
|
| 389 |
+
except Exception:
|
| 390 |
+
pass
|
| 391 |
+
|
| 392 |
|
| 393 |
@server.exception_handler(fastapi.exceptions.RequestValidationError)
|
| 394 |
async def validation_error_handler(request: fastapi.Request, exc: fastapi.exceptions.RequestValidationError):
|
|
|
|
| 505 |
markdown = payload["markdown"]
|
| 506 |
|
| 507 |
if body.stream:
|
| 508 |
+
include_usage = bool((body.stream_options or {}).get("include_usage"))
|
| 509 |
+
|
| 510 |
def sse():
|
| 511 |
def chunk(delta, finish=None, usage=None):
|
| 512 |
data = {
|
|
|
|
| 522 |
|
| 523 |
yield chunk({"role": "assistant", "content": ""})
|
| 524 |
yield chunk({"content": markdown})
|
| 525 |
+
yield chunk({}, finish="stop", usage=payload["usage"] if include_usage else None)
|
| 526 |
yield "data: [DONE]\n\n"
|
| 527 |
|
| 528 |
return StreamingResponse(
|