mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 10:52:15 +00:00
Add 9 capabilities to match IPW pipeline: Eval Pipeline: - AgenticRunner for multi-turn agent execution with per-turn trace decomposition - QueryTrace/TurnTrace data model for agentic workload telemetry - EventRecorder for thread-safe agent event collection - TerminalBenchTaskEnv for Docker-based task execution - Cost computation via engine/cloud.py PRICING table - Rich export: JSONL, HF Arrow, summary JSON, artifacts manifest - CLI: --agentic, --concurrency, --query-timeout flags Telemetry: - TelemetrySession with background-sampling ring buffer (Python fallback) - Phase metrics: prefill/decode energy split at TTFT boundary - ITL percentile tracking (p50/p90/p95/p99) - FLOPs estimation and MFU computation - EnergyMonitor.snapshot() method Rust Performance Layer: - Ring buffer with binary search O(log n) window queries - Trapezoidal energy integration - Phase metrics, ITL stats, FLOPs estimation in Rust - PyO3 bindings for all new telemetry modules (50 Rust tests) Savings Meter & Benchmarks: - Use-case benchmark datasets (coding, email, research, knowledge, morning brief) - Savings dashboard component with cost comparison visualization - Cloud cost calculator and comparison server routes - Use-case eval configs for multiple agent/engine combinations Tests: 80 new tests (3779 total pass, 37 skipped, 0 failures) Lint: ruff check src/ tests/ — all checks passed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
168 lines
5.3 KiB
Python
168 lines
5.3 KiB
Python
"""FastAPI application factory for the OpenJarvis API server."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import time
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.responses import FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from openjarvis.server.api_routes import include_all_routes
|
|
from openjarvis.server.comparison import comparison_router
|
|
from openjarvis.server.dashboard import dashboard_router
|
|
from openjarvis.server.routes import router
|
|
|
|
# No-cache headers applied to static file responses
|
|
_NO_CACHE_HEADERS = {
|
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
"Pragma": "no-cache",
|
|
"Expires": "0",
|
|
}
|
|
|
|
|
|
class _NoCacheStaticFiles(StaticFiles):
|
|
"""StaticFiles subclass that adds no-cache headers to every response."""
|
|
|
|
async def __call__(self, scope, receive, send):
|
|
async def _send_with_headers(message):
|
|
if message["type"] == "http.response.start":
|
|
extra = [
|
|
(k.encode(), v.encode()) for k, v in _NO_CACHE_HEADERS.items()
|
|
]
|
|
# Remove etag and last-modified
|
|
existing = [
|
|
(k, v)
|
|
for k, v in message.get("headers", [])
|
|
if k.lower() not in (b"etag", b"last-modified")
|
|
]
|
|
message = {**message, "headers": existing + extra}
|
|
await send(message)
|
|
|
|
await super().__call__(scope, receive, _send_with_headers)
|
|
|
|
|
|
def create_app(
|
|
engine,
|
|
model: str,
|
|
*,
|
|
agent=None,
|
|
bus=None,
|
|
engine_name: str = "",
|
|
agent_name: str = "",
|
|
channel_bridge=None,
|
|
config=None,
|
|
speech_backend=None,
|
|
) -> FastAPI:
|
|
"""Create and configure the FastAPI application.
|
|
|
|
Parameters
|
|
----------
|
|
engine:
|
|
The inference engine to use for completions.
|
|
model:
|
|
Default model name.
|
|
agent:
|
|
Optional agent instance for agent-mode completions.
|
|
bus:
|
|
Optional event bus for telemetry.
|
|
channel_bridge:
|
|
Optional channel bridge for multi-platform messaging.
|
|
config:
|
|
Optional JarvisConfig for security guardrails and other settings.
|
|
"""
|
|
# Wrap engine with security guardrails if configured
|
|
security_enabled = (
|
|
config is not None
|
|
and getattr(config, "security", None)
|
|
and config.security.enabled
|
|
)
|
|
if security_enabled:
|
|
try:
|
|
from openjarvis.security.guardrails import GuardrailsEngine
|
|
from openjarvis.security.scanner import PIIScanner, SecretScanner
|
|
from openjarvis.security.types import RedactionMode
|
|
|
|
scanners = []
|
|
if config.security.secret_scanner:
|
|
scanners.append(SecretScanner())
|
|
if config.security.pii_scanner:
|
|
scanners.append(PIIScanner())
|
|
if scanners:
|
|
mode = RedactionMode(config.security.mode)
|
|
engine = GuardrailsEngine(
|
|
engine,
|
|
scanners=scanners,
|
|
mode=mode,
|
|
scan_input=config.security.scan_input,
|
|
scan_output=config.security.scan_output,
|
|
bus=bus,
|
|
)
|
|
except Exception:
|
|
pass # security is best-effort
|
|
|
|
app = FastAPI(
|
|
title="OpenJarvis API",
|
|
description="OpenAI-compatible API server for OpenJarvis",
|
|
version="1.0.0",
|
|
)
|
|
|
|
# Store dependencies in app state
|
|
app.state.engine = engine
|
|
app.state.model = model
|
|
app.state.agent = agent
|
|
app.state.bus = bus
|
|
app.state.engine_name = engine_name
|
|
app.state.agent_name = agent_name or (
|
|
getattr(agent, "agent_id", None) if agent else None
|
|
)
|
|
app.state.channel_bridge = channel_bridge
|
|
app.state.speech_backend = speech_backend
|
|
app.state.session_start = time.time()
|
|
|
|
app.include_router(router)
|
|
app.include_router(dashboard_router)
|
|
app.include_router(comparison_router)
|
|
include_all_routes(app)
|
|
|
|
# Add security headers middleware
|
|
try:
|
|
from openjarvis.server.middleware import create_security_middleware
|
|
|
|
middleware_cls = create_security_middleware()
|
|
if middleware_cls is not None:
|
|
app.add_middleware(middleware_cls)
|
|
except Exception:
|
|
pass # middleware is best-effort
|
|
|
|
# Serve static frontend assets if the static/ directory exists
|
|
static_dir = pathlib.Path(__file__).parent / "static"
|
|
if static_dir.is_dir():
|
|
assets_dir = static_dir / "assets"
|
|
if assets_dir.is_dir():
|
|
app.mount(
|
|
"/assets",
|
|
_NoCacheStaticFiles(directory=assets_dir),
|
|
name="static-assets",
|
|
)
|
|
|
|
@app.get("/{full_path:path}")
|
|
async def spa_catch_all(full_path: str):
|
|
"""Serve static files directly, fall back to index.html for SPA routes."""
|
|
if full_path:
|
|
candidate = (static_dir / full_path).resolve()
|
|
# Path traversal prevention
|
|
resolved_root = static_dir.resolve()
|
|
if candidate.is_relative_to(resolved_root) and candidate.is_file():
|
|
return FileResponse(candidate, headers=_NO_CACHE_HEADERS)
|
|
return FileResponse(
|
|
static_dir / "index.html",
|
|
headers=_NO_CACHE_HEADERS,
|
|
)
|
|
|
|
return app
|
|
|
|
|
|
__all__ = ["create_app"]
|