Files
OpenJarvis/src/openjarvis/server/app.py
T
Jon Saad-FalconandClaude Opus 4.6 d89bbcce52 feat: upgrade eval pipeline with agentic runner, telemetry session, and savings meter
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>
2026-03-06 04:22:55 +00:00

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"]