Spaces:
Running
Running
File size: 10,814 Bytes
fa81843 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | """
InferRoute Plugins Package.
Provides specific high-level API endpoints for Image OCR, Quant.ai, Paper Reader, Text Summarization, and Translation.
"""
import base64
import time
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, status, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from inferroute.auth import verify_api_key
from inferroute.adapters.gemini import GeminiAdapter
from inferroute.config import settings
from inferroute.plugins.quant import router as quant_router
from inferroute.plugins.paper import router as paper_router
logger = logging.getLogger("inferroute.plugins")
router = APIRouter(prefix="/v1/plugins", tags=["AI Plugins"])
router.include_router(quant_router)
router.include_router(paper_router)
# Reuse the Gemini adapter for high-performance low-cost inference
gemini_adapter = GeminiAdapter()
@router.post("/ocr")
async def plugin_ocr(
background_tasks: BackgroundTasks,
image: UploadFile = File(...),
language: str = Form("auto"),
tenant_id: str = Depends(verify_api_key)
):
"""
OCR / Image Text Recognition Plugin.
Uploads an image, extracts all text and structures it.
"""
start_time = time.time()
try:
# Read file bytes
image_bytes = await image.read()
if not image_bytes:
raise HTTPException(status_code=400, detail="Uploaded file is empty")
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
mime_type = image.content_type or "image/jpeg"
# Construct prompt
prompt = (
f"You are an expert OCR engine. Extract all text from this image. "
f"Preserve formatting, layout, tables, and paragraphs where possible. "
f"Target language setting: {language}. "
f"Return ONLY the extracted text content. Do not write any greetings or explanations."
)
# Construct OpenAI-compatible multimodal payload
payload = {
"model": "gemini-1.5-flash",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {"url": f"data:{mime_type};base64,{image_b64}"}
}
]
}
],
"tenant_id": tenant_id
}
# Run completion
response = await gemini_adapter.generate(payload)
choices = response.get("choices", [])
extracted_text = choices[0].get("message", {}).get("content", "") if choices else ""
if not extracted_text:
if gemini_adapter.mock_mode:
extracted_text = (
f"--- [Mock OCR Result for {image.filename}] ---\n"
f"Receipt / Document Details:\n"
f"Date: {time.strftime('%Y-%m-%d')}\n"
f"Total Amount: $128.50\n"
f"Merchant: Antigravity AI Coffee Hub\n"
f"Items Purchased:\n"
f"1. Double Espresso (x2) - $8.00\n"
f"2. Smart Routing Gateway Sub (Monthly) - $120.50\n"
f"Thank you for your business!"
)
else:
raise HTTPException(status_code=502, detail="Failed to extract text from the image")
latency_ms = (time.time() - start_time) * 1000.0
usage = response.get("usage", {})
cost_usd = usage.get("estimated_cost_usd", 0.0)
from inferroute.main import db_log_request
background_tasks.add_task(
db_log_request,
tenant_id=tenant_id,
model="gemini-1.5-flash",
logical_model="plugin/ocr",
provider="gemini",
prompt_tokens=usage.get("prompt_tokens", 250),
completion_tokens=usage.get("completion_tokens", 100),
cost_usd=cost_usd,
cache_hit=False,
cache_type=None,
prefix_cache_hit=False,
dedup_hit=False,
primary_backend="gemini",
selected_backend="gemini",
fallback_count=0,
routing_policy="plugin",
circuit_state="CLOSED",
slo_met=latency_ms <= settings.SLO_P95_MS,
status_str="completed",
error_message=None,
queue_ms=0.0,
ttft_ms=latency_ms,
latency_ms=latency_ms
)
return {
"success": True,
"plugin": "ocr",
"filename": image.filename,
"extracted_text": extracted_text,
"latency_ms": round(latency_ms, 1),
"estimated_cost_usd": cost_usd
}
except Exception as e:
logger.error(f"OCR plugin execution failed: {e}")
raise HTTPException(status_code=500, detail=f"OCR failed: {str(e)}")
@router.post("/summarize")
async def plugin_summarize(
background_tasks: BackgroundTasks,
text: str = Form(...),
max_length: int = Form(300),
tenant_id: str = Depends(verify_api_key)
):
"""
Summarize Plugin.
Summarizes long input text and lists key bullet points.
"""
start_time = time.time()
try:
if not text.strip():
raise HTTPException(status_code=400, detail="Text body cannot be empty")
prompt = (
f"Please summarize the following text. Highlight the core message and key takeaways "
f"using bullet points. Limit the summary to approximately {max_length} words. \n\n"
f"Text content:\n{text}"
)
payload = {
"model": "gemini-1.5-flash",
"messages": [{"role": "user", "content": prompt}],
"tenant_id": tenant_id
}
response = await gemini_adapter.generate(payload)
choices = response.get("choices", [])
summary_text = choices[0].get("message", {}).get("content", "") if choices else ""
if not summary_text and gemini_adapter.mock_mode:
summary_text = (
f"### [Mock Summary] Key Takeaways:\n"
f"- The input document was successfully analyzed.\n"
f"- A 56% reduction in operational cost was observed when routing model calls.\n"
f"- Concurrency limiters protected model nodes from overload."
)
latency_ms = (time.time() - start_time) * 1000.0
usage = response.get("usage", {})
cost_usd = usage.get("estimated_cost_usd", 0.0)
from inferroute.main import db_log_request
background_tasks.add_task(
db_log_request,
tenant_id=tenant_id,
model="gemini-1.5-flash",
logical_model="plugin/summarize",
provider="gemini",
prompt_tokens=usage.get("prompt_tokens", 50),
completion_tokens=usage.get("completion_tokens", 80),
cost_usd=cost_usd,
cache_hit=False,
cache_type=None,
prefix_cache_hit=False,
dedup_hit=False,
primary_backend="gemini",
selected_backend="gemini",
fallback_count=0,
routing_policy="plugin",
circuit_state="CLOSED",
slo_met=latency_ms <= settings.SLO_P95_MS,
status_str="completed",
error_message=None,
queue_ms=0.0,
ttft_ms=latency_ms,
latency_ms=latency_ms
)
return {
"success": True,
"plugin": "summarize",
"summary": summary_text,
"latency_ms": round(latency_ms, 1),
"estimated_cost_usd": cost_usd
}
except Exception as e:
logger.error(f"Summarize plugin failed: {e}")
raise HTTPException(status_code=500, detail=f"Summarize failed: {str(e)}")
@router.post("/translate")
async def plugin_translate(
background_tasks: BackgroundTasks,
text: str = Form(...),
target_lang: str = Form("Chinese"),
tenant_id: str = Depends(verify_api_key)
):
"""
Translate Plugin.
Translates input text into the target language.
"""
start_time = time.time()
try:
if not text.strip():
raise HTTPException(status_code=400, detail="Text body cannot be empty")
prompt = (
f"You are a professional translator. Translate the following text into {target_lang}. "
f"Maintain the original tone, context, and formatting. "
f"Return ONLY the translation. Do not include notes or explanations.\n\n"
f"Text to translate:\n{text}"
)
payload = {
"model": "gemini-1.5-flash",
"messages": [{"role": "user", "content": prompt}],
"tenant_id": tenant_id
}
response = await gemini_adapter.generate(payload)
choices = response.get("choices", [])
translated_text = choices[0].get("message", {}).get("content", "") if choices else ""
if not translated_text and gemini_adapter.mock_mode:
translated_text = f"这是模拟翻译结果 (Target language: {target_lang}):\n{text}"
latency_ms = (time.time() - start_time) * 1000.0
usage = response.get("usage", {})
cost_usd = usage.get("estimated_cost_usd", 0.0)
from inferroute.main import db_log_request
background_tasks.add_task(
db_log_request,
tenant_id=tenant_id,
model="gemini-1.5-flash",
logical_model="plugin/translate",
provider="gemini",
prompt_tokens=usage.get("prompt_tokens", 40),
completion_tokens=usage.get("completion_tokens", 40),
cost_usd=cost_usd,
cache_hit=False,
cache_type=None,
prefix_cache_hit=False,
dedup_hit=False,
primary_backend="gemini",
selected_backend="gemini",
fallback_count=0,
routing_policy="plugin",
circuit_state="CLOSED",
slo_met=latency_ms <= settings.SLO_P95_MS,
status_str="completed",
error_message=None,
queue_ms=0.0,
ttft_ms=latency_ms,
latency_ms=latency_ms
)
return {
"success": True,
"plugin": "translate",
"translated_text": translated_text,
"latency_ms": round(latency_ms, 1),
"estimated_cost_usd": cost_usd
}
except Exception as e:
logger.error(f"Translate plugin failed: {e}")
raise HTTPException(status_code=500, detail=f"Translate failed: {str(e)}")
|