InferRoute / inferroute /plugins /__init__.py
Ypeng12's picture
feat: platform ecosystem showcase and UI upgrade
fa81843
Raw
History Blame Contribute Delete
10.8 kB
"""
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)}")