From ace4044f43b35dd7e3125618327ea76f4aada612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Lange?= Date: Wed, 1 Apr 2026 03:14:37 +0200 Subject: [PATCH 1/2] Merge upstream provider metadata support with cost truth and recommendations - Integrate upstream shared provider metadata feature (environment variables, external catalog loading) - Preserve caching and cost truth reporting for pricing metadata - Add provider pricing lookup with support for external catalog, overlay, and built-in registry - Extend dashboard with cost truth metrics and priority clusters - Add recommendations test --- faigate/dashboard_web.py | 39 +++ faigate/provider_catalog.py | 456 +++++++++++++++++++++++++++++++++ scripts/sync-metadata.sh | 46 ++++ tests/test_provider_catalog.py | 43 ++++ 4 files changed, 584 insertions(+) create mode 100644 scripts/sync-metadata.sh diff --git a/faigate/dashboard_web.py b/faigate/dashboard_web.py index 5c19798..a5eef1c 100644 --- a/faigate/dashboard_web.py +++ b/faigate/dashboard_web.py @@ -2121,6 +2121,24 @@ def _inline_svg(name: str) -> str:
+
+
+
+

Priority clusters

+

Catalog health areas needing attention.

+
+
+
+
+
+
+
+

Recommendations

+

Actionable guidance based on catalog health.

+
+
+
+
@@ -2655,6 +2673,10 @@ def _inline_svg(name: str) -> str: const traces = bundle.traces.traces || []; const catalogItems = bundle.catalog.items || []; const sourceCatalog = bundle.catalog.source_catalog || {}; + const costTruth = bundle.catalog.cost_truth || {}; + const priorityClusters = bundle.catalog.priority_clusters || []; + const recommendations = bundle.catalog.recommendations || []; + const trackedProviders = bundle.catalog.tracked_providers || 0; const catalogAlerts = (bundle.catalog.alerts || []).concat(bundle.catalog.source_alerts || []); const operatorRows = bundle.operators.events || []; const modalityRows = bundle.stats.modalities || []; @@ -2945,6 +2967,9 @@ def _inline_svg(name: str) -> str: {kicker:'Volatile offers', value:String(catalogItems.filter(row => String(row.volatility || '').toLowerCase().includes('high')).length), detail:'Offers flagged as highly volatile', tone:'orange'}, {kicker:'Untracked providers', value:String(Math.max(0, (bundle.catalog.total_providers || providers.length) - (bundle.catalog.tracked_providers || 0))), detail:'Configured providers outside coverage', tone:'blue'}, {kicker:'Recent changes', value:String(sourceCatalog.recent_changes || 0), detail:'Source updates in the review window', tone:'lime'}, + {kicker:'Pricing coverage', value:String(costTruth.tracked_with_pricing || 0) + '/' + trackedProviders, detail:'Providers with pricing metadata', tone:'blue'}, + {kicker:'Numeric rates', value:String(costTruth.tracked_with_numeric_rates || 0) + '/' + trackedProviders, detail:'Providers with numeric rates', tone:'green'}, + {kicker:'Fresh pricing', value:String((costTruth.pricing_freshness || {}).fresh || 0) + '/' + (costTruth.tracked_with_pricing || 0), detail:'Pricing metadata updated within freshness window', tone:'lime'}, ].map(metricCard).join(''); $('#catalog-summary').innerHTML = [ {title:'Tracked providers', body:String(bundle.catalog.tracked_providers || 0) + ' of ' + String(bundle.catalog.total_providers || providers.length) + ' configured routes are covered.'}, @@ -2966,6 +2991,20 @@ def _inline_svg(name: str) -> str: tone: 'green', empty: 'No refresh guidance in this scope', }); + $('#catalog-priority-clusters').innerHTML = barList(priorityClusters.slice(0, 6), { + label: cluster => cluster.name, + detail: cluster => cluster.description, + value: cluster => cluster.item_count, + format: (value, cluster) => value + '/' + cluster.total_items, + tone: cluster => cluster.priority === 'high' ? 'orange' : cluster.priority === 'medium' ? 'blue' : 'green', + empty: 'No priority clusters to display', + }); + $('#catalog-recommendations').innerHTML = recommendations.length ? recommendations.slice(0, 5).map(rec => alertCard({ + level: rec.priority || 'medium', + headline: rec.title || rec.id, + detail: rec.description || '', + suggestion: rec.action || '', + })).join('') : empty('No recommendations in this scope', 'Catalog health is fully aligned.'); $('#catalog-table tbody').innerHTML = catalogItems.length ? catalogItems.map(row => ` ${esc(row.provider || '—')} diff --git a/faigate/provider_catalog.py b/faigate/provider_catalog.py index 7030905..4c1bfaf 100644 --- a/faigate/provider_catalog.py +++ b/faigate/provider_catalog.py @@ -16,6 +16,15 @@ get_canonical_model_catalog, get_provider_lane_binding, ) +from . import registry + + +# Path to external fusionaize-metadata repository +_EXTERNAL_METADATA_ROOT = Path( + "/Users/andrelange/Documents/repositories/github/fusionaize-metadata" +) +_EXTERNAL_CATALOG_PATH = _EXTERNAL_METADATA_ROOT / "providers" / "catalog.v1.json" +_EXTERNAL_OVERLAY_PATH = _EXTERNAL_METADATA_ROOT / "products" / "gate" / "overlays.v1.json" _COMMUNITY_WATCHLIST = { "label": "free-llm-api-resources", @@ -33,6 +42,189 @@ _DEFAULT_METADATA_PRODUCT = "gate" _METADATA_CATALOG_RELATIVE_PATH = Path("providers") / "catalog.v1.json" +# Hardcoded fallback path for external metadata repository (legacy) +_EXTERNAL_METADATA_ROOT = Path( + "/Users/andrelange/Documents/repositories/github/fusionaize-metadata" +) + +# Cache for external metadata +_EXTERNAL_CATALOG_CACHE: dict[str, Any] | None = None +_EXTERNAL_CATALOG_MTIME: float = 0.0 +_EXTERNAL_OVERLAY_CACHE: dict[str, Any] | None = None +_EXTERNAL_OVERLAY_MTIME: float = 0.0 + + +def _get_external_metadata_root() -> Path: + """Determine the external metadata root directory from environment variables.""" + metadata_dir = os.environ.get(_EXTERNAL_CATALOG_DIR_ENV, "").strip() + if metadata_dir: + return Path(metadata_dir).expanduser() + # Fallback to hardcoded path + return _EXTERNAL_METADATA_ROOT + + +def _get_external_catalog_path() -> Path: + """Get path to external catalog.v1.json.""" + metadata_file = os.environ.get(_EXTERNAL_CATALOG_ENV, "").strip() + if metadata_file: + return Path(metadata_file).expanduser() + # Fallback to default location relative to metadata root + root = _get_external_metadata_root() + return root / "providers" / "catalog.v1.json" + + +def _get_external_overlay_path() -> Path: + """Get path to external overlays.v1.json for the current product.""" + product = os.environ.get(_EXTERNAL_CATALOG_PRODUCT_ENV, _DEFAULT_METADATA_PRODUCT).strip() + if not product: + product = _DEFAULT_METADATA_PRODUCT + root = _get_external_metadata_root() + return root / "products" / product / "overlays.v1.json" + + +def _load_external_catalog() -> dict[str, Any]: + """Load external catalog.v1.json if available.""" + global _EXTERNAL_CATALOG_CACHE, _EXTERNAL_CATALOG_MTIME + + catalog_path = _get_external_catalog_path() + + # Check if cache is still valid + if _EXTERNAL_CATALOG_CACHE is not None and catalog_path.exists(): + current_mtime = catalog_path.stat().st_mtime + if current_mtime <= _EXTERNAL_CATALOG_MTIME: + return _EXTERNAL_CATALOG_CACHE + # File has changed, invalidate cache + _EXTERNAL_CATALOG_CACHE = None + + if not catalog_path.exists(): + _EXTERNAL_CATALOG_CACHE = {} + _EXTERNAL_CATALOG_MTIME = 0.0 + return {} + + try: + with open(catalog_path, "r", encoding="utf-8") as f: + data = json.load(f) + _EXTERNAL_CATALOG_CACHE = data.get("providers", {}) + _EXTERNAL_CATALOG_MTIME = catalog_path.stat().st_mtime + except Exception: + _EXTERNAL_CATALOG_CACHE = {} + _EXTERNAL_CATALOG_MTIME = 0.0 + + return _EXTERNAL_CATALOG_CACHE + + +def _load_external_overlay() -> dict[str, Any]: + """Load external overlays.v1.json if available.""" + global _EXTERNAL_OVERLAY_CACHE, _EXTERNAL_OVERLAY_MTIME + + overlay_path = _get_external_overlay_path() + + # Check if cache is still valid + if _EXTERNAL_OVERLAY_CACHE is not None and overlay_path.exists(): + current_mtime = overlay_path.stat().st_mtime + if current_mtime <= _EXTERNAL_OVERLAY_MTIME: + return _EXTERNAL_OVERLAY_CACHE + # File has changed, invalidate cache + _EXTERNAL_OVERLAY_CACHE = None + + if not overlay_path.exists(): + _EXTERNAL_OVERLAY_CACHE = {} + _EXTERNAL_OVERLAY_MTIME = 0.0 + return {} + + try: + with open(overlay_path, "r", encoding="utf-8") as f: + data = json.load(f) + _EXTERNAL_OVERLAY_CACHE = data.get("providers", {}) + _EXTERNAL_OVERLAY_MTIME = overlay_path.stat().st_mtime + except Exception: + _EXTERNAL_OVERLAY_CACHE = {} + _EXTERNAL_OVERLAY_MTIME = 0.0 + + return _EXTERNAL_OVERLAY_CACHE + + +def _get_provider_pricing(provider_name: str) -> dict[str, Any]: + """Get pricing metadata for a provider from multiple sources.""" + pricing = {} + + # 1. Check external overlay (product-specific) + overlay = _load_external_overlay() + if provider_name in overlay: + provider_data = overlay[provider_name] + if "pricing" in provider_data: + pricing.update(provider_data["pricing"]) + + # 2. Check external base catalog + catalog = _load_external_catalog() + if provider_name in catalog: + provider_data = catalog[provider_name] + if "pricing" in provider_data: + # Overlay may have overridden some fields, merge + for key, value in provider_data["pricing"].items(): + if key not in pricing: + pricing[key] = value + + # Normalize pricing field names from external catalog + # Map input_cost_per_1m -> input, output_cost_per_1m -> output, cache_read_cost_per_1m -> cache_read + field_mapping = { + "input_cost_per_1m": "input", + "output_cost_per_1m": "output", + "cache_read_cost_per_1m": "cache_read", + } + for src, dst in field_mapping.items(): + if src in pricing and dst not in pricing: + pricing[dst] = pricing[src] + + # 3. Check built-in registry for numeric rates + # Map provider_name to registry key (simplistic: try exact match, then partial) + registry_key = None + if provider_name in registry.BUILTIN: + registry_key = provider_name + else: + # Special case mappings + special_mappings = { + "anthropic-haiku": "anthropic", + "anthropic-sonnet": "anthropic", + "anthropic-claude": "anthropic", + "gemini-flash": "google", + "gemini-flash-lite": "google", + "gemini-pro-high": "google", + "gemini-pro-low": "google", + "openai-gpt4o": "openai", + "openai-images": "openai", + "openai-codex": "openai", + "openrouter-fallback": "openrouter", + } + if provider_name in special_mappings: + registry_key = special_mappings[provider_name] + else: + # Try prefix matching (e.g., "mistral-large" -> "mistral") + for key in registry.BUILTIN: + if provider_name.startswith(key) or key.startswith(provider_name): + registry_key = key + break + # If still not found, try partial match in key + if registry_key is None: + for key in registry.BUILTIN: + if provider_name in key or key in provider_name: + registry_key = key + break + + if registry_key and "pricing" in registry.BUILTIN[registry_key]: + registry_pricing = registry.BUILTIN[registry_key]["pricing"] + # Merge numeric rates, but don't overwrite metadata fields + numeric_fields = {"input", "output", "cache_read"} + for field in numeric_fields: + if field in registry_pricing and registry_pricing[field]: + # Convert to float if not already + value = registry_pricing[field] + if isinstance(value, (int, float)) and value > 0 and field not in pricing: + pricing[field] = float(value) + + return pricing + + _CATALOG: dict[str, dict[str, Any]] = { "deepseek-chat": { "recommended_model": get_active_model_id("deepseek/chat"), @@ -223,6 +415,66 @@ "notes": "Quality-first Anthropic default", "last_reviewed": "2026-03-19", }, + "anthropic-haiku": { + "recommended_model": get_active_model_id("anthropic/haiku-4.5"), + "aliases": ["claude-haiku-3-5"], + "track": "stable", + "offer_track": "direct", + "provider_type": "direct", + "auth_modes": ["api_key"], + "volatility": "low", + "evidence_level": "official", + "official_source_url": "https://docs.anthropic.com/en/docs/about-claude/models", + "signup_url": "https://console.anthropic.com/", + "watch_sources": [], + "notes": "Fast and cheap Anthropic model", + "last_reviewed": "2026-04-01", + }, + "anthropic-sonnet": { + "recommended_model": get_active_model_id("anthropic/sonnet-4.6"), + "aliases": ["claude-sonnet-4-6"], + "track": "stable", + "offer_track": "direct", + "provider_type": "direct", + "auth_modes": ["api_key"], + "volatility": "low", + "evidence_level": "official", + "official_source_url": "https://docs.anthropic.com/en/docs/about-claude/models", + "signup_url": "https://console.anthropic.com/", + "watch_sources": [], + "notes": "Balanced Anthropic model", + "last_reviewed": "2026-04-01", + }, + "gemini-pro-high": { + "recommended_model": get_active_model_id("google/gemini-pro-high"), + "aliases": ["gemini-3.1-pro"], + "track": "stable", + "offer_track": "direct", + "provider_type": "direct", + "auth_modes": ["api_key"], + "volatility": "low", + "evidence_level": "official", + "official_source_url": "https://ai.google.dev/gemini-api/docs/models", + "signup_url": "https://aistudio.google.com/", + "watch_sources": [], + "notes": "High-quality Gemini Pro lane", + "last_reviewed": "2026-04-01", + }, + "gemini-pro-low": { + "recommended_model": get_active_model_id("google/gemini-pro-low"), + "aliases": ["gemini-3.1-pro"], + "track": "stable", + "offer_track": "direct", + "provider_type": "direct", + "auth_modes": ["api_key"], + "volatility": "low", + "evidence_level": "official", + "official_source_url": "https://ai.google.dev/gemini-api/docs/models", + "signup_url": "https://aistudio.google.com/", + "watch_sources": [], + "notes": "Balanced Gemini Pro lane", + "last_reviewed": "2026-04-01", + }, "clawrouter": { "recommended_model": "auto", "aliases": ["auto", "eco", "premium", "free"], @@ -528,6 +780,14 @@ def _tracked_item( lane = dict(provider.get("lane") or get_provider_lane_binding(provider_name)) canonical_catalog = get_canonical_model_catalog() canonical_entry = canonical_catalog.get(str(lane.get("canonical_model", "")), {}) + + # Get pricing metadata + pricing = _get_provider_pricing(provider_name) + has_numeric_rates = ( + bool(pricing.get("input")) or bool(pricing.get("output")) or bool(pricing.get("cache_read")) + ) + pricing_available = bool(pricing) + return { "provider": provider_name, "configured_model": model, @@ -558,6 +818,9 @@ def _tracked_item( canonical_entry.get("preferred_degrades", lane.get("degrade_to", [])) ), "lane": lane, + "pricing": pricing, + "pricing_available": pricing_available, + "has_numeric_rates": has_numeric_rates, } @@ -672,11 +935,204 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: ) ) + # Calculate cost truth statistics + cost_truth_stats = { + "tracked_with_pricing": 0, + "tracked_with_numeric_rates": 0, + "pricing_freshness": {"fresh": 0, "aging": 0, "stale": 0, "unknown": 0}, + "missing_pricing": 0, + } + + for item in items: + if item.get("status") != "tracked": + continue + + if item.get("pricing_available"): + cost_truth_stats["tracked_with_pricing"] += 1 + + # Check freshness from pricing metadata + pricing = item.get("pricing", {}) + freshness = pricing.get("freshness_status", "unknown") + if freshness in cost_truth_stats["pricing_freshness"]: + cost_truth_stats["pricing_freshness"][freshness] += 1 + else: + cost_truth_stats["pricing_freshness"]["unknown"] += 1 + + if item.get("has_numeric_rates"): + cost_truth_stats["tracked_with_numeric_rates"] += 1 + else: + cost_truth_stats["missing_pricing"] += 1 + + # Priority clusters based on catalog health + priority_clusters = [ + { + "id": "cost_truth", + "name": "Cost truth", + "description": "Provider pricing metadata completeness and freshness", + "priority": "high", + "item_count": cost_truth_stats["missing_pricing"], + "total_items": tracked, + }, + { + "id": "tracked_provider_coverage", + "name": "Tracked provider coverage", + "description": "Providers not yet in the curated catalog", + "priority": "medium", + "item_count": len(config.providers) - tracked, + "total_items": len(config.providers), + }, + { + "id": "provider_model_alignment", + "name": "Provider model alignment", + "description": "Configured models that don't match catalog recommendations", + "priority": "medium", + "item_count": sum( + 1 + for item in items + if item.get("status") == "tracked" and not item.get("model_matches_recommendation") + ), + "total_items": tracked, + }, + { + "id": "source_provenance_review", + "name": "Source provenance review", + "description": "Providers with unofficial evidence sources", + "priority": "low", + "item_count": sum( + 1 + for item in items + if item.get("status") == "tracked" and item.get("evidence_level") != "official" + ), + "total_items": tracked, + }, + { + "id": "volatile_offer_review", + "name": "Volatile offer review", + "description": "Providers on volatile offer tracks (free/credit/marketplace)", + "priority": "low", + "item_count": sum( + 1 + for item in items + if item.get("status") == "tracked" + and item.get("volatility") in {"medium", "high"} + and item.get("offer_track") in {"free", "credit", "byok", "marketplace"} + ), + "total_items": tracked, + }, + { + "id": "catalog_freshness", + "name": "Catalog freshness", + "description": "Catalog entries older than max age threshold", + "priority": "low", + "item_count": sum( + 1 + for item in items + if item.get("status") == "tracked" + and item.get("catalog_age_days", 0) > int(check_cfg.get("max_catalog_age_days", 30)) + ), + "total_items": tracked, + }, + ] + + # Determine next priority (first cluster with item_count > 0) + priority_next = None + for cluster in priority_clusters: + if cluster["item_count"] > 0: + priority_next = cluster["id"] + break + + # Generate actionable recommendations from priority clusters + recommendations = [] + for cluster in priority_clusters: + if cluster["item_count"] == 0: + continue + if cluster["id"] == "cost_truth": + recommendations.append( + { + "id": "improve_pricing_coverage", + "title": "Improve pricing metadata coverage", + "description": f"{cluster['item_count']} tracked providers lack numeric pricing rates.", + "priority": cluster["priority"], + "action": "Add numeric pricing rates to external catalog for providers missing rates.", + "cluster_id": cluster["id"], + } + ) + elif cluster["id"] == "tracked_provider_coverage": + recommendations.append( + { + "id": "expand_catalog_coverage", + "title": "Expand catalog coverage", + "description": f"{cluster['item_count']} configured providers are not yet tracked in the catalog.", + "priority": cluster["priority"], + "action": "Add catalog entries for untracked providers.", + "cluster_id": cluster["id"], + } + ) + elif cluster["id"] == "provider_model_alignment": + recommendations.append( + { + "id": "align_models_with_recommendations", + "title": "Align configured models with catalog recommendations", + "description": f"{cluster['item_count']} tracked providers have configured models that don't match catalog recommendations.", + "priority": cluster["priority"], + "action": "Update provider model configurations to match catalog recommendations.", + "cluster_id": cluster["id"], + } + ) + elif cluster["id"] == "source_provenance_review": + recommendations.append( + { + "id": "review_evidence_sources", + "title": "Review evidence sources", + "description": f"{cluster['item_count']} tracked providers rely on unofficial evidence sources.", + "priority": cluster["priority"], + "action": "Verify and potentially upgrade evidence sources to official documentation.", + "cluster_id": cluster["id"], + } + ) + elif cluster["id"] == "volatile_offer_review": + recommendations.append( + { + "id": "review_volatile_offers", + "title": "Review volatile offers", + "description": f"{cluster['item_count']} tracked providers are on volatile offer tracks (free/credit/marketplace).", + "priority": cluster["priority"], + "action": "Monitor these providers for changes in pricing, availability, or terms.", + "cluster_id": cluster["id"], + } + ) + elif cluster["id"] == "catalog_freshness": + recommendations.append( + { + "id": "refresh_stale_catalog_entries", + "title": "Refresh stale catalog entries", + "description": f"{cluster['item_count']} catalog entries are older than the maximum age threshold.", + "priority": cluster["priority"], + "action": "Review and update catalog entries to ensure they reflect current provider offerings.", + "cluster_id": cluster["id"], + } + ) + else: + recommendations.append( + { + "id": cluster["id"], + "title": cluster["name"], + "description": cluster["description"], + "priority": cluster["priority"], + "action": f"Address {cluster['item_count']} items in this category.", + "cluster_id": cluster["id"], + } + ) + return { "enabled": bool(check_cfg.get("enabled")), "tracked_providers": tracked, "total_providers": len(config.providers), "alert_count": len(alerts), + "cost_truth": cost_truth_stats, + "priority_clusters": priority_clusters, + "priority_next": priority_next, + "recommendations": recommendations, "recommendation_policy": { "provider_links_affect_ranking": False, "ranking_basis": [ diff --git a/scripts/sync-metadata.sh b/scripts/sync-metadata.sh new file mode 100644 index 0000000..d8f9054 --- /dev/null +++ b/scripts/sync-metadata.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Synchronize external metadata repository with local copy + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +METADATA_DIR="/Users/andrelange/Documents/repositories/github/fusionaize-metadata" +LOG_FILE="/tmp/faigate-metadata-sync.log" + +echo "$(date): Starting metadata synchronization" >> "$LOG_FILE" + +cd "$METADATA_DIR" || { + echo "ERROR: Metadata directory not found: $METADATA_DIR" >> "$LOG_FILE" + exit 1 +} + +# Check if git is available +if ! command -v git &> /dev/null; then + echo "ERROR: git not found" >> "$LOG_FILE" + exit 1 +fi + +# Pull latest changes +echo "Pulling latest metadata from repository..." >> "$LOG_FILE" +if git pull origin main 2>&1 | tee -a "$LOG_FILE"; then + echo "$(date): Successfully updated metadata repository" >> "$LOG_FILE" + echo "Metadata updated successfully." + + # Optionally touch the catalog file to ensure mtime changes + # This ensures Gate picks up changes even if file content identical but metadata updated + if [ -f "providers/catalog.v1.json" ]; then + touch "providers/catalog.v1.json" + echo "Touched catalog file to update mtime" >> "$LOG_FILE" + fi + if [ -f "products/gate/overlays.v1.json" ]; then + touch "products/gate/overlays.v1.json" + echo "Touched overlay file to update mtime" >> "$LOG_FILE" + fi +else + echo "$(date): Failed to update metadata repository" >> "$LOG_FILE" + echo "ERROR: git pull failed" >> "$LOG_FILE" + exit 1 +fi + +echo "$(date): Synchronization completed" >> "$LOG_FILE" +exit 0 \ No newline at end of file diff --git a/tests/test_provider_catalog.py b/tests/test_provider_catalog.py index 6344d73..6262c5d 100644 --- a/tests/test_provider_catalog.py +++ b/tests/test_provider_catalog.py @@ -495,3 +495,46 @@ def test_materialize_provider_metadata_snapshot_writes_effective_catalog(tmp_pat assert written["providers"]["deepseek-chat"]["notes"] == "Gate note" assert output_path.exists() is True assert "Gate note" in output_path.read_text(encoding="utf-8") + + +def test_provider_catalog_report_includes_recommendations(tmp_path): + from faigate.config import load_config + from faigate.provider_catalog import build_provider_catalog_report + + cfg = load_config( + _write_config( + tmp_path, + """ +server: + host: "127.0.0.1" + port: 8090 +providers: + deepseek-chat: + backend: openai-compat + base_url: "https://api.deepseek.com/v1" + api_key: "secret" + model: "deepseek-chat" +fallback_chain: [] +metrics: + enabled: false +""", + ) + ) + + report = build_provider_catalog_report(cfg) + + # Recommendations field should be present + assert "recommendations" in report + assert isinstance(report["recommendations"], list) + + # If there are priority clusters with items, there should be recommendations + if any(cluster["item_count"] > 0 for cluster in report["priority_clusters"]): + assert len(report["recommendations"]) > 0 + # Each recommendation should have required fields + for rec in report["recommendations"]: + assert "id" in rec + assert "title" in rec + assert "description" in rec + assert "priority" in rec + assert "action" in rec + assert "cluster_id" in rec From 0a3db41b4e2886de9fc55b9b0352bb1aa48aee72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Lange?= Date: Wed, 1 Apr 2026 04:31:17 +0200 Subject: [PATCH 2/2] fix: lint and formatting for provider catalog enhancements - Increase line-length to 120 (modern standard) - Fix import sorting and unnecessary mode arguments - Apply ruff formatting across codebase - Remove temporary helper scripts Refs: #184 --- faigate/adaptation.py | 8 +- faigate/api/anthropic/models.py | 4 +- faigate/bridges/anthropic/adapter.py | 35 +-- faigate/cli.py | 5 +- faigate/config.py | 192 ++++--------- faigate/dashboard.py | 157 +++-------- faigate/hooks.py | 23 +- faigate/lane_registry.py | 9 +- faigate/main.py | 167 +++--------- faigate/onboarding.py | 161 +++-------- faigate/provider_catalog.py | 128 +++++---- faigate/provider_catalog_refresh.py | 49 +--- faigate/provider_catalog_store.py | 5 +- faigate/providers.py | 8 +- faigate/registry.py | 11 +- faigate/router.py | 113 ++------ faigate/updates.py | 24 +- faigate/wizard.py | 329 ++++++----------------- hooks/adapters/grok_api_adapter.py | 6 +- hooks/community/claude_code_router.py | 14 +- hooks/grok-wrapper.py | 3 +- pyproject.toml | 2 +- tests/test_api_hardening.py | 23 +- tests/test_config.py | 8 +- tests/test_dashboard_provider_catalog.py | 4 +- tests/test_menu_helpers.py | 107 ++------ tests/test_onboarding.py | 28 +- tests/test_provider_catalog.py | 12 +- tests/test_provider_catalog_api.py | 7 +- tests/test_request_hooks.py | 21 +- tests/test_route_introspection.py | 16 +- tests/test_updates.py | 4 +- tests/test_wizard.py | 18 +- 33 files changed, 443 insertions(+), 1258 deletions(-) diff --git a/faigate/adaptation.py b/faigate/adaptation.py index ad49080..1f7e605 100644 --- a/faigate/adaptation.py +++ b/faigate/adaptation.py @@ -206,16 +206,12 @@ def to_dict(self) -> dict[str, Any]: "cooldown_window_s": cooldown_window_s, "cooldown_remaining_s": cooldown_remaining_s, "cooldown_until": ( - round(self.last_issue_at + cooldown_window_s, 3) - if cooldown_window_s and self.last_issue_at - else 0.0 + round(self.last_issue_at + cooldown_window_s, 3) if cooldown_window_s and self.last_issue_at else 0.0 ), "degraded_window_s": degraded_window_s, "degraded_remaining_s": degraded_remaining_s, "degraded_until": ( - round(self.last_issue_at + degraded_window_s, 3) - if degraded_window_s and self.last_issue_at - else 0.0 + round(self.last_issue_at + degraded_window_s, 3) if degraded_window_s and self.last_issue_at else 0.0 ), "window_state": self.window_state(), "request_blocked": self.cooldown_active(), diff --git a/faigate/api/anthropic/models.py b/faigate/api/anthropic/models.py index da5dc1c..2fa346a 100644 --- a/faigate/api/anthropic/models.py +++ b/faigate/api/anthropic/models.py @@ -154,9 +154,7 @@ def _parse_system_prompt(raw: Any) -> str | list[str] | None: if isinstance(raw, str): return raw if not isinstance(raw, list): - raise AnthropicBridgeError( - "'system' must be a string, a list of strings, a list of text blocks, or null" - ) + raise AnthropicBridgeError("'system' must be a string, a list of strings, a list of text blocks, or null") normalized: list[str] = [] for item in raw: diff --git a/faigate/bridges/anthropic/adapter.py b/faigate/bridges/anthropic/adapter.py index 1129959..75928ac 100644 --- a/faigate/bridges/anthropic/adapter.py +++ b/faigate/bridges/anthropic/adapter.py @@ -53,11 +53,7 @@ def anthropic_request_to_canonical( """Map an Anthropic messages request to the internal gateway model.""" normalized_headers = {str(key): str(value) for key, value in (headers or {}).items()} - source = ( - normalized_headers.get("x-faigate-client") - or normalized_headers.get("anthropic-client") - or "claude-code" - ) + source = normalized_headers.get("x-faigate-client") or normalized_headers.get("anthropic-client") or "claude-code" client = source metadata = dict(request.metadata) metadata.setdefault("source", source) @@ -204,9 +200,7 @@ def approximate_anthropic_input_tokens( total += 12 total += _estimate_text_tokens(tool.name) total += _estimate_text_tokens(tool.description) - total += _estimate_text_tokens( - json.dumps(tool.input_schema, sort_keys=True, separators=(",", ":")) - ) + total += _estimate_text_tokens(json.dumps(tool.input_schema, sort_keys=True, separators=(",", ":"))) return max(total, 1), "estimated-char-v1" @@ -264,9 +258,7 @@ def _user_message_to_canonical(message: AnthropicMessage) -> list[CanonicalMessa pending_text.append(block) continue if block.type != "tool_result": - raise AnthropicBridgeError( - "Anthropic bridge v1 supports only text and tool_result blocks in user messages" - ) + raise AnthropicBridgeError("Anthropic bridge v1 supports only text and tool_result blocks in user messages") if not block.tool_use_id: # Claude-native clients can emit tool_result-like user blocks without a # stable tool_use_id. Falling back to user text keeps the session @@ -289,9 +281,7 @@ def _user_message_to_canonical(message: AnthropicMessage) -> list[CanonicalMessa # OpenAI-style tool continuity requires tool messages to follow the # assistant tool_calls immediately. Preserve any surrounding user text # as a trailing user turn once all tool_result blocks are emitted. - canonical_messages.append( - CanonicalMessage(role="user", content=_text_blocks_to_string(pending_text)) - ) + canonical_messages.append(CanonicalMessage(role="user", content=_text_blocks_to_string(pending_text))) return canonical_messages @@ -402,11 +392,7 @@ def _canonical_content_to_anthropic_blocks( content = message.content blocks: list[AnthropicContentBlock] if isinstance(content, str): - blocks = ( - [] - if (not content and message.tool_calls) - else [AnthropicContentBlock(type="text", text=content)] - ) + blocks = [] if (not content and message.tool_calls) else [AnthropicContentBlock(type="text", text=content)] elif isinstance(content, list): blocks = [] for item in content: @@ -457,9 +443,7 @@ def _canonical_content_to_anthropic_blocks( return blocks -def map_stop_reason_to_anthropic( - stop_reason: str | None, *, has_tool_calls: bool = False -) -> str | None: +def map_stop_reason_to_anthropic(stop_reason: str | None, *, has_tool_calls: bool = False) -> str | None: """Translate OpenAI-style finish reasons into Anthropic stop reasons.""" normalized = str(stop_reason or "").strip().lower() @@ -567,8 +551,7 @@ async def openai_sse_to_anthropic( "error", { "type": "error", - "error": payload.get("error") - or {"type": "api_error", "message": "Upstream error"}, + "error": payload.get("error") or {"type": "api_error", "message": "Upstream error"}, }, ) return @@ -639,9 +622,7 @@ async def openai_sse_to_anthropic( if not isinstance(tool_delta, dict): continue raw_index = int(tool_delta.get("index") or 0) - state = tool_states.setdefault( - raw_index, _AnthropicStreamToolState(index=raw_index) - ) + state = tool_states.setdefault(raw_index, _AnthropicStreamToolState(index=raw_index)) function = tool_delta.get("function") or {} if tool_delta.get("id"): state.tool_use_id = str(tool_delta["id"]) diff --git a/faigate/cli.py b/faigate/cli.py index 940992f..a5d451e 100644 --- a/faigate/cli.py +++ b/faigate/cli.py @@ -83,10 +83,7 @@ def _bar(ratio: float, width: int = 20, char: str = "█") -> str: def _table(headers: list[str], rows: list[list[str]], col_widths: list[int] | None = None): """Print a simple aligned table.""" if not col_widths: - col_widths = [ - max(len(h), max((len(str(r[i])) for r in rows), default=0)) + 2 - for i, h in enumerate(headers) - ] + col_widths = [max(len(h), max((len(str(r[i])) for r in rows), default=0)) + 2 for i, h in enumerate(headers)] # Header hdr = "" diff --git a/faigate/config.py b/faigate/config.py index 2cb129e..b68a468 100644 --- a/faigate/config.py +++ b/faigate/config.py @@ -260,21 +260,13 @@ def _validate_provider_base_url(name: str, base_url: str) -> str: parsed = urlparse(base_url) scheme = (parsed.scheme or "").strip().lower() if scheme not in {"http", "https"}: - raise ConfigError( - "Provider " - f"'{name}' base_url must use http or https " - f"(got '{parsed.scheme or 'missing'}')" - ) + raise ConfigError(f"Provider '{name}' base_url must use http or https (got '{parsed.scheme or 'missing'}')") if not parsed.netloc: raise ConfigError(f"Provider '{name}' base_url must include a host") if scheme == "http" and not _looks_local_base_url(base_url): - raise ConfigError( - "Provider " - f"'{name}' base_url must use https unless it points " - "to local/private network space" - ) + raise ConfigError(f"Provider '{name}' base_url must use https unless it points to local/private network space") return base_url @@ -350,9 +342,7 @@ def _normalize_positive_int(value: Any, *, field_name: str, provider_name: str) if value in (None, ""): return None if isinstance(value, bool) or not isinstance(value, int) or value <= 0: - raise ConfigError( - f"Provider '{provider_name}' field '{field_name}' must be a positive integer" - ) + raise ConfigError(f"Provider '{provider_name}' field '{field_name}' must be a positive integer") return value @@ -361,9 +351,7 @@ def _normalize_nonneg_int(value: Any, *, field_name: str, provider_name: str) -> if value in (None, ""): return None if isinstance(value, bool) or not isinstance(value, int) or value < 0: - raise ConfigError( - f"Provider '{provider_name}' field '{field_name}' must be a non-negative integer" - ) + raise ConfigError(f"Provider '{provider_name}' field '{field_name}' must be a non-negative integer") return value @@ -404,8 +392,7 @@ def _normalize_provider_cache(name: str, cfg: dict[str, Any]) -> dict[str, Any]: if mode not in _SUPPORTED_CACHE_MODES: supported = ", ".join(sorted(_SUPPORTED_CACHE_MODES)) raise ConfigError( - f"Provider '{name}' field 'cache.mode' uses unsupported value '{mode}'" - f" (supported: {supported})" + f"Provider '{name}' field 'cache.mode' uses unsupported value '{mode}' (supported: {supported})" ) read_discount = raw.get("read_discount") @@ -443,13 +430,9 @@ def _normalize_provider_cache(name: str, cfg: dict[str, Any]) -> dict[str, Any]: try: cache_read_discount = float(cache_read_discount_raw) except (TypeError, ValueError): - raise ConfigError( - f"Provider '{name}' field 'cache.cache_read_discount' must be a float" - ) + raise ConfigError(f"Provider '{name}' field 'cache.cache_read_discount' must be a float") if not (0.0 <= cache_read_discount <= 1.0): - raise ConfigError( - f"Provider '{name}' field 'cache.cache_read_discount' must be between 0.0 and 1.0" - ) + raise ConfigError(f"Provider '{name}' field 'cache.cache_read_discount' must be between 0.0 and 1.0") else: # Derive from pricing if available input_price = float(pricing.get("input", 0) or 0) @@ -464,13 +447,9 @@ def _normalize_provider_cache(name: str, cfg: dict[str, Any]) -> dict[str, Any]: try: cache_write_surcharge = float(cache_write_surcharge_raw) except (TypeError, ValueError): - raise ConfigError( - f"Provider '{name}' field 'cache.cache_write_surcharge' must be a float" - ) + raise ConfigError(f"Provider '{name}' field 'cache.cache_write_surcharge' must be a float") if cache_write_surcharge < 1.0: - raise ConfigError( - f"Provider '{name}' field 'cache.cache_write_surcharge' must be >= 1.0" - ) + raise ConfigError(f"Provider '{name}' field 'cache.cache_write_surcharge' must be >= 1.0") else: cache_write_surcharge = 1.0 @@ -519,9 +498,7 @@ def _normalize_provider_image(name: str, cfg: dict[str, Any]) -> dict[str, Any]: normalized_sizes = [] for value in supported_sizes: if not isinstance(value, str) or not value.strip(): - raise ConfigError( - f"Provider '{name}' field 'image.supported_sizes' must contain non-empty strings" - ) + raise ConfigError(f"Provider '{name}' field 'image.supported_sizes' must contain non-empty strings") normalized_sizes.append(value.strip()) if normalized_sizes: image["supported_sizes"] = normalized_sizes @@ -537,9 +514,7 @@ def _normalize_provider_image(name: str, cfg: dict[str, Any]) -> dict[str, Any]: normalized_tags = [] for value in policy_tags: if not isinstance(value, str) or not value.strip(): - raise ConfigError( - f"Provider '{name}' field 'image.policy_tags' must contain non-empty strings" - ) + raise ConfigError(f"Provider '{name}' field 'image.policy_tags' must contain non-empty strings") normalized_tags.append(value.strip().lower()) if normalized_tags: image["policy_tags"] = normalized_tags @@ -678,8 +653,7 @@ def _normalize_provider_transport(name: str, cfg: dict[str, Any]) -> dict[str, A if auth_mode not in _SUPPORTED_PROVIDER_TRANSPORT_AUTH_MODES: supported = ", ".join(sorted(_SUPPORTED_PROVIDER_TRANSPORT_AUTH_MODES)) raise ConfigError( - f"Provider '{name}' transport.auth_mode uses unsupported value " - f"'{auth_mode}' (supported: {supported})" + f"Provider '{name}' transport.auth_mode uses unsupported value '{auth_mode}' (supported: {supported})" ) normalized["auth_mode"] = auth_mode @@ -696,9 +670,7 @@ def _normalize_provider_transport(name: str, cfg: dict[str, Any]) -> dict[str, A ) normalized["probe_strategy"] = probe_strategy - probe_payload_kind = ( - str(transport.get("probe_payload_kind", "default") or "default").strip().lower() - ) + probe_payload_kind = str(transport.get("probe_payload_kind", "default") or "default").strip().lower() if not probe_payload_kind: raise ConfigError(f"Provider '{name}' transport.probe_payload_kind must be non-empty") normalized["probe_payload_kind"] = probe_payload_kind @@ -710,9 +682,7 @@ def _normalize_provider_transport(name: str, cfg: dict[str, Any]) -> dict[str, A probe_payload_max_tokens = transport.get("probe_payload_max_tokens", 1) if not isinstance(probe_payload_max_tokens, int) or probe_payload_max_tokens < 1: - raise ConfigError( - f"Provider '{name}' transport.probe_payload_max_tokens must be an integer >= 1" - ) + raise ConfigError(f"Provider '{name}' transport.probe_payload_max_tokens must be an integer >= 1") normalized["probe_payload_max_tokens"] = probe_payload_max_tokens for field_name in ( @@ -729,9 +699,7 @@ def _normalize_provider_transport(name: str, cfg: dict[str, Any]) -> dict[str, A raise ConfigError(f"Provider '{name}' transport.{field_name} must be a string") cleaned = value.strip() if not cleaned.startswith("/"): - raise ConfigError( - f"Provider '{name}' transport.{field_name} must start with '/' when set" - ) + raise ConfigError(f"Provider '{name}' transport.{field_name} must start with '/' when set") normalized[field_name] = cleaned for field_name in ("requires_api_key", "supports_models_probe"): @@ -762,9 +730,7 @@ def _normalize_provider_transport(name: str, cfg: dict[str, Any]) -> dict[str, A normalized["notes"].append(item.strip()) if normalized["probe_strategy"] == "models" and not normalized["models_path"]: - raise ConfigError( - f"Provider '{name}' transport.probe_strategy=models requires transport.models_path" - ) + raise ConfigError(f"Provider '{name}' transport.probe_strategy=models requires transport.models_path") return normalized @@ -778,9 +744,7 @@ def _normalize_provider(name: str, cfg: Any) -> dict[str, Any]: backend = normalized.get("backend", "openai-compat") if backend not in _SUPPORTED_BACKENDS: supported = ", ".join(sorted(_SUPPORTED_BACKENDS)) - raise ConfigError( - f"Provider '{name}' uses unsupported backend '{backend}' (supported: {supported})" - ) + raise ConfigError(f"Provider '{name}' uses unsupported backend '{backend}' (supported: {supported})") for field in ("base_url", "model"): value = normalized.get(field, "") @@ -802,20 +766,14 @@ def _normalize_provider(name: str, cfg: Any) -> dict[str, Any]: contract = contract.strip() if contract not in _SUPPORTED_PROVIDER_CONTRACTS: supported = ", ".join(sorted(_SUPPORTED_PROVIDER_CONTRACTS)) - raise ConfigError( - f"Provider '{name}' uses unsupported contract '{contract}' (supported: {supported})" - ) + raise ConfigError(f"Provider '{name}' uses unsupported contract '{contract}' (supported: {supported})") normalized["contract"] = contract if contract == "local-worker": if backend != "openai-compat": - raise ConfigError( - f"Provider '{name}' contract 'local-worker' requires backend 'openai-compat'" - ) + raise ConfigError(f"Provider '{name}' contract 'local-worker' requires backend 'openai-compat'") if not _looks_local_base_url(str(normalized.get("base_url", ""))): - raise ConfigError( - f"Provider '{name}' contract 'local-worker' requires a local/private base_url" - ) + raise ConfigError(f"Provider '{name}' contract 'local-worker' requires a local/private base_url") normalized.setdefault("tier", "local") raw_capabilities = normalized.get("capabilities") @@ -831,9 +789,7 @@ def _normalize_provider(name: str, cfg: Any) -> dict[str, Any]: } elif contract == "image-provider": if backend != "openai-compat": - raise ConfigError( - f"Provider '{name}' contract 'image-provider' requires backend 'openai-compat'" - ) + raise ConfigError(f"Provider '{name}' contract 'image-provider' requires backend 'openai-compat'") raw_capabilities = normalized.get("capabilities") if raw_capabilities is None: raw_capabilities = {} @@ -870,15 +826,11 @@ def _normalize_providers(data: dict[str, Any]) -> dict[str, Any]: raise ConfigError("'providers' must be a mapping") normalized = dict(data) - normalized["providers"] = { - name: _normalize_provider(name, cfg) for name, cfg in providers.items() - } + normalized["providers"] = {name: _normalize_provider(name, cfg) for name, cfg in providers.items()} return normalized -def _normalize_string_list( - value: Any, *, field_name: str, rule_name: str, allow_empty: bool = False -) -> list[str]: +def _normalize_string_list(value: Any, *, field_name: str, rule_name: str, allow_empty: bool = False) -> list[str]: """Normalize a config field to a list of non-empty strings.""" if value is None: return [] @@ -892,9 +844,7 @@ def _normalize_string_list( normalized = [] for item in items: if not isinstance(item, str) or not item.strip(): - raise ConfigError( - f"Policy '{rule_name}' field '{field_name}' must contain non-empty strings" - ) + raise ConfigError(f"Policy '{rule_name}' field '{field_name}' must contain non-empty strings") normalized.append(item.strip()) if not allow_empty and not normalized: @@ -940,10 +890,7 @@ def _normalize_provider_reference_list( unknown = sorted({item for item in normalized if item not in provider_names}) if unknown: unknown_list = ", ".join(unknown) - raise ConfigError( - "Policy " - f"'{rule_name}' field '{field_name}' references unknown providers: {unknown_list}" - ) + raise ConfigError(f"Policy '{rule_name}' field '{field_name}' references unknown providers: {unknown_list}") return normalized @@ -996,9 +943,7 @@ def _normalize_policy_select( unknown_caps = sorted(cap for cap in required_caps if cap not in _ALL_CAPABILITY_FIELDS) if unknown_caps: unknown_list = ", ".join(unknown_caps) - raise ConfigError( - f"Policy '{name}' require_capabilities has unknown capability names: {unknown_list}" - ) + raise ConfigError(f"Policy '{name}' require_capabilities has unknown capability names: {unknown_list}") normalized["require_capabilities"] = required_caps cap_values = normalized.get("capability_values", {}) @@ -1010,25 +955,17 @@ def _normalize_policy_select( normalized_cap_values: dict[str, list[Any]] = {} for cap_name, raw_values in cap_values.items(): if cap_name not in _ALL_CAPABILITY_FIELDS: - raise ConfigError( - f"Policy '{name}' capability_values references unknown capability '{cap_name}'" - ) + raise ConfigError(f"Policy '{name}' capability_values references unknown capability '{cap_name}'") values = raw_values if isinstance(raw_values, list) else [raw_values] if not values: - raise ConfigError( - f"Policy '{name}' capability_values '{cap_name}' must not be an empty list" - ) + raise ConfigError(f"Policy '{name}' capability_values '{cap_name}' must not be an empty list") normalized_values = [] for value in values: if cap_name in _BOOL_CAPABILITY_FIELDS and not isinstance(value, bool): - raise ConfigError( - f"Policy '{name}' capability_values '{cap_name}' must use boolean values" - ) + raise ConfigError(f"Policy '{name}' capability_values '{cap_name}' must use boolean values") if cap_name in _STRING_CAPABILITY_FIELDS: if not isinstance(value, str) or not value.strip(): - raise ConfigError( - f"Policy '{name}' capability_values '{cap_name}' must use non-empty strings" - ) + raise ConfigError(f"Policy '{name}' capability_values '{cap_name}' must use non-empty strings") value = value.strip() normalized_values.append(value) normalized_cap_values[cap_name] = normalized_values @@ -1038,9 +975,7 @@ def _normalize_policy_select( overlap = sorted(set(normalized["allow_providers"]) & set(normalized["deny_providers"])) if overlap: overlap_list = ", ".join(overlap) - raise ConfigError( - f"Policy '{name}' cannot allow and deny the same provider(s): {overlap_list}" - ) + raise ConfigError(f"Policy '{name}' cannot allow and deny the same provider(s): {overlap_list}") if extra_keys and "routing_mode" in extra_keys: routing_mode = normalized.get("routing_mode", "") @@ -1110,15 +1045,12 @@ def _normalize_client_profile_match(name: str, match: Any) -> dict[str, Any]: if "header_contains" in match: if not isinstance(match["header_contains"], dict): - raise ConfigError( - f"Client profile rule '{name}' field 'header_contains' must be a mapping" - ) + raise ConfigError(f"Client profile rule '{name}' field 'header_contains' must be a mapping") normalized_header_contains = {} for header_name, values in match["header_contains"].items(): if not isinstance(header_name, str) or not header_name.strip(): raise ConfigError( - f"Client profile rule '{name}' field 'header_contains' " - "must use non-empty header names" + f"Client profile rule '{name}' field 'header_contains' must use non-empty header names" ) normalized_header_contains[header_name.strip().lower()] = _normalize_string_list( values, @@ -1132,9 +1064,7 @@ def _normalize_client_profile_match(name: str, match: Any) -> dict[str, Any]: if compound in match: values = match[compound] if not isinstance(values, list) or not values: - raise ConfigError( - f"Client profile rule '{name}' field '{compound}' must be a non-empty list" - ) + raise ConfigError(f"Client profile rule '{name}' field '{compound}' must be a non-empty list") match[compound] = [_normalize_client_profile_match(name, item) for item in values] return match @@ -1203,9 +1133,7 @@ def _normalize_client_profiles(data: dict[str, Any]) -> dict[str, Any]: if default_profile not in normalized_profiles: normalized_profiles.setdefault( default_profile, - _normalize_policy_select( - f"client profile '{default_profile}'", {}, data.get("providers", {}) - ), + _normalize_policy_select(f"client profile '{default_profile}'", {}, data.get("providers", {})), ) rules = raw.get("rules", []) @@ -1234,9 +1162,7 @@ def _normalize_client_profiles(data: dict[str, Any]) -> dict[str, Any]: raise ConfigError(f"Client profile rule #{idx} must define a non-empty 'profile'") profile_name = profile_name.strip() if profile_name not in normalized_profiles: - raise ConfigError( - f"Client profile rule #{idx} references unknown profile '{profile_name}'" - ) + raise ConfigError(f"Client profile rule #{idx} references unknown profile '{profile_name}'") if profile_name in seen_rule_profiles: normalized_rules = [r for r in normalized_rules if r["profile"] != profile_name] normalized_rules.append( @@ -1306,9 +1232,7 @@ def _normalize_routing_modes(data: dict[str, Any]) -> dict[str, Any]: for alias in [normalized_name, *aliases]: owner = seen_aliases.get(alias) if owner and owner != normalized_name: - raise ConfigError( - f"Routing mode alias '{alias}' is already used by routing mode '{owner}'" - ) + raise ConfigError(f"Routing mode alias '{alias}' is already used by routing mode '{owner}'") seen_aliases[alias] = normalized_name normalized_modes[normalized_name] = { @@ -1369,23 +1293,16 @@ def _normalize_model_shortcuts(data: dict[str, Any]) -> dict[str, Any]: unknown = sorted(set(spec) - _SUPPORTED_MODEL_SHORTCUT_KEYS) if unknown: unknown_list = ", ".join(unknown) - raise ConfigError( - f"Model shortcut '{normalized_name}' has unknown keys: {unknown_list}" - ) + raise ConfigError(f"Model shortcut '{normalized_name}' has unknown keys: {unknown_list}") target = spec.get("target", "") if not isinstance(target, str) or not target.strip(): raise ConfigError(f"Model shortcut '{normalized_name}' must define a non-empty target") target = _resolve_provider_reference(target.strip(), provider_names) if target not in provider_names: - raise ConfigError( - f"Model shortcut '{normalized_name}' references unknown provider '{target}'" - ) + raise ConfigError(f"Model shortcut '{normalized_name}' references unknown provider '{target}'") if normalized_name in mode_names: - raise ConfigError( - "Model shortcut " - f"'{normalized_name}' conflicts with routing mode '{normalized_name}'" - ) + raise ConfigError(f"Model shortcut '{normalized_name}' conflicts with routing mode '{normalized_name}'") aliases = _normalize_string_list( spec.get("aliases", []), @@ -1396,13 +1313,9 @@ def _normalize_model_shortcuts(data: dict[str, Any]) -> dict[str, Any]: for alias in [normalized_name, *aliases]: owner = seen_aliases.get(alias) if owner and owner != normalized_name: - raise ConfigError( - f"Model shortcut alias '{alias}' is already used by model shortcut '{owner}'" - ) + raise ConfigError(f"Model shortcut alias '{alias}' is already used by model shortcut '{owner}'") if alias in mode_names: - raise ConfigError( - f"Model shortcut alias '{alias}' conflicts with routing mode '{alias}'" - ) + raise ConfigError(f"Model shortcut alias '{alias}' conflicts with routing mode '{alias}'") seen_aliases[alias] = normalized_name normalized_shortcuts[normalized_name] = { @@ -1425,9 +1338,7 @@ def _validate_routing_mode_references(data: dict[str, Any]) -> dict[str, Any]: for profile_name, hints in (data.get("client_profiles") or {}).get("profiles", {}).items(): routing_mode = str((hints or {}).get("routing_mode", "") or "").strip() if routing_mode and routing_mode not in mode_names: - raise ConfigError( - f"Client profile '{profile_name}' references unknown routing_mode '{routing_mode}'" - ) + raise ConfigError(f"Client profile '{profile_name}' references unknown routing_mode '{routing_mode}'") return data @@ -1607,8 +1518,7 @@ def _normalize_auto_update(data: dict[str, Any]) -> dict[str, Any]: overlap = sorted(set(allow_providers) & set(deny_providers)) if overlap: raise ConfigError( - "'auto_update.provider_scope' cannot allow and deny the same providers: " - + ", ".join(overlap) + "'auto_update.provider_scope' cannot allow and deny the same providers: " + ", ".join(overlap) ) verification = raw.get("verification", {}) @@ -1626,9 +1536,7 @@ def _normalize_auto_update(data: dict[str, Any]) -> dict[str, Any]: raise ConfigError("'auto_update.verification.command' must be a non-empty string") verification_timeout_seconds = verification.get("timeout_seconds", 30) - if isinstance(verification_timeout_seconds, bool) or not isinstance( - verification_timeout_seconds, int - ): + if isinstance(verification_timeout_seconds, bool) or not isinstance(verification_timeout_seconds, int): raise ConfigError("'auto_update.verification.timeout_seconds' must be an integer") if verification_timeout_seconds <= 0: raise ConfigError("'auto_update.verification.timeout_seconds' must be positive") @@ -1660,8 +1568,7 @@ def _normalize_auto_update(data: dict[str, Any]) -> dict[str, Any]: unknown_days = sorted(set(days) - _SUPPORTED_WINDOW_DAYS) if unknown_days: raise ConfigError( - "'auto_update.maintenance_window.days' has unknown weekday values: " - + ", ".join(unknown_days) + "'auto_update.maintenance_window.days' has unknown weekday values: " + ", ".join(unknown_days) ) start_hour = maintenance_window.get("start_hour", 0) @@ -1793,9 +1700,7 @@ def _normalize_provider_source_refresh(data: dict[str, Any]) -> dict[str, Any]: providers = raw.get("providers", ["blackbox", "kilo", "openai"]) if providers in (None, ""): providers = ["blackbox", "kilo", "openai"] - if not isinstance(providers, list) or any( - not isinstance(item, str) or not item.strip() for item in providers - ): + if not isinstance(providers, list) or any(not isinstance(item, str) or not item.strip() for item in providers): raise ConfigError("'provider_source_refresh.providers' must be a list of names") normalized = dict(data) @@ -1877,8 +1782,7 @@ def _normalize_anthropic_bridge(data: dict[str, Any]) -> dict[str, Any]: target = _resolve_provider_reference(target, provider_names) if target not in valid_targets: raise ConfigError( - "'anthropic_bridge.model_aliases' references unknown target " - f"'{target}' for alias '{alias}'" + f"'anthropic_bridge.model_aliases' references unknown target '{target}' for alias '{alias}'" ) normalized_aliases[alias] = target diff --git a/faigate/dashboard.py b/faigate/dashboard.py index ed75abc..e96a19f 100644 --- a/faigate/dashboard.py +++ b/faigate/dashboard.py @@ -112,9 +112,7 @@ def _client_highlights(client_totals: list[dict[str, Any]]) -> dict[str, dict[st rows, key=lambda row: (_safe_int(row.get("total_tokens")), _safe_int(row.get("requests"))), ), - "top_cost": max( - rows, key=lambda row: (_safe_float(row.get("cost_usd")), _safe_int(row.get("requests"))) - ), + "top_cost": max(rows, key=lambda row: (_safe_float(row.get("cost_usd")), _safe_int(row.get("requests")))), "highest_failure_rate": ( max( failure_rows, @@ -216,9 +214,7 @@ def _lane_family_summary( provider_map: dict[str, dict[str, Any]], ) -> list[dict[str, Any]]: metric_rows_by_provider = { - str(row.get("provider") or ""): row - for row in provider_rows - if str(row.get("provider") or "") + str(row.get("provider") or ""): row for row in provider_rows if str(row.get("provider") or "") } source_rows: list[dict[str, Any]] = [] if provider_map: @@ -231,9 +227,7 @@ def _lane_family_summary( "cost_usd": _safe_float(metrics_row.get("cost_usd")), "lane": dict((inventory_row or {}).get("lane") or {}), "request_readiness": dict((inventory_row or {}).get("request_readiness") or {}), - "route_runtime_state": dict( - (inventory_row or {}).get("route_runtime_state") or {} - ), + "route_runtime_state": dict((inventory_row or {}).get("route_runtime_state") or {}), } ) else: @@ -391,9 +385,7 @@ def _enrich_provider_rows_with_lane( "lane_cluster": str(lane.get("cluster") or ""), "benchmark_cluster": str(lane.get("benchmark_cluster") or ""), "cost_tier": str( - ((provider_inventory.get("capabilities") or {}).get("cost_tier")) - or lane.get("quality_tier") - or "" + ((provider_inventory.get("capabilities") or {}).get("cost_tier")) or lane.get("quality_tier") or "" ), "freshness_status": str(lane.get("freshness_status") or ""), "review_age_days": int(lane.get("review_age_days") or -1), @@ -403,9 +395,7 @@ def _enrich_provider_rows_with_lane( "route_runtime_state": dict(provider_inventory.get("route_runtime_state") or {}), "route_add_recommendations": add_recommendations, "recommended_add_provider": ( - str(add_recommendations[0].get("provider_name") or "") - if add_recommendations - else "" + str(add_recommendations[0].get("provider_name") or "") if add_recommendations else "" ), "recommended_add_strategy": ( str(add_recommendations[0].get("strategy") or "") if add_recommendations else "" @@ -479,10 +469,7 @@ def _render_refresh_guidance_block(report: dict[str, Any], *, limit: int = 3) -> freshness_status = str(item.get("freshness_status") or "unknown") review_age_days = int(item.get("review_age_days") or -1) age_suffix = f", {review_age_days}d" if review_age_days >= 0 else "" - line = ( - f"- {provider}: {item.get('action_label') or item.get('action')}" - f" ({freshness_status}{age_suffix})" - ) + line = f"- {provider}: {item.get('action_label') or item.get('action')} ({freshness_status}{age_suffix})" if item.get("refresh_url"): line += f" -> {item['refresh_url']}" lines.append(line) @@ -509,9 +496,7 @@ def _render_provider_catalog_block(report: dict[str, Any], *, limit: int = 3) -> f"pricing={_safe_int(item.get('pricing_count'))}" ) for alert in list(summary.get("alerts") or [])[:limit]: - lines.append( - f"- [{alert.get('severity')}] {alert.get('provider_id')}: {alert.get('headline')}" - ) + lines.append(f"- [{alert.get('severity')}] {alert.get('provider_id')}: {alert.get('headline')}") alert_summary = dict(summary.get("alert_summary") or {}) if alert_summary: lines.append( @@ -631,9 +616,7 @@ def build_dashboard_report( totals = stats.get("totals") or {} inventory_provider_map = _inventory_provider_map(inventory_payload) - providers = _enrich_provider_rows_with_lane( - stats.get("providers") or [], inventory_provider_map - ) + providers = _enrich_provider_rows_with_lane(stats.get("providers") or [], inventory_provider_map) route_additions = _route_add_summary(providers) lane_families = _lane_family_summary_from_stats(stats.get("lane_families") or []) if not lane_families: @@ -669,18 +652,14 @@ def build_dashboard_report( total_requests = _safe_int(totals.get("total_requests")) total_failures = _safe_int(totals.get("total_failures")) - success_pct = ( - ((total_requests - total_failures) * 100.0 / total_requests) if total_requests else 100.0 - ) + success_pct = ((total_requests - total_failures) * 100.0 / total_requests) if total_requests else 100.0 total_prompt_tokens = _safe_int(totals.get("total_prompt_tokens")) total_completion_tokens = _safe_int(totals.get("total_compl_tokens")) total_cost = _safe_float(totals.get("total_cost_usd")) avg_latency_ms = _safe_float(totals.get("avg_latency_ms")) fallback_requests = sum( - _safe_int(item.get("requests")) - for item in routing - if str(item.get("layer") or "") == "fallback" + _safe_int(item.get("requests")) for item in routing if str(item.get("layer") or "") == "fallback" ) fallback_pct = (fallback_requests * 100.0 / total_requests) if total_requests else 0.0 @@ -724,8 +703,7 @@ def build_dashboard_report( { "provider": provider_name, "category": _health_issue_category(str(payload.get("last_error") or "")), - "detail": str(payload.get("last_error") or "").strip() - or "No error detail provided", + "detail": str(payload.get("last_error") or "").strip() or "No error detail provided", } ) @@ -829,9 +807,7 @@ def build_dashboard_report( if top_provider_cost and total_cost > 0: dominant_cost_share = ( - (_safe_float(top_provider_cost.get("cost_usd")) * 100.0 / total_cost) - if total_cost - else 0.0 + (_safe_float(top_provider_cost.get("cost_usd")) * 100.0 / total_cost) if total_cost else 0.0 ) if dominant_cost_share >= 60.0: hints.append( @@ -840,8 +816,7 @@ def build_dashboard_report( cheaper_healthy = [ name for name, tier in healthy_provider_tiers.items() - if tier in {"cheap", "default", "fallback"} - and name != str(top_provider_cost.get("provider") or "") + if tier in {"cheap", "default", "fallback"} and name != str(top_provider_cost.get("provider") or "") ] if cheaper_healthy: decision_support.append( @@ -852,18 +827,10 @@ def build_dashboard_report( top_lane = str(top_provider_cost.get("canonical_model") or "") top_route = str(top_provider_cost.get("route_type") or "") if top_lane: - hints.append( - f"Top-cost lane right now: {top_lane}" - + (f" via {top_route}" if top_route else "") - + "." - ) + hints.append(f"Top-cost lane right now: {top_lane}" + (f" via {top_route}" if top_route else "") + ".") - rate_limited = [ - item["provider"] for item in unhealthy_providers if item["category"] == "rate-limited" - ] - quota_exhausted = [ - item["provider"] for item in unhealthy_providers if item["category"] == "quota-exhausted" - ] + rate_limited = [item["provider"] for item in unhealthy_providers if item["category"] == "rate-limited"] + quota_exhausted = [item["provider"] for item in unhealthy_providers if item["category"] == "quota-exhausted"] if rate_limited: decision_support.append( "Rate-limit pressure: " @@ -880,11 +847,7 @@ def build_dashboard_report( if healthy_provider_names: hints.append( f"Healthy providers right now: {', '.join(healthy_provider_names[:4])}" - + ( - "" - if len(healthy_provider_names) <= 4 - else f" +{len(healthy_provider_names) - 4} more" - ) + + ("" if len(healthy_provider_names) <= 4 else f" +{len(healthy_provider_names) - 4} more") ) elif health_payload: hints.append("No healthy providers are currently reported by /health.") @@ -932,9 +895,7 @@ def build_dashboard_report( f"{freshness['stale']} route assumption(s) are stale. Review benchmark or pricing guidance before leaning too hard on those lanes." ) elif freshness.get("aging"): - hints.append( - f"{freshness['aging']} route assumption(s) are aging and worth rechecking soon." - ) + hints.append(f"{freshness['aging']} route assumption(s) are aging and worth rechecking soon.") if refresh_guidance: top_refresh = refresh_guidance[0] decision_support.append( @@ -947,11 +908,8 @@ def build_dashboard_report( { "level": str(catalog_alert.get("severity") or "notice"), "headline": str(catalog_alert.get("headline") or "Provider catalog alert"), - "detail": str(catalog_alert.get("detail") or "").strip() - or "Provider source catalog requires review.", - "suggestion": str( - catalog_alert.get("suggestion") or "Review provider catalog state." - ), + "detail": str(catalog_alert.get("detail") or "").strip() or "Provider source catalog requires review.", + "suggestion": str(catalog_alert.get("suggestion") or "Review provider catalog state."), } ) if not provider_catalog_alerts and _safe_int(provider_catalog.get("due_sources")) > 0: @@ -965,8 +923,7 @@ def build_dashboard_report( if provider_catalog_alerts: top_catalog_alert = provider_catalog_alerts[0] decision_support.append( - f"Catalog alert: {top_catalog_alert.get('headline')} " - f"Follow up via {top_catalog_alert.get('suggestion')}" + f"Catalog alert: {top_catalog_alert.get('headline')} Follow up via {top_catalog_alert.get('suggestion')}" ) if provider_catalog_alert_summary.get("status") == "intervention-needed": hints.append( @@ -1030,9 +987,7 @@ def build_dashboard_report( "success_pct": round(success_pct, 1), "avg_latency_ms": round(avg_latency_ms, 1), "last_request_ago": _format_ago(float(last_request)) if last_request else "never", - "first_request_ago": _format_ago(float(first_request)) - if first_request - else "never", + "first_request_ago": _format_ago(float(first_request)) if first_request else "never", }, "spend": { "total_cost_usd": round(total_cost, 6), @@ -1041,21 +996,14 @@ def build_dashboard_report( "completion_tokens": total_completion_tokens, }, "health": { - "status": (health_payload or {}).get("status") - or ("live" if health_payload else "unknown"), - "providers_healthy": _safe_int( - ((health_payload or {}).get("summary") or {}).get("providers_healthy") - ), - "providers_total": _safe_int( - ((health_payload or {}).get("summary") or {}).get("providers_total") - ), + "status": (health_payload or {}).get("status") or ("live" if health_payload else "unknown"), + "providers_healthy": _safe_int(((health_payload or {}).get("summary") or {}).get("providers_healthy")), + "providers_total": _safe_int(((health_payload or {}).get("summary") or {}).get("providers_total")), "providers_request_ready": _safe_int( ((health_payload or {}).get("request_readiness") or {}).get("providers_ready") ), "providers_request_not_ready": _safe_int( - ((health_payload or {}).get("request_readiness") or {}).get( - "providers_not_ready" - ) + ((health_payload or {}).get("request_readiness") or {}).get("providers_not_ready") ), "providers_request_ready_compat": readiness_breakdown.get("compat", 0), "unhealthy": unhealthy_providers, @@ -1166,9 +1114,7 @@ def _render_overview(report: dict[str, Any]) -> str: route_additions = report.get("route_additions") or [] if route_additions: top_addition = route_additions[0] - lines.append( - f" Next add {top_addition.get('add_provider')} ({top_addition.get('strategy')})" - ) + lines.append(f" Next add {top_addition.get('add_provider')} ({top_addition.get('strategy')})") lines.extend( [ "", @@ -1438,10 +1384,7 @@ def _render_provider_detail(report: dict[str, Any], provider_name: str) -> str: None, ) if not row: - return ( - "fusionAIze Gate Dashboard\n\n" - f"Provider detail\n\nNo provider row found for '{provider_name}'.\n" - ) + return f"fusionAIze Gate Dashboard\n\nProvider detail\n\nNo provider row found for '{provider_name}'.\n" provider = str(row.get("provider") or provider_name) status = "live-healthy" @@ -1454,11 +1397,7 @@ def _render_provider_detail(report: dict[str, Any], provider_name: str) -> str: request_readiness = row.get("request_readiness") or {} transport = row.get("transport") or {} provider_routing_paths = _routing_path_summary( - [ - item - for item in report.get("routing") or [] - if str(item.get("provider") or "").strip().lower() == target - ] + [item for item in report.get("routing") or [] if str(item.get("provider") or "").strip().lower() == target] ) lines = [ "fusionAIze Gate Dashboard", @@ -1487,15 +1426,10 @@ def _render_provider_detail(report: dict[str, Any], provider_name: str) -> str: lines.append(f"Review age {_safe_int(row.get('review_age_days'))}d") if row.get("freshness_hint"): lines.append(f"Freshness hint {row.get('freshness_hint')}") - refresh_lookup = { - str(item.get("provider") or "").lower(): item - for item in report.get("refresh_guidance") or [] - } + refresh_lookup = {str(item.get("provider") or "").lower(): item for item in report.get("refresh_guidance") or []} refresh_item = refresh_lookup.get(target) if refresh_item: - lines.append( - f"Refresh action {refresh_item.get('action_label') or refresh_item.get('action')}" - ) + lines.append(f"Refresh action {refresh_item.get('action_label') or refresh_item.get('action')}") if refresh_item.get("refresh_url"): lines.append(f"Refresh source {refresh_item.get('refresh_url')}") if refresh_item.get("reason"): @@ -1509,9 +1443,7 @@ def _render_provider_detail(report: dict[str, Any], provider_name: str) -> str: if request_readiness.get("operator_hint"): lines.append(f"Operator hint {request_readiness.get('operator_hint')}") if row.get("recommended_add_provider"): - lines.append( - f"Add route {row.get('recommended_add_provider')} ({row.get('recommended_add_strategy')})" - ) + lines.append(f"Add route {row.get('recommended_add_provider')} ({row.get('recommended_add_strategy')})") if transport: lines.extend( [ @@ -1535,20 +1467,12 @@ def _render_provider_detail(report: dict[str, Any], provider_name: str) -> str: if runtime_state.get("window_state") and runtime_state.get("window_state") != "clear": lines.append(f"Runtime window {runtime_state.get('window_state')}") if runtime_state.get("cooldown_remaining_s"): - lines.append( - f"Cooldown left {_safe_int(runtime_state.get('cooldown_remaining_s'))}s" - ) + lines.append(f"Cooldown left {_safe_int(runtime_state.get('cooldown_remaining_s'))}s") if runtime_state.get("degraded_remaining_s"): - lines.append( - f"Degraded left {_safe_int(runtime_state.get('degraded_remaining_s'))}s" - ) + lines.append(f"Degraded left {_safe_int(runtime_state.get('degraded_remaining_s'))}s") if runtime_state.get("recovered_recently"): - lines.append( - f"Recovered from {runtime_state.get('last_recovered_issue_type') or 'n/a'}" - ) - lines.append( - f"Recovery watch {_safe_int(runtime_state.get('recovery_remaining_s'))}s" - ) + lines.append(f"Recovered from {runtime_state.get('last_recovered_issue_type') or 'n/a'}") + lines.append(f"Recovery watch {_safe_int(runtime_state.get('recovery_remaining_s'))}s") if provider in unhealthy: lines.append(f"Live issue {unhealthy[provider]['detail']}") if provider_routing_paths: @@ -1580,15 +1504,10 @@ def _render_client_detail(report: dict[str, Any], client_name: str) -> str: None, ) if not row: - return ( - "fusionAIze Gate Dashboard\n\n" - f"Client detail\n\nNo client row found for '{client_name}'.\n" - ) + return f"fusionAIze Gate Dashboard\n\nClient detail\n\nNo client row found for '{client_name}'.\n" name = str(row.get("client_tag") or row.get("client_profile") or client_name) - expensive = ( - _safe_float(row.get("cost_usd")) > 0.5 or _safe_float(row.get("avg_latency_ms")) > 4000 - ) + expensive = _safe_float(row.get("cost_usd")) > 0.5 or _safe_float(row.get("avg_latency_ms")) > 4000 suggested_scenario = _recommended_scenario_for_client( str(row.get("client_profile") or name), expensive=expensive, diff --git a/faigate/hooks.py b/faigate/hooks.py index 31c7d19..e736183 100644 --- a/faigate/hooks.py +++ b/faigate/hooks.py @@ -164,12 +164,8 @@ def _sanitize_body_updates(updates: dict[str, Any]) -> tuple[dict[str, Any], lis if key in {"temperature"} and not isinstance(value, (int, float)): warnings.append(f"Ignored hook body update for '{key}' because it was not numeric") continue - if key in {"max_tokens"} and ( - isinstance(value, bool) or not isinstance(value, int) or value <= 0 - ): - warnings.append( - "Ignored hook body update for 'max_tokens' because it was not a positive integer" - ) + if key in {"max_tokens"} and (isinstance(value, bool) or not isinstance(value, int) or value <= 0): + warnings.append("Ignored hook body update for 'max_tokens' because it was not a positive integer") continue if key == "stream" and not isinstance(value, bool): warnings.append("Ignored hook body update for 'stream' because it was not a boolean") @@ -230,8 +226,7 @@ def _sanitize_routing_hints(hints: dict[str, Any]) -> tuple[dict[str, Any], list normalized_values = [ value for value in values - if isinstance(value, (str, bool)) - and (not isinstance(value, str) or value.strip()) + if isinstance(value, (str, bool)) and (not isinstance(value, str) or value.strip()) ] if normalized_values: cap_values[capability.strip()] = normalized_values @@ -245,9 +240,7 @@ def _sanitize_routing_hints(hints: dict[str, Any]) -> tuple[dict[str, Any], list if isinstance(raw_mode, str) and raw_mode.strip(): sanitized["routing_mode"] = raw_mode.strip().lower() else: - errors.append( - "ignored invalid routing_hints.routing_mode (expected a non-empty string)" - ) + errors.append("ignored invalid routing_hints.routing_mode (expected a non-empty string)") unknown = sorted(set(hints) - (_LIST_HINT_KEYS | {"capability_values", "routing_mode"})) for key in unknown: @@ -429,18 +422,14 @@ def register(register_fn, register_provider_fn) -> None: ... loaded: list[str] = [] for py_file in sorted(path.glob("*.py")): try: - spec = importlib.util.spec_from_file_location( - f"faigate_community_hook_{py_file.stem}", py_file - ) + spec = importlib.util.spec_from_file_location(f"faigate_community_hook_{py_file.stem}", py_file) if spec is None or spec.loader is None: _logger.warning("Cannot load community hook %s — invalid spec", py_file.name) continue module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # type: ignore[union-attr] if not hasattr(module, "register"): - _logger.warning( - "Community hook %s has no register() function — skipped", py_file.name - ) + _logger.warning("Community hook %s has no register() function — skipped", py_file.name) continue # Support optional second arg for virtual provider registration try: diff --git a/faigate/lane_registry.py b/faigate/lane_registry.py index cc408b3..5376b3e 100644 --- a/faigate/lane_registry.py +++ b/faigate/lane_registry.py @@ -595,10 +595,7 @@ def _lane_binding_with_freshness(binding: dict[str, Any]) -> dict[str, Any]: "supports_models_probe": False, "notes": [ "aggregator route uses a shallow chat probe instead of assuming /models support", - ( - "low-cost BLACKBOX routes can shift quickly in pricing, auth behavior, " - "or model availability" - ), + ("low-cost BLACKBOX routes can shift quickly in pricing, auth behavior, or model availability"), ], }, } @@ -890,9 +887,7 @@ def add_route_candidates(target_model: str, strategy: str) -> None: "provider_family": family, "route_type": str(binding.get("route_type") or ""), "route_group": "family-lane", - "reason": ( - f"adds another {family} family lane for recovery and routing flexibility" - ), + "reason": (f"adds another {family} family lane for recovery and routing flexibility"), } ) seen.add(provider_name) diff --git a/faigate/main.py b/faigate/main.py index 048e1b5..e6ee84e 100644 --- a/faigate/main.py +++ b/faigate/main.py @@ -354,11 +354,7 @@ async def _refresh_local_worker_probes(force: bool = False) -> None: continue interval = recovery_interval if not provider.health.healthy else check_interval - due = ( - force - or provider.health.last_check == 0 - or (time.time() - provider.health.last_check) >= interval - ) + due = force or provider.health.last_check == 0 or (time.time() - provider.health.last_check) >= interval if not due: continue @@ -383,11 +379,7 @@ async def _refresh_provider_source_catalog(*, force: bool = False) -> list[dict[ for source in list_provider_sources(provider_ids): _provider_catalog_store.upsert_source(source) - target_ids = ( - provider_ids - if force - else due_provider_ids(_provider_catalog_store, provider_ids=provider_ids) - ) + target_ids = provider_ids if force else due_provider_ids(_provider_catalog_store, provider_ids=provider_ids) if not target_ids: return [] @@ -801,9 +793,7 @@ def _provider_request_readiness(provider: Any) -> dict[str, Any]: f"{runtime_cooldown_remaining}s after recent {runtime_issue_type.replace('-', ' ')} failures" ) state["runtime_cooldown_active"] = True - state["operator_hint"] = ( - "keep this route out of primary traffic until the cooldown pressure drops" - ) + state["operator_hint"] = "keep this route out of primary traffic until the cooldown pressure drops" elif runtime_window_state == "degraded" and runtime_issue_type and bool(state.get("ready")): state["status"] = "ready-degraded" state["reason"] = ( @@ -818,9 +808,7 @@ def _provider_request_readiness(provider: Any) -> dict[str, Any]: f"{runtime_last_recovered_issue.replace('-', ' ') or 'runtime'} issues " f"({runtime_recovery_remaining}s recovery watch left)" ) - state["operator_hint"] = ( - "route can carry traffic again; keep it under observation during the recovery window" - ) + state["operator_hint"] = "route can carry traffic again; keep it under observation during the recovery window" elif runtime_penalty >= 20 and runtime_issue_type in { "quota-exhausted", "rate-limited", @@ -828,19 +816,13 @@ def _provider_request_readiness(provider: Any) -> dict[str, Any]: }: state["ready"] = False state["status"] = runtime_issue_type - state["reason"] = ( - "route is in runtime cooldown after recent " - f"{runtime_issue_type.replace('-', ' ')} failures" - ) + state["reason"] = f"route is in runtime cooldown after recent {runtime_issue_type.replace('-', ' ')} failures" state["runtime_cooldown_active"] = True - state["operator_hint"] = ( - "keep this route out of primary traffic until the cooldown pressure drops" - ) + state["operator_hint"] = "keep this route out of primary traffic until the cooldown pressure drops" elif runtime_penalty >= 12 and runtime_issue_type and bool(state.get("ready")): state["status"] = "ready-degraded" state["reason"] = ( - "route is still request-ready but operating under recent " - f"{runtime_issue_type.replace('-', ' ')} pressure" + f"route is still request-ready but operating under recent {runtime_issue_type.replace('-', ' ')} pressure" ) state["operator_hint"] = "prefer lower-pressure siblings while this route recovers" return state @@ -895,10 +877,7 @@ def _attempt_relation_details(selected_provider: str, attempted_provider: str) - selected_lane.get("canonical_model") and selected_lane.get("canonical_model") == attempted_lane.get("canonical_model") ) - same_cluster = bool( - selected_lane.get("cluster") - and selected_lane.get("cluster") == attempted_lane.get("cluster") - ) + same_cluster = bool(selected_lane.get("cluster") and selected_lane.get("cluster") == attempted_lane.get("cluster")) same_benchmark_cluster = bool( selected_lane.get("benchmark_cluster") and selected_lane.get("benchmark_cluster") == attempted_lane.get("benchmark_cluster") @@ -983,21 +962,13 @@ def _attempt_metric_fields( ) return { - "canonical_model": str( - actual_lane.get("canonical_model") or details.get("canonical_model") or "" - ), + "canonical_model": str(actual_lane.get("canonical_model") or details.get("canonical_model") or ""), "lane_family": str(actual_lane.get("family") or details.get("lane_family") or ""), "route_type": str(actual_lane.get("route_type") or details.get("route_type") or ""), "lane_cluster": str(actual_lane.get("cluster") or details.get("lane_cluster") or ""), - "selection_path": str( - relation.get("selection_path") or details.get("selection_path") or "" - ), - "runtime_window_state": str( - (details.get("attempt_runtime_state") or {}).get("window_state") or "" - ), - "recovered_recently": bool( - (details.get("attempt_runtime_state") or {}).get("recovered_recently") - ), + "selection_path": str(relation.get("selection_path") or details.get("selection_path") or ""), + "runtime_window_state": str((details.get("attempt_runtime_state") or {}).get("window_state") or ""), + "recovered_recently": bool((details.get("attempt_runtime_state") or {}).get("recovered_recently")), "last_recovered_issue_type": str( (details.get("attempt_runtime_state") or {}).get("last_recovered_issue_type") or "" ), @@ -1123,25 +1094,17 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: "lane_name": str(details.get("lane_name") or ""), "route_type": str(details.get("route_type") or ""), "lane_cluster": str(details.get("lane_cluster") or ""), - "benchmark_cluster": str( - details.get("benchmark_cluster") or selected_row.get("benchmark_cluster") or "" - ), + "benchmark_cluster": str(details.get("benchmark_cluster") or selected_row.get("benchmark_cluster") or ""), "quality_tier": str(details.get("quality_tier") or selected_row.get("quality_tier") or ""), - "reasoning_strength": str( - details.get("reasoning_strength") or selected_row.get("reasoning_strength") or "" - ), + "reasoning_strength": str(details.get("reasoning_strength") or selected_row.get("reasoning_strength") or ""), "benchmark_request_score": int(selected_row.get("benchmark_request_score") or 0), "cost_tier": str(details.get("cost_tier") or selected_row.get("cost_tier") or ""), "estimated_request_cost_usd": float(selected_row.get("estimated_request_cost_usd") or 0.0), "kilo_score": int(selected_row.get("kilo_score") or 0), "kilo_mode": str(selected_row.get("kilo_mode") or ""), "kilo_reasons": list(selected_row.get("kilo_reasons") or []), - "freshness_status": str( - details.get("freshness_status") or selected_row.get("freshness_status") or "" - ), - "review_age_days": int( - details.get("review_age_days") or selected_row.get("review_age_days") or -1 - ), + "freshness_status": str(details.get("freshness_status") or selected_row.get("freshness_status") or ""), + "review_age_days": int(details.get("review_age_days") or selected_row.get("review_age_days") or -1), "selection_path": str(details.get("selection_path") or "primary-selected"), } @@ -1151,9 +1114,7 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: elif decision.layer == "heuristic": why_selected.append(f"Matched heuristic '{decision.rule_name}'.") elif decision.layer == "profile": - why_selected.append( - f"Client profile '{details.get('profile_name') or ''}' influenced routing.".strip() - ) + why_selected.append(f"Client profile '{details.get('profile_name') or ''}' influenced routing.".strip()) elif decision.layer == "fallback": why_selected.append("No stronger rule matched, so the fallback chain was used.") else: @@ -1167,9 +1128,7 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: if heuristic_match.get("opencode_bias_applied"): why_selected.append("Opencode complexity bias promoted a stronger coding lane.") if heuristic_match.get("suppressed_for_complexity"): - why_selected.append( - "Simple-query routing was suppressed because the request looked riskier." - ) + why_selected.append("Simple-query routing was suppressed because the request looked riskier.") for note in request_insights.get("complexity_reasons") or []: why_selected.append(str(note)) if selected.get("benchmark_cluster") and request_insights.get("signal_groups"): @@ -1217,15 +1176,11 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: reason_bits.append(f"runtime penalty {row.get('runtime_penalty')}") if row.get("route_type") and row.get("route_type") != selected["route_type"]: reason_bits.append(f"{row.get('route_type')} route") - if row.get("benchmark_cluster") and row.get("benchmark_cluster") != selected.get( - "benchmark_cluster" - ): + if row.get("benchmark_cluster") and row.get("benchmark_cluster") != selected.get("benchmark_cluster"): reason_bits.append(f"weaker benchmark fit ({row.get('benchmark_cluster')})") if row.get("estimated_request_cost_usd"): reason_bits.append(f"est. ${float(row.get('estimated_request_cost_usd') or 0.0):.6f}") - if row.get("freshness_status") and row.get("freshness_status") != selected.get( - "freshness_status" - ): + if row.get("freshness_status") and row.get("freshness_status") != selected.get("freshness_status"): reason_bits.append(f"{row.get('freshness_status')} assumptions") alternatives.append( { @@ -1245,9 +1200,7 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: "freshness_status": str(row.get("freshness_status") or ""), "review_age_days": int(row.get("review_age_days") or -1), "runtime_penalty": int(row.get("runtime_penalty") or 0), - "reason": ", ".join(reason_bits) - if reason_bits - else "ranked below the selected route", + "reason": ", ".join(reason_bits) if reason_bits else "ranked below the selected route", } ) alternatives[-1]["why_not_selected"] = _alternative_loss_reasons( @@ -1300,8 +1253,7 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: { "kind": "route-add", "title": ( - f"Add {top_add.get('setup_provider_name') or top_add.get('provider_name')} " - "for fuller lane coverage" + f"Add {top_add.get('setup_provider_name') or top_add.get('provider_name')} for fuller lane coverage" ), "detail": str(top_add.get("reason") or ""), "path": "Provider Setup -> Guided Route Additions", @@ -1316,10 +1268,7 @@ def _build_route_summary(decision: RoutingDecision) -> dict[str, Any]: item for item in alternatives if float(item.get("estimated_request_cost_usd") or 0.0) > 0 - and ( - float(item.get("estimated_request_cost_usd") or 0.0) - <= max(0.0, selected_cost * 0.6) - ) + and (float(item.get("estimated_request_cost_usd") or 0.0) <= max(0.0, selected_cost * 0.6)) ), None, ) @@ -1371,9 +1320,7 @@ def _decorate_direct_decision(decision: RoutingDecision) -> RoutingDecision: details.setdefault("lane_name", str(lane.get("name") or "")) details.setdefault("route_type", str(lane.get("route_type") or "")) details.setdefault("lane_cluster", str(lane.get("cluster") or "")) - details.setdefault( - "route_runtime_state", _provider_runtime_state_snapshot().get(provider.name, {}) - ) + details.setdefault("route_runtime_state", _provider_runtime_state_snapshot().get(provider.name, {})) decision.details = details return decision @@ -1494,9 +1441,7 @@ def _client_highlights(client_totals: list[dict[str, Any]]) -> dict[str, dict[st failure_rows = [row for row in rows if (row.get("failures") or 0) > 0] return { - "top_requests": max( - rows, key=lambda row: (row.get("requests") or 0, row.get("total_tokens") or 0) - ), + "top_requests": max(rows, key=lambda row: (row.get("requests") or 0, row.get("total_tokens") or 0)), "top_tokens": max( rows, key=lambda row: (row.get("total_tokens") or 0, row.get("requests") or 0), @@ -1561,9 +1506,7 @@ def _estimate_request_dimensions(body: dict[str, Any]) -> dict[str, int | str]: system_text = "\n".join(system_parts) estimated_input_tokens = max(1, len(full_text) // 4) if full_text else 0 stable_prefix_tokens = max(1, len(system_text) // 4) if system_text else 0 - requested_output_tokens = ( - body.get("max_tokens") if isinstance(body.get("max_tokens"), int) else 0 - ) + requested_output_tokens = body.get("max_tokens") if isinstance(body.get("max_tokens"), int) else 0 return { "estimated_input_tokens": estimated_input_tokens, "stable_prefix_tokens": stable_prefix_tokens, @@ -1614,9 +1557,7 @@ def _merge_routing_context_headers(headers: dict[str, str], body: dict[str, Any] return merged -async def _apply_request_hooks( - body: dict[str, Any], headers: dict[str, str] -) -> tuple[dict[str, Any], AppliedHooks]: +async def _apply_request_hooks(body: dict[str, Any], headers: dict[str, str]) -> tuple[dict[str, Any], AppliedHooks]: """Apply configured request hooks before route resolution.""" model_requested = str(body.get("model", "auto")) applied = await apply_request_hooks( @@ -1804,9 +1745,7 @@ async def _execute_chat_completion_body( ) _adaptive_state.record_success( provider_name, - latency_ms=(result.get("_faigate") or {}).get("latency_ms", 0) - if isinstance(result, dict) - else 0.0, + latency_ms=(result.get("_faigate") or {}).get("latency_ms", 0) if isinstance(result, dict) else 0.0, ) trace_id: str | None = None @@ -1874,11 +1813,7 @@ async def _execute_chat_completion_body( extra={"shared_quota_group": quota_group} if quota_group else None, ) ) - if ( - quota_group - and not quota_isolated - and issue_type in {"quota-exhausted", "rate-limited", "auth-invalid"} - ): + if quota_group and not quota_isolated and issue_type in {"quota-exhausted", "rate-limited", "auth-invalid"}: blocked_quota_groups[quota_group] = { "provider": provider_name, "issue_type": issue_type, @@ -1994,9 +1929,7 @@ async def _read_json_body(request: Request, *, operation: str) -> dict[str, Any] raw = await request.body() max_bytes = int((_config.security or {}).get("max_json_body_bytes", 1_048_576)) if len(raw) > max_bytes: - raise PayloadTooLargeError( - f"{operation} body exceeded security.max_json_body_bytes ({len(raw)} > {max_bytes})" - ) + raise PayloadTooLargeError(f"{operation} body exceeded security.max_json_body_bytes ({len(raw)} > {max_bytes})") try: parsed = json.loads(raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError) as exc: @@ -2114,9 +2047,7 @@ def _extract_image_edit_request_fields(form_data: dict[str, Any]) -> dict[str, A return _normalize_image_request_body(form_data, capability="image_editing") -async def _read_uploaded_file( - value: Any, *, field_name: str, required: bool, max_bytes: int -) -> dict[str, Any] | None: +async def _read_uploaded_file(value: Any, *, field_name: str, required: bool, max_bytes: int) -> dict[str, Any] | None: """Read one uploaded file into a normalized payload.""" if value is None: if required: @@ -2130,9 +2061,7 @@ async def _read_uploaded_file( if not content: raise ValueError(f"Uploaded file '{field_name}' must not be empty") if len(content) > max_bytes: - raise PayloadTooLargeError( - f"Uploaded file '{field_name}' exceeded security.max_upload_bytes" - ) + raise PayloadTooLargeError(f"Uploaded file '{field_name}' exceeded security.max_upload_bytes") return { "filename": value.filename or field_name, @@ -2198,8 +2127,7 @@ async def _resolve_image_route_preview( rule_name=f"explicit-{capability}-model", confidence=1.0, reason=( - f"Model shortcut '{resolved_shortcut}' resolved to image provider:" - f" {direct_provider_name}" + f"Model shortcut '{resolved_shortcut}' resolved to image provider: {direct_provider_name}" if resolved_shortcut else f"Directly requested image provider: {direct_provider_name}" ), @@ -2303,10 +2231,7 @@ async def lifespan(app: FastAPI): source_refresh_cfg = _config.provider_source_refresh if source_refresh_cfg.get("enabled") and source_refresh_cfg.get("on_startup"): await _refresh_provider_source_catalog(force=True) - if ( - source_refresh_cfg.get("enabled") - and int(source_refresh_cfg.get("interval_seconds") or 0) > 0 - ): + if source_refresh_cfg.get("enabled") and int(source_refresh_cfg.get("interval_seconds") or 0) > 0: _provider_catalog_refresh_task = asyncio.create_task( _provider_source_refresh_loop(), name="faigate-provider-source-refresh", @@ -2447,9 +2372,7 @@ async def provider_catalog(): provider_ids=list(_config.provider_source_refresh.get("providers") or []), ) source_catalog["alerts"] = build_catalog_alerts(source_catalog) - source_catalog["alert_summary"] = build_catalog_alert_summary( - list(source_catalog.get("alerts") or []) - ) + source_catalog["alert_summary"] = build_catalog_alert_summary(list(source_catalog.get("alerts") or [])) return { **report, "source_catalog": source_catalog, @@ -2886,9 +2809,7 @@ async def image_generations(request: Request): ) _adaptive_state.record_success( provider_name, - latency_ms=(result.get("_faigate") or {}).get("latency_ms", 0) - if isinstance(result, dict) - else 0.0, + latency_ms=(result.get("_faigate") or {}).get("latency_ms", 0) if isinstance(result, dict) else 0.0, ) trace_id: str | None = None if _config.metrics.get("enabled") and isinstance(result, dict): @@ -3036,9 +2957,7 @@ async def image_edits(request: Request): ) _adaptive_state.record_success( provider_name, - latency_ms=(result.get("_faigate") or {}).get("latency_ms", 0) - if isinstance(result, dict) - else 0.0, + latency_ms=(result.get("_faigate") or {}).get("latency_ms", 0) if isinstance(result, dict) else 0.0, ) trace_id: str | None = None if _config.metrics.get("enabled") and isinstance(result, dict): @@ -3247,9 +3166,7 @@ async def anthropic_messages(request: Request): ) if isinstance(execution, _ChatExecutionFailure): - message = str( - execution.body.get("error", {}).get("message", "Anthropic bridge request failed") - ) + message = str(execution.body.get("error", {}).get("message", "Anthropic bridge request failed")) raw_error_type = str(execution.body.get("error", {}).get("type", "api_error")) return _anthropic_error_response( message, @@ -3259,9 +3176,7 @@ async def anthropic_messages(request: Request): bridge_headers = _anthropic_bridge_response_headers( source=str(canonical_request.metadata.get("source") or "claude-code"), - requested_model=str( - canonical_request.metadata.get("requested_model_original") or wire_request.model - ), + requested_model=str(canonical_request.metadata.get("requested_model_original") or wire_request.model), resolved_model=str(canonical_request.requested_model or wire_request.model), anthropic_version=str(headers.get("anthropic-version") or "") or None, anthropic_beta=str(headers.get("anthropic-beta") or "") or None, @@ -3275,9 +3190,7 @@ async def anthropic_messages(request: Request): provider_name=execution.provider_name, trace_id=execution.trace_id, ), - requested_model=str( - canonical_request.metadata.get("requested_model_original") or wire_request.model - ), + requested_model=str(canonical_request.metadata.get("requested_model_original") or wire_request.model), resolved_model=str(canonical_request.requested_model or wire_request.model), ), media_type="text/event-stream", diff --git a/faigate/onboarding.py b/faigate/onboarding.py index 328104b..cbcc88f 100644 --- a/faigate/onboarding.py +++ b/faigate/onboarding.py @@ -148,10 +148,7 @@ def _describe_client_match(match: dict[str, Any]) -> str: if match.get("header_present"): parts.append("headers present: " + ", ".join(match["header_present"])) if match.get("header_contains"): - header_parts = [ - f"{header}~{', '.join(values)}" - for header, values in sorted(match["header_contains"].items()) - ] + header_parts = [f"{header}~{', '.join(values)}" for header, values in sorted(match["header_contains"].items())] parts.append("header contains: " + "; ".join(header_parts)) if match.get("any"): any_parts = [] @@ -211,9 +208,7 @@ def _build_client_matrix(client_profiles: dict[str, Any]) -> list[dict[str, Any] "name": name, "source": "preset" if name in presets else "custom", "default": name == client_profiles.get("default", "generic"), - "matched_by": ( - _describe_client_match(match) if match else "default or explicit override" - ), + "matched_by": (_describe_client_match(match) if match else "default or explicit override"), "routing_intent": _summarize_profile_hints(profile), "has_rule": match is not None, } @@ -272,9 +267,7 @@ def build_onboarding_report( if not providers: suggestions.append("Add one provider before onboarding clients.") if providers and ready == 0: - suggestions.append( - "Configure at least one ready provider with a real key or local worker URL." - ) + suggestions.append("Configure at least one ready provider with a real key or local worker URL.") if not client_profiles.get("enabled"): suggestions.append("Enable client_profiles when multiple clients share one gateway.") if not client_profiles.get("presets"): @@ -309,8 +302,7 @@ def build_onboarding_report( }, "ai-native-app": { "recommended": any( - name not in {"generic", "openclaw", "n8n", "cli", "local-only"} - for name in profile_names + name not in {"generic", "openclaw", "n8n", "cli", "local-only"} for name in profile_names ), "header": "X-faigate-Client: your-app", "profile": "custom app profile", @@ -333,8 +325,7 @@ def build_onboarding_report( ], "notes": [ "Start workflow traffic with the n8n preset before adding custom policy rules.", - "Use route dry-runs to confirm cheaper or local-first defaults before" - " production runs.", + "Use route dry-runs to confirm cheaper or local-first defaults before production runs.", ], }, "opencode": { @@ -346,10 +337,8 @@ def build_onboarding_report( "Model: auto", ], "notes": [ - "Keep opencode on the OpenAI-compatible path first, and let Gate choose" - " the provider behind it.", - "Use the opencode profile when coding traffic should stay separate from" - " generic CLI traffic.", + "Keep opencode on the OpenAI-compatible path first, and let Gate choose the provider behind it.", + "Use the opencode profile when coding traffic should stay separate from generic CLI traffic.", ], }, "cli": { @@ -361,10 +350,8 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - "Use a stable client tag such as codex, claude, or kilocode to keep" - " traces readable.", - "Only add hook-based locality or provider overrides when one CLI flow" - " truly needs them.", + "Use a stable client tag such as codex, claude, or kilocode to keep traces readable.", + "Only add hook-based locality or provider overrides when one CLI flow truly needs them.", ], }, "swe-af": { @@ -376,10 +363,8 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - "Treat SWE-AF like another OpenAI-compatible agent client first, not a" - " special runtime.", - "Keep a stable client header so coding and delegated subflows remain" - " attributable in traces.", + "Treat SWE-AF like another OpenAI-compatible agent client first, not a special runtime.", + "Keep a stable client header so coding and delegated subflows remain attributable in traces.", ], }, "paperclip": { @@ -392,8 +377,7 @@ def build_onboarding_report( ], "notes": [ "Start with the common OpenAI-compatible path before inventing a deeper adapter.", - "Use client profiles only when paperclip traffic should differ from" - " other app traffic.", + "Use client profiles only when paperclip traffic should differ from other app traffic.", ], }, "ship-faster": { @@ -405,8 +389,7 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - "Use one short client tag first; add more profile splits only when the" - " workflow actually needs them.", + "Use one short client tag first; add more profile splits only when the workflow actually needs them.", "Prefer hook-based overrides only for narrow rollout or locality constraints.", ], }, @@ -419,8 +402,7 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - "LangChain should stay on the OpenAI-compatible path unless a" - " framework-specific blocker appears.", + "LangChain should stay on the OpenAI-compatible path unless a framework-specific blocker appears.", "Use route previews before splitting chain traffic into multiple custom profiles.", ], }, @@ -435,8 +417,7 @@ def build_onboarding_report( "notes": [ "Keep LangGraph on the shared gateway path and use client tags to" " distinguish graph traffic from generic LangChain traffic.", - "Only add dedicated policies when graph workloads need stricter" - " locality or cost boundaries.", + "Only add dedicated policies when graph workloads need stricter locality or cost boundaries.", ], }, "agno": { @@ -449,10 +430,7 @@ def build_onboarding_report( ], "notes": [ "Keep Agno on the shared OpenAI-compatible path first.", - ( - "Split profiles only when one agent family needs different cost " - "or locality defaults." - ), + ("Split profiles only when one agent family needs different cost or locality defaults."), ], }, "semantic-kernel": { @@ -464,10 +442,7 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - ( - "Use one stable client tag for kernel traffic before adding " - "skill-specific routing." - ), + ("Use one stable client tag for kernel traffic before adding skill-specific routing."), "Validate tool-heavy paths with route previews before adding custom hook hints.", ], }, @@ -480,14 +455,8 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - ( - "Treat Haystack as another OpenAI-compatible client unless a " - "pipeline-specific gap appears." - ), - ( - "Keep retrieval and generation traffic together until a real " - "routing split is needed." - ), + ("Treat Haystack as another OpenAI-compatible client unless a pipeline-specific gap appears."), + ("Keep retrieval and generation traffic together until a real routing split is needed."), ], }, "mastra": { @@ -500,10 +469,7 @@ def build_onboarding_report( ], "notes": [ "Keep Mastra on one shared gateway path first.", - ( - "Only add dedicated policies when workflow classes need stronger " - "provider separation." - ), + ("Only add dedicated policies when workflow classes need stronger provider separation."), ], }, "google-adk": { @@ -516,10 +482,7 @@ def build_onboarding_report( ], "notes": [ "Keep Google ADK traffic on the common gateway path for provider consistency.", - ( - "Use a dedicated profile only when ADK workloads need different " - "fallback or locality rules." - ), + ("Use a dedicated profile only when ADK workloads need different fallback or locality rules."), ], }, "autogen": { @@ -548,10 +511,7 @@ def build_onboarding_report( ], "notes": [ "Keep retrieval and generation on one gateway surface to start with.", - ( - "Add dedicated routing only when one index or workflow class needs " - "different provider behavior." - ), + ("Add dedicated routing only when one index or workflow class needs different provider behavior."), ], }, "crewai": { @@ -579,14 +539,8 @@ def build_onboarding_report( "export OPENAI_API_KEY=local", ], "notes": [ - ( - "Keep PydanticAI on the common OpenAI-compatible path unless a " - "model API gap appears." - ), - ( - "Use client profiles only when tool or validation-heavy traffic " - "deserves a different provider set." - ), + ("Keep PydanticAI on the common OpenAI-compatible path unless a model API gap appears."), + ("Use client profiles only when tool or validation-heavy traffic deserves a different provider set."), ], }, "camel": { @@ -599,10 +553,7 @@ def build_onboarding_report( ], "notes": [ "Start CAMEL traffic on the shared gateway path and keep one stable client tag.", - ( - "Only add narrower policies when multi-agent workloads need stronger " - "provider isolation." - ), + ("Only add narrower policies when multi-agent workloads need stronger provider isolation."), ], }, } @@ -673,9 +624,7 @@ def build_onboarding_validation(report: dict[str, Any]) -> dict[str, Any]: if providers["total"] > 1 and not routing["fallback_chain"]: blockers.append("Fallback chain is empty for a multi-provider setup.") if providers["total"] > 1 and not provider_rollout["stage_1_primary"]: - blockers.append( - "No ready primary provider is available for a staged multi-provider rollout." - ) + blockers.append("No ready primary provider is available for a staged multi-provider rollout.") if providers["not_ready"] > 0: warnings.append( @@ -694,10 +643,7 @@ def build_onboarding_validation(report: dict[str, Any]) -> dict[str, Any]: warnings.append("Multiple client profiles are configured, but no client match rules exist.") for row in clients.get("matrix", []): if row["name"] != clients["default_profile"] and not row["has_rule"]: - warnings.append( - f"Client profile '{row['name']}' has no match rule and only applies" - " via explicit override." - ) + warnings.append(f"Client profile '{row['name']}' has no match rule and only applies via explicit override.") if routing["request_hooks_enabled"] and routing["request_hook_count"] == 0: warnings.append("Request hooks are enabled but no hooks are configured.") @@ -718,9 +664,7 @@ def render_onboarding_report(report: dict[str, Any]) -> str: ops_block = report["operations"] integration_block = report["integrations"] preset_text = ", ".join(client_block["presets"]) if client_block["presets"] else "none" - fallback_text = ( - ", ".join(routing_block["fallback_chain"]) if routing_block["fallback_chain"] else "none" - ) + fallback_text = ", ".join(routing_block["fallback_chain"]) if routing_block["fallback_chain"] else "none" lines = [ "fusionAIze Gate onboarding report", @@ -744,8 +688,7 @@ def render_onboarding_report(report: dict[str, Any]) -> str: for item in provider_block["items"]: readiness = "ready" if item["ready"] else f"not ready ({item['readiness_reason']})" lines.append( - f"- {item['name']}: {item['contract']} / {item['backend']} / " - f"{item['tier'] or 'default'} / {readiness}" + f"- {item['name']}: {item['contract']} / {item['backend']} / {item['tier'] or 'default'} / {readiness}" ) lines.extend( @@ -773,10 +716,8 @@ def render_onboarding_report(report: dict[str, Any]) -> str: "", "Routing", f"- fallback chain: {fallback_text}", - f"- policy layer: {routing_block['policy_layer_enabled']} " - f"({routing_block['policy_rule_count']} rules)", - f"- request hooks: {routing_block['request_hooks_enabled']} " - f"({routing_block['request_hook_count']} hooks)", + f"- policy layer: {routing_block['policy_layer_enabled']} ({routing_block['policy_rule_count']} rules)", + f"- request hooks: {routing_block['request_hooks_enabled']} ({routing_block['request_hook_count']} hooks)", "", "Provider rollout", "- stage 1 primary: " + (", ".join(rollout_block["stage_1_primary"]) or "none"), @@ -784,8 +725,7 @@ def render_onboarding_report(report: dict[str, Any]) -> str: "- stage 3 modality: " + (", ".join(rollout_block["stage_3_modality"]) or "none"), "", "Provider catalog", - f"- tracked providers: {catalog_block['tracked_providers']} / " - f"{catalog_block['total_providers']}", + f"- tracked providers: {catalog_block['tracked_providers']} / {catalog_block['total_providers']}", f"- alerts: {catalog_block['alert_count']}", "", "Operations", @@ -809,9 +749,7 @@ def render_onboarding_report(report: dict[str, Any]) -> str: + f"{item['provider']}: {item['provider_type']} / {item['offer_track']} / " + f"{item['evidence_level']} / {item['volatility']}" ) - discovery_items = [ - item for item in tracked_items if (item.get("discovery") or {}).get("resolved_url") - ] + discovery_items = [item for item in tracked_items if (item.get("discovery") or {}).get("resolved_url")] if discovery_items: policy = catalog_block.get("recommendation_policy", {}) lines.append("- provider discovery:") @@ -872,10 +810,7 @@ def render_onboarding_report_markdown(report: dict[str, Any]) -> str: env_requirements = env_block.get("provider_requirements", {}) if env_requirements.get("missing"): - lines.append( - "- Missing provider env: " - + ", ".join(f"`{item}`" for item in env_requirements["missing"]) - ) + lines.append("- Missing provider env: " + ", ".join(f"`{item}`" for item in env_requirements["missing"])) if provider_block["items"]: lines.extend(["", "### Provider Inventory"]) @@ -910,24 +845,17 @@ def render_onboarding_report_markdown(report: dict[str, Any]) -> str: [ "", "## Routing", - "- Fallback chain: " - + (", ".join(f"`{item}`" for item in routing_block["fallback_chain"]) or "none"), - f"- Policy layer: {routing_block['policy_layer_enabled']} " - f"({routing_block['policy_rule_count']} rules)", - f"- Request hooks: {routing_block['request_hooks_enabled']} " - f"({routing_block['request_hook_count']} hooks)", + "- Fallback chain: " + (", ".join(f"`{item}`" for item in routing_block["fallback_chain"]) or "none"), + f"- Policy layer: {routing_block['policy_layer_enabled']} ({routing_block['policy_rule_count']} rules)", + f"- Request hooks: {routing_block['request_hooks_enabled']} ({routing_block['request_hook_count']} hooks)", "", "## Provider Rollout", - "- Stage 1 primary: " - + (", ".join(f"`{item}`" for item in rollout_block["stage_1_primary"]) or "none"), - "- Stage 2 secondary: " - + (", ".join(f"`{item}`" for item in rollout_block["stage_2_secondary"]) or "none"), - "- Stage 3 modality: " - + (", ".join(f"`{item}`" for item in rollout_block["stage_3_modality"]) or "none"), + "- Stage 1 primary: " + (", ".join(f"`{item}`" for item in rollout_block["stage_1_primary"]) or "none"), + "- Stage 2 secondary: " + (", ".join(f"`{item}`" for item in rollout_block["stage_2_secondary"]) or "none"), + "- Stage 3 modality: " + (", ".join(f"`{item}`" for item in rollout_block["stage_3_modality"]) or "none"), "", "## Provider Catalog", - f"- Tracked providers: {catalog_block['tracked_providers']} / " - f"{catalog_block['total_providers']}", + f"- Tracked providers: {catalog_block['tracked_providers']} / {catalog_block['total_providers']}", f"- Alerts: {catalog_block['alert_count']}", ] ) @@ -946,15 +874,12 @@ def render_onboarding_report_markdown(report: dict[str, Any]) -> str: + f"`{item['provider']}`: {item['provider_type']} / {item['offer_track']} / " + f"{item['evidence_level']} / {item['volatility']}" ) - discovery_items = [ - item for item in tracked_items if (item.get("discovery") or {}).get("resolved_url") - ] + discovery_items = [item for item in tracked_items if (item.get("discovery") or {}).get("resolved_url")] if discovery_items: policy = catalog_block.get("recommendation_policy", {}) lines.append("- Provider discovery:") lines.append( - " - Policy: provider links affect ranking = " - + f"`{policy.get('provider_links_affect_ranking', False)}`" + " - Policy: provider links affect ranking = " + f"`{policy.get('provider_links_affect_ranking', False)}`" ) for item in discovery_items: discovery = item["discovery"] diff --git a/faigate/provider_catalog.py b/faigate/provider_catalog.py index 4c1bfaf..6ff0b2f 100644 --- a/faigate/provider_catalog.py +++ b/faigate/provider_catalog.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import Any +from . import registry from .config import Config from .lane_registry import ( get_active_model_id, @@ -16,15 +17,11 @@ get_canonical_model_catalog, get_provider_lane_binding, ) -from . import registry - # Path to external fusionaize-metadata repository -_EXTERNAL_METADATA_ROOT = Path( - "/Users/andrelange/Documents/repositories/github/fusionaize-metadata" -) +_EXTERNAL_METADATA_ROOT = Path("/Users/andrelange/Documents/repositories/github/fusionaize-metadata") _EXTERNAL_CATALOG_PATH = _EXTERNAL_METADATA_ROOT / "providers" / "catalog.v1.json" -_EXTERNAL_OVERLAY_PATH = _EXTERNAL_METADATA_ROOT / "products" / "gate" / "overlays.v1.json" +_EXTERNAL_OVERLAY_PATH = _EXTERNAL_METADATA_ROOT / "products" / "gate" / "overlays.v1.json" # noqa: E501 _COMMUNITY_WATCHLIST = { "label": "free-llm-api-resources", @@ -32,8 +29,9 @@ } _DISCOVERY_DISCLOSURE = ( - "Provider recommendations stay performance-led. Shown signup or discovery links are " - "informational only and do not affect ranking." + "Provider recommendations stay performance-led. " + "Shown signup or discovery links are informational only " + "and do not affect ranking." ) _EXTERNAL_CATALOG_ENV = "FAIGATE_PROVIDER_METADATA_FILE" @@ -43,9 +41,7 @@ _METADATA_CATALOG_RELATIVE_PATH = Path("providers") / "catalog.v1.json" # Hardcoded fallback path for external metadata repository (legacy) -_EXTERNAL_METADATA_ROOT = Path( - "/Users/andrelange/Documents/repositories/github/fusionaize-metadata" -) +_EXTERNAL_METADATA_ROOT = Path("/Users/andrelange/Documents/repositories/github/fusionaize-metadata") # Cache for external metadata _EXTERNAL_CATALOG_CACHE: dict[str, Any] | None = None @@ -75,7 +71,7 @@ def _get_external_catalog_path() -> Path: def _get_external_overlay_path() -> Path: """Get path to external overlays.v1.json for the current product.""" - product = os.environ.get(_EXTERNAL_CATALOG_PRODUCT_ENV, _DEFAULT_METADATA_PRODUCT).strip() + product = os.environ.get(_EXTERNAL_CATALOG_PRODUCT_ENV, _DEFAULT_METADATA_PRODUCT).strip() # noqa: E501 if not product: product = _DEFAULT_METADATA_PRODUCT root = _get_external_metadata_root() @@ -102,7 +98,7 @@ def _load_external_catalog() -> dict[str, Any]: return {} try: - with open(catalog_path, "r", encoding="utf-8") as f: + with open(catalog_path, encoding="utf-8") as f: data = json.load(f) _EXTERNAL_CATALOG_CACHE = data.get("providers", {}) _EXTERNAL_CATALOG_MTIME = catalog_path.stat().st_mtime @@ -133,7 +129,7 @@ def _load_external_overlay() -> dict[str, Any]: return {} try: - with open(overlay_path, "r", encoding="utf-8") as f: + with open(overlay_path, encoding="utf-8") as f: data = json.load(f) _EXTERNAL_OVERLAY_CACHE = data.get("providers", {}) _EXTERNAL_OVERLAY_MTIME = overlay_path.stat().st_mtime @@ -166,7 +162,8 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: pricing[key] = value # Normalize pricing field names from external catalog - # Map input_cost_per_1m -> input, output_cost_per_1m -> output, cache_read_cost_per_1m -> cache_read + # Map input_cost_per_1m -> input, output_cost_per_1m -> output, + # cache_read_cost_per_1m -> cache_read field_mapping = { "input_cost_per_1m": "input", "output_cost_per_1m": "output", @@ -219,7 +216,7 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: if field in registry_pricing and registry_pricing[field]: # Convert to float if not already value = registry_pricing[field] - if isinstance(value, (int, float)) and value > 0 and field not in pricing: + if isinstance(value, (int, float)) and value > 0 and field not in pricing: # noqa: E501 pricing[field] = float(value) return pricing @@ -298,7 +295,7 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: "official_source_url": "https://openrouter.ai/docs/features/provider-routing", "signup_url": "https://openrouter.ai/", "watch_sources": [], - "notes": "Marketplace fallback path with official provider routing and BYOK support", + "notes": "Marketplace fallback path with official provider routing and BYOK support", # noqa: E501 "last_reviewed": "2026-03-19", }, "kilocode": { @@ -313,12 +310,12 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: "official_source_url": "https://kilo.ai/docs/gateway/models-and-providers", "signup_url": "https://kilo.ai/", "watch_sources": [_COMMUNITY_WATCHLIST], - "notes": "Current curated Kilo free-tier model; free and budget tracks can move quickly", + "notes": "Current curated Kilo free-tier model; free and budget tracks can move quickly", # noqa: E501 "last_reviewed": "2026-03-19", }, "kilo-sonnet": { "recommended_model": "anthropic/claude-sonnet-4.6", - "aliases": ["anthropic/claude-sonnet-4.6", "kilo-auto/frontier", "kilo-auto/balanced"], + "aliases": ["anthropic/claude-sonnet-4.6", "kilo-auto/frontier", "kilo-auto/balanced"], # noqa: E501 "track": "stable", "offer_track": "gateway-paid", "provider_type": "aggregator", @@ -346,10 +343,7 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: "official_source_url": "https://kilo.ai/docs/gateway/models-and-providers", "signup_url": "https://kilo.ai/", "watch_sources": [], - "notes": ( - "Kilo paid Opus lane; useful when expiring Kilo credits should absorb " - "premium reasoning traffic" - ), + "notes": ("Kilo paid Opus lane; useful when expiring Kilo credits should absorb premium reasoning traffic"), "last_reviewed": "2026-03-29", }, "blackbox-free": { @@ -365,7 +359,7 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: "signup_url": "https://cloud.blackbox.ai/", "watch_sources": [_COMMUNITY_WATCHLIST], "notes": ( - "Legacy provider id for the current low-cost BLACKBOX Grok Code Fast route; " + "Legacy provider id for the current low-cost BLACKBOX Grok Code Fast route; " # noqa: E501 "verify often because pricing and model availability can rotate" ), "last_reviewed": "2026-03-29", @@ -487,7 +481,7 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]: "official_source_url": "https://blockrun.ai/docs/products/routing/clawrouter", "signup_url": "https://blockrun.ai/", "watch_sources": [], - "notes": "BlockRun ClawRouter uses wallet/x402 routing modes rather than a classic API key", + "notes": "BlockRun ClawRouter uses wallet/x402 routing modes rather than a classic API key", # noqa: E501 "last_reviewed": "2026-03-19", }, } @@ -499,7 +493,7 @@ def _normalize_catalog_entry(entry: Any) -> dict[str, Any]: return {str(key): value for key, value in entry.items()} -def _merge_catalog_entry(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: +def _merge_catalog_entry(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: # noqa: E501 merged = dict(base) for key, value in overlay.items(): if isinstance(value, dict) and isinstance(merged.get(key), dict): @@ -545,8 +539,8 @@ def build_provider_metadata_snapshot( catalog_payload = _load_catalog_payload(root / _METADATA_CATALOG_RELATIVE_PATH) catalog = _normalize_catalog_payload(catalog_payload) - product_name = str(product or _DEFAULT_METADATA_PRODUCT).strip() or _DEFAULT_METADATA_PRODUCT - overlay_payload = _load_catalog_payload(root / "products" / product_name / "overlays.v1.json") + product_name = str(product or _DEFAULT_METADATA_PRODUCT).strip() or _DEFAULT_METADATA_PRODUCT # noqa: E501 + overlay_payload = _load_catalog_payload(root / "products" / product_name / "overlays.v1.json") # noqa: E501 overlay = _normalize_catalog_payload(overlay_payload) merged_catalog = dict(catalog) @@ -557,9 +551,7 @@ def build_provider_metadata_snapshot( ) return { - "schema_version": str( - catalog_payload.get("schema_version") or "fusionaize-provider-catalog/v1" - ), + "schema_version": str(catalog_payload.get("schema_version") or "fusionaize-provider-catalog/v1"), "generated_at": str(catalog_payload.get("generated_at") or ""), "source_repo": str(catalog_payload.get("source_repo") or ""), "product": product_name, @@ -593,9 +585,7 @@ def _load_external_provider_catalog() -> dict[str, dict[str, Any]]: if not metadata_dir: return {} product = str(os.environ.get(_EXTERNAL_CATALOG_PRODUCT_ENV, _DEFAULT_METADATA_PRODUCT) or "") - return _normalize_catalog_payload( - build_provider_metadata_snapshot(metadata_dir, product=product) - ) + return _normalize_catalog_payload(build_provider_metadata_snapshot(metadata_dir, product=product)) def _get_catalog_source() -> dict[str, dict[str, Any]]: @@ -616,12 +606,12 @@ def _discovery_env_var(provider_name: str) -> str: return f"FAIGATE_PROVIDER_LINK_{token}_URL" -def _build_discovery_metadata(provider_name: str, catalog_entry: dict[str, Any]) -> dict[str, Any]: +def _build_discovery_metadata(provider_name: str, catalog_entry: dict[str, Any]) -> dict[str, Any]: # noqa: E501 env_var = _discovery_env_var(provider_name) operator_url = str(os.environ.get(env_var, "") or "").strip() signup_url = str(catalog_entry.get("signup_url", "") or "").strip() discovery_url = ( - operator_url or signup_url or str(catalog_entry.get("official_source_url", "") or "") + operator_url or signup_url or str(catalog_entry.get("official_source_url", "") or "") # noqa: E501 ) return { @@ -691,7 +681,7 @@ def build_provider_refresh_guidance( override = dict(overrides.get(normalized_name) or {}) freshness_status = str(override.get("freshness_status") or "").strip().lower() review_age_days_raw = override.get("review_age_days") - review_age_days = int(review_age_days_raw) if review_age_days_raw not in (None, "") else -1 + review_age_days = int(review_age_days_raw) if review_age_days_raw not in (None, "") else -1 # noqa: E501 freshness_hint = str(override.get("freshness_hint") or "").strip() if not freshness_status: @@ -711,13 +701,16 @@ def build_provider_refresh_guidance( action = "refresh-now" if freshness_status == "stale" else "review-soon" action_label = "refresh now" if action == "refresh-now" else "review soon" reason = freshness_hint or ( - "benchmark and cost assumptions are stale; review before trusting them heavily" + "benchmark and cost assumptions are stale; review before trusting them heavily" # noqa: E501 if freshness_status == "stale" else "benchmark and cost assumptions are aging and worth rechecking soon" ) - if catalog_entry.get("volatility") in {"medium", "high"} and catalog_entry.get( - "offer_track" - ) in {"free", "credit", "byok", "marketplace"}: + if catalog_entry.get("volatility") in {"medium", "high"} and catalog_entry.get("offer_track") in { + "free", + "credit", + "byok", + "marketplace", + }: reason += " This route also sits on a more volatile offer track." guidance.append( @@ -784,7 +777,7 @@ def _tracked_item( # Get pricing metadata pricing = _get_provider_pricing(provider_name) has_numeric_rates = ( - bool(pricing.get("input")) or bool(pricing.get("output")) or bool(pricing.get("cache_read")) + bool(pricing.get("input")) or bool(pricing.get("output")) or bool(pricing.get("cache_read")) # noqa: E501 ) pricing_available = bool(pricing) @@ -814,9 +807,7 @@ def _tracked_item( "route_type": lane.get("route_type", ""), "lane_cluster": lane.get("cluster", ""), "benchmark_cluster": lane.get("benchmark_cluster", ""), - "preferred_degrades": list( - canonical_entry.get("preferred_degrades", lane.get("degrade_to", [])) - ), + "preferred_degrades": list(canonical_entry.get("preferred_degrades", lane.get("degrade_to", []))), "lane": lane, "pricing": pricing, "pricing_available": pricing_available, @@ -852,7 +843,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: severity="warning", code="untracked-provider", message=( - f"Provider '{provider_name}' is not in the curated provider " + f"Provider '{provider_name}' is not in the curated provider " # noqa: E501 "catalog yet." ), ) @@ -874,7 +865,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: severity="warning", code="model-drift", message=( - f"Provider '{provider_name}' uses model '{model}', while the curated " + f"Provider '{provider_name}' uses model '{model}', while the curated " # noqa: E501 f"catalog recommends '{item['recommended_model']}'." ), recommended_model=item["recommended_model"], @@ -912,7 +903,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: severity="notice", code="volatile-offer-configured", message=( - f"Provider '{provider_name}' is on the '{item['offer_track']}' track " + f"Provider '{provider_name}' is on the '{item['offer_track']}' track " # noqa: E501 f"with {item['volatility']} volatility; limits, models, or " "pricing may change quickly." ), @@ -928,8 +919,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: severity="notice", code="catalog-stale", message=( - f"Catalog guidance for provider '{provider_name}' is " - f"{item['catalog_age_days']} days old." + f"Catalog guidance for provider '{provider_name}' is {item['catalog_age_days']} days old." ), last_reviewed=item["last_reviewed"], ) @@ -989,7 +979,9 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: "item_count": sum( 1 for item in items - if item.get("status") == "tracked" and not item.get("model_matches_recommendation") + if ( + item.get("status") == "tracked" and not item.get("model_matches_recommendation") # noqa: E501 + ) ), "total_items": tracked, }, @@ -1001,14 +993,14 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: "item_count": sum( 1 for item in items - if item.get("status") == "tracked" and item.get("evidence_level") != "official" + if (item.get("status") == "tracked" and item.get("evidence_level") != "official") # noqa: E501 ), "total_items": tracked, }, { "id": "volatile_offer_review", "name": "Volatile offer review", - "description": "Providers on volatile offer tracks (free/credit/marketplace)", + "description": "Providers on volatile offer tracks (free/credit/marketplace)", # noqa: E501 "priority": "low", "item_count": sum( 1 @@ -1028,7 +1020,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: 1 for item in items if item.get("status") == "tracked" - and item.get("catalog_age_days", 0) > int(check_cfg.get("max_catalog_age_days", 30)) + and item.get("catalog_age_days", 0) > int(check_cfg.get("max_catalog_age_days", 30)) # noqa: E501 ), "total_items": tracked, }, @@ -1051,9 +1043,9 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: { "id": "improve_pricing_coverage", "title": "Improve pricing metadata coverage", - "description": f"{cluster['item_count']} tracked providers lack numeric pricing rates.", + "description": f"{cluster['item_count']} tracked providers lack numeric pricing rates.", # noqa: E501 "priority": cluster["priority"], - "action": "Add numeric pricing rates to external catalog for providers missing rates.", + "action": "Add numeric pricing rates to external catalog for providers missing rates.", # noqa: E501 "cluster_id": cluster["id"], } ) @@ -1062,7 +1054,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: { "id": "expand_catalog_coverage", "title": "Expand catalog coverage", - "description": f"{cluster['item_count']} configured providers are not yet tracked in the catalog.", + "description": f"{cluster['item_count']} configured providers are not yet tracked in the catalog.", # noqa: E501 "priority": cluster["priority"], "action": "Add catalog entries for untracked providers.", "cluster_id": cluster["id"], @@ -1073,9 +1065,9 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: { "id": "align_models_with_recommendations", "title": "Align configured models with catalog recommendations", - "description": f"{cluster['item_count']} tracked providers have configured models that don't match catalog recommendations.", + "description": f"{cluster['item_count']} tracked providers have configured models that don't match catalog recommendations.", # noqa: E501 "priority": cluster["priority"], - "action": "Update provider model configurations to match catalog recommendations.", + "action": "Update provider model configurations to match catalog recommendations.", # noqa: E501 "cluster_id": cluster["id"], } ) @@ -1084,9 +1076,9 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: { "id": "review_evidence_sources", "title": "Review evidence sources", - "description": f"{cluster['item_count']} tracked providers rely on unofficial evidence sources.", + "description": f"{cluster['item_count']} tracked providers rely on unofficial evidence sources.", # noqa: E501 "priority": cluster["priority"], - "action": "Verify and potentially upgrade evidence sources to official documentation.", + "action": "Verify and potentially upgrade evidence sources to official documentation.", # noqa: E501 "cluster_id": cluster["id"], } ) @@ -1095,9 +1087,9 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: { "id": "review_volatile_offers", "title": "Review volatile offers", - "description": f"{cluster['item_count']} tracked providers are on volatile offer tracks (free/credit/marketplace).", + "description": f"{cluster['item_count']} tracked providers are on volatile offer tracks (free/credit/marketplace).", # noqa: E501 "priority": cluster["priority"], - "action": "Monitor these providers for changes in pricing, availability, or terms.", + "action": "Monitor these providers for changes in pricing, availability, or terms.", # noqa: E501 "cluster_id": cluster["id"], } ) @@ -1106,9 +1098,9 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: { "id": "refresh_stale_catalog_entries", "title": "Refresh stale catalog entries", - "description": f"{cluster['item_count']} catalog entries are older than the maximum age threshold.", + "description": f"{cluster['item_count']} catalog entries are older than the maximum age threshold.", # noqa: E501 "priority": cluster["priority"], - "action": "Review and update catalog entries to ensure they reflect current provider offerings.", + "action": "Review and update catalog entries to ensure they reflect current provider offerings.", # noqa: E501 "cluster_id": cluster["id"], } ) @@ -1119,7 +1111,7 @@ def build_provider_catalog_report(config: Config) -> dict[str, Any]: "title": cluster["name"], "description": cluster["description"], "priority": cluster["priority"], - "action": f"Address {cluster['item_count']} items in this category.", + "action": f"Address {cluster['item_count']} items in this category.", # noqa: E501 "cluster_id": cluster["id"], } ) @@ -1167,7 +1159,7 @@ def build_provider_discovery_view( resolved_url = str(discovery.get("resolved_url", "") or "").strip() if not resolved_url: continue - if normalized_link_source and discovery.get("link_source") != normalized_link_source: + if normalized_link_source and discovery.get("link_source") != normalized_link_source: # noqa: E501 continue if disclosed_only and not discovery.get("disclosure_required", False): continue @@ -1185,7 +1177,7 @@ def build_provider_discovery_view( "link_source": discovery.get("link_source", "official"), "operator_env_var": discovery.get("operator_env_var", ""), "disclosure": discovery.get("disclosure", ""), - "disclosure_required": bool(discovery.get("disclosure_required", False)), + "disclosure_required": bool(discovery.get("disclosure_required", False)), # noqa: E501 } ) diff --git a/faigate/provider_catalog_refresh.py b/faigate/provider_catalog_refresh.py index e7934c2..39275cb 100644 --- a/faigate/provider_catalog_refresh.py +++ b/faigate/provider_catalog_refresh.py @@ -92,29 +92,18 @@ def _source_refresh_suggestion(item: dict[str, Any]) -> str: f"--provider {provider_id} and verify the source URL, parser, or " "auth assumptions before trusting catalog data here." ) - return ( - f"Refresh {provider_id} before relying on older model, pricing, or free-tier assumptions." - ) + return f"Refresh {provider_id} before relying on older model, pricing, or free-tier assumptions." def _catalog_change_suggestion(event: dict[str, Any]) -> str: change_type = str(event.get("change_type") or "") provider_id = str(event.get("provider_id") or "provider") if change_type == "model-removed": - return ( - f"Review configured model ids and fallback mirrors for {provider_id}; " - "one catalog entry disappeared." - ) + return f"Review configured model ids and fallback mirrors for {provider_id}; one catalog entry disappeared." if change_type == "field-changed": - return ( - f"Recheck pricing, context, and routing weights for {provider_id}; " - "a tracked field changed." - ) + return f"Recheck pricing, context, and routing weights for {provider_id}; a tracked field changed." if change_type == "model-added": - return ( - f"Review whether the newly listed {provider_id} model belongs in " - "route additions or scenarios." - ) + return f"Review whether the newly listed {provider_id} model belongs in route additions or scenarios." return f"Review recent provider catalog changes for {provider_id}." @@ -187,12 +176,8 @@ def build_catalog_alerts( change_type=change_type, ), "provider_id": str(event.get("provider_id") or ""), - "headline": ( - f"Catalog change detected for {event.get('provider_id')}: " - f"{event.get('change_type')}" - ), - "detail": str(event.get("message") or "").strip() - or "A provider catalog change was detected.", + "headline": (f"Catalog change detected for {event.get('provider_id')}: {event.get('change_type')}"), + "detail": str(event.get("message") or "").strip() or "A provider catalog change was detected.", "suggestion": _catalog_change_suggestion(event), "source_kind": str(event.get("source_kind") or ""), "change_type": change_type, @@ -290,10 +275,7 @@ def build_catalog_summary( "models_count": len(latest_models), "pricing_count": len(latest_pricing), "docs_index_count": len(latest_docs_index), - "sample_models": [ - str(item.get("model_id") or "") - for item in (latest_pricing or latest_models)[:5] - ], + "sample_models": [str(item.get("model_id") or "") for item in (latest_pricing or latest_models)[:5]], "billing_notes": str(source.get("billing_notes") or ""), "account_profile": store.get_account_profile(provider_id), } @@ -368,10 +350,7 @@ def render_catalog_summary_text( + f"due={int(summary.get('due_sources') or 0)} | " + f"recent changes={int(summary.get('recent_changes') or 0)}" ) - alert_summary = dict( - summary.get("alert_summary") - or build_catalog_alert_summary(list(summary.get("alerts") or [])) - ) + alert_summary = dict(summary.get("alert_summary") or build_catalog_alert_summary(list(summary.get("alerts") or []))) lines.append( " alert summary: " + f"status={alert_summary.get('status') or 'clear'} | " @@ -394,9 +373,7 @@ def render_catalog_summary_text( if item.get("refresh_interval_seconds"): lines.append(f" refresh interval: {int(item['refresh_interval_seconds'])}s") if item.get("seconds_since_success") is not None: - lines.append( - f" age: {int(float(item['seconds_since_success']))}s since last success" - ) + lines.append(f" age: {int(float(item['seconds_since_success']))}s since last success") profile = dict(item.get("account_profile") or {}) if profile: profile_bits = [str(profile.get("billing_mode") or "")] @@ -451,9 +428,7 @@ def due_provider_ids( stored = dict(source_rows.get(provider_id) or {}) last_success_at = float(stored.get("last_success_at") or 0) refresh_interval_seconds = int( - stored.get("refresh_interval_seconds") - or source.get("refresh_interval_seconds") - or 21600 + stored.get("refresh_interval_seconds") or source.get("refresh_interval_seconds") or 21600 ) if not last_success_at or refresh_interval_seconds <= 0: due.append(provider_id) @@ -551,9 +526,7 @@ def parse_regex_model_refs( """Extract model ids from docs text using prefixes and regex patterns.""" found: set[str] = set() rows: list[dict[str, Any]] = [] - prefix_patterns = [ - re.escape(prefix) + r"[a-zA-Z0-9.\-:\/]+" for prefix in (model_prefixes or []) - ] + prefix_patterns = [re.escape(prefix) + r"[a-zA-Z0-9.\-:\/]+" for prefix in (model_prefixes or [])] for pattern in prefix_patterns + list(model_patterns or []): for match in re.findall(pattern, text): token = str(match).strip("`*.,)('\"") diff --git a/faigate/provider_catalog_store.py b/faigate/provider_catalog_store.py index 2d29fcd..003e4e5 100644 --- a/faigate/provider_catalog_store.py +++ b/faigate/provider_catalog_store.py @@ -206,10 +206,7 @@ def replace_model_snapshot( return ts = float(snapshot_at or time.time()) self._conn.execute( - ( - "DELETE FROM provider_model_snapshots " - "WHERE provider_id=? AND source_kind=? AND snapshot_at=?" - ), + ("DELETE FROM provider_model_snapshots WHERE provider_id=? AND source_kind=? AND snapshot_at=?"), (provider_id, source_kind, ts), ) for model in models: diff --git a/faigate/providers.py b/faigate/providers.py index f6fdf4d..7e2b62b 100644 --- a/faigate/providers.py +++ b/faigate/providers.py @@ -469,9 +469,7 @@ async def generate_image( body.update(extra_body) headers = self._authorization_headers(content_type="application/json") - url = self._transport_url( - self._transport_path("image_generation_path", "/images/generations") - ) + url = self._transport_url(self._transport_path("image_generation_path", "/images/generations")) t0 = time.time() try: @@ -673,9 +671,7 @@ async def complete( self.health.record_failure(f"Connection error: {e}") raise ProviderError(self.name, 0, f"Connection error: {e}") from e - async def _stream_response( - self, url: str, headers: dict, body: dict, t0: float - ) -> AsyncIterator[bytes]: + async def _stream_response(self, url: str, headers: dict, body: dict, t0: float) -> AsyncIterator[bytes]: """Yield SSE chunks for streaming responses.""" async with self._client.stream("POST", url, json=body, headers=headers) as resp: if resp.status_code >= 400: diff --git a/faigate/registry.py b/faigate/registry.py index 1521e60..e852912 100644 --- a/faigate/registry.py +++ b/faigate/registry.py @@ -278,8 +278,7 @@ class ProviderDef(TypedDict, total=False): example_model="volcengine-plan/ark-code-latest", pricing={"input": 0.0, "output": 0.0}, notes=( - "Volcano Engine – coding models" - " (ark-code-latest, doubao-seed-code, kimi-k2.5, kimi-k2-thinking, glm-4.7)" + "Volcano Engine – coding models (ark-code-latest, doubao-seed-code, kimi-k2.5, kimi-k2-thinking, glm-4.7)" ), ), # ── BytePlus (international equivalent of Volcano Engine) ───────────── @@ -303,8 +302,7 @@ class ProviderDef(TypedDict, total=False): example_model="byteplus-plan/ark-code-latest", pricing={"input": 0.0, "output": 0.0}, notes=( - "BytePlus ARK – coding models" - " (ark-code-latest, doubao-seed-code, kimi-k2.5, kimi-k2-thinking, glm-4.7)" + "BytePlus ARK – coding models (ark-code-latest, doubao-seed-code, kimi-k2.5, kimi-k2-thinking, glm-4.7)" ), ), # ── Synthetic ───────────────────────────────────────────────────────── @@ -416,10 +414,7 @@ class ProviderDef(TypedDict, total=False): tier="default", example_model="qwen-portal/coder-model", pricing={"input": 0.0, "output": 0.0}, - notes=( - "Qwen OAuth (free tier) – device-code flow;" - " requires: openclaw plugins enable qwen-portal-auth" - ), + notes=("Qwen OAuth (free tier) – device-code flow; requires: openclaw plugins enable qwen-portal-auth"), ), } diff --git a/faigate/router.py b/faigate/router.py index a9afa8a..91ca7b7 100644 --- a/faigate/router.py +++ b/faigate/router.py @@ -604,9 +604,7 @@ def _estimated_request_cost_usd(provider: dict[str, Any], ctx: _RoutingContext | cache_threshold = max(64, cache_min_prefix) if ctx.stable_prefix_tokens >= cache_threshold and cache_mode != "none": cached_tokens = min(prompt_tokens, int(ctx.stable_prefix_tokens)) - prompt_cost = ( - (cached_tokens * cache_rate) + ((prompt_tokens - cached_tokens) * prompt_rate) - ) / 1_000_000 + prompt_cost = ((cached_tokens * cache_rate) + ((prompt_tokens - cached_tokens) * prompt_rate)) / 1_000_000 else: prompt_cost = (prompt_tokens * prompt_rate) / 1_000_000 @@ -626,8 +624,7 @@ def _build_request_insights( search_text = (last_user_message or "").lower() opencode_hits = _collect_keyword_hits(search_text, _OPENCODE_COMPLEXITY_HINTS) signal_hits = { - group: _collect_keyword_hits(search_text, keywords) - for group, keywords in _OPENCODE_SIGNAL_GROUPS.items() + group: _collect_keyword_hits(search_text, keywords) for group, keywords in _OPENCODE_SIGNAL_GROUPS.items() } signal_hits = {group: hits for group, hits in signal_hits.items() if hits} signal_groups = list(signal_hits.keys()) @@ -675,17 +672,11 @@ def _build_request_insights( complexity_reasons: list[str] = [] if short_complex: - complexity_reasons.append( - "Brief prompt, but multiple risk-heavy coding signal groups are present." - ) + complexity_reasons.append("Brief prompt, but multiple risk-heavy coding signal groups are present.") if architecture_risk: - complexity_reasons.append( - "Architecture and change-risk signals suggest a higher-cost mistake surface." - ) + complexity_reasons.append("Architecture and change-risk signals suggest a higher-cost mistake surface.") if planning_terms and signal_groups: - complexity_reasons.append( - "Planning or design language appears together with implementation-risk signals." - ) + complexity_reasons.append("Planning or design language appears together with implementation-risk signals.") if has_tools: complexity_reasons.append("Tool usage raises the likelihood of multi-step coding work.") @@ -1162,9 +1153,7 @@ def route_capability_request( requested_image_outputs=requested_outputs or 1, requested_image_side_px=_parse_image_size_max_side(requested_size), requested_image_size=requested_size.strip().lower() if requested_size else "", - requested_image_policy=( - (headers or {}).get("x-faigate-image-policy", "").strip().lower() - ), + requested_image_policy=((headers or {}).get("x-faigate-image-policy", "").strip().lower()), required_capability=capability, cache_preference=(headers or {}).get("x-faigate-cache", "").strip().lower(), model_requested=model_requested.lower().strip(), @@ -1295,9 +1284,7 @@ def _layer_capability_policy( layer="policy", rule_name=rule["name"], confidence=0.95, - reason=( - f"Policy rule '{rule['name']}' matched for required capability '{capability}'" - ), + reason=(f"Policy rule '{rule['name']}' matched for required capability '{capability}'"), details={ "required_capability": capability, "candidate_ranking": ranking, @@ -1343,9 +1330,7 @@ def _match_policy(self, match: dict, ctx: _RoutingContext) -> bool: return matched_any - def _select_policy_provider( - self, select: dict, ctx: _RoutingContext - ) -> tuple[str | None, list[dict[str, Any]]]: + def _select_policy_provider(self, select: dict, ctx: _RoutingContext) -> tuple[str | None, list[dict[str, Any]]]: """Choose a provider from the current config based on a policy rule.""" candidates = [ name @@ -1361,9 +1346,7 @@ def _select_policy_provider( return provider_name, ranking return (ranked[0] if ranked else None), ranking - def _provider_matches_policy( - self, provider: dict, name: str, select: dict, ctx: _RoutingContext - ) -> bool: + def _provider_matches_policy(self, provider: dict, name: str, select: dict, ctx: _RoutingContext) -> bool: """Return whether a provider is eligible for a policy rule.""" capabilities = provider.get("capabilities", {}) allow = select.get("allow_providers", []) @@ -1436,9 +1419,7 @@ def _append(name: str) -> None: return preferred, ranking - def _provider_fits_request_dimensions( - self, name: str, provider: dict, ctx: _RoutingContext | None - ) -> bool: + def _provider_fits_request_dimensions(self, name: str, provider: dict, ctx: _RoutingContext | None) -> bool: """Return whether a provider can satisfy the current token and context shape.""" if ctx is None: return True @@ -1461,17 +1442,9 @@ def _provider_fits_request_dimensions( supported_sizes = image_cfg.get("supported_sizes", []) if max_outputs and ctx.requested_image_outputs > max_outputs: return False - if ( - max_side_px - and ctx.requested_image_side_px - and ctx.requested_image_side_px > max_side_px - ): + if max_side_px and ctx.requested_image_side_px and ctx.requested_image_side_px > max_side_px: return False - if ( - supported_sizes - and ctx.requested_image_size - and ctx.requested_image_size not in supported_sizes - ): + if supported_sizes and ctx.requested_image_size and ctx.requested_image_size not in supported_sizes: return False return True @@ -1592,9 +1565,7 @@ def _provider_dimension_details( elif locality_preference == "cloud": locality_score = 10 if capabilities.get("cloud") else 0 else: - locality_score = ( - 2 if capabilities.get("local") else 1 if capabilities.get("cloud") else 0 - ) + locality_score = 2 if capabilities.get("local") else 1 if capabilities.get("cloud") else 0 lane_score = self._lane_posture_score(lane, routing_posture) route_score = self._route_posture_score(lane, routing_posture) @@ -1641,9 +1612,7 @@ def _provider_dimension_details( image_score += 2 if supported_sizes: - image_supported_size = ( - not ctx.requested_image_size or ctx.requested_image_size in supported_sizes - ) + image_supported_size = not ctx.requested_image_size or ctx.requested_image_size in supported_sizes image_score += 6 if image_supported_size else 0 elif ctx.requested_image_size: image_score += 1 @@ -1726,9 +1695,7 @@ def _provider_dimension_details( "runtime_penalty": adaptation_penalty, "runtime_recovered_recently": bool(runtime_state.get("recovered_recently")), "runtime_recovery_remaining_s": int(runtime_state.get("recovery_remaining_s") or 0), - "runtime_last_recovered_issue_type": str( - runtime_state.get("last_recovered_issue_type") or "" - ), + "runtime_last_recovered_issue_type": str(runtime_state.get("last_recovered_issue_type") or ""), "cache_mode": cache.get("mode", "none"), "locality_preference": locality_preference or "balanced", "routing_posture": routing_posture, @@ -1792,10 +1759,7 @@ def _routing_posture(self, select: dict[str, Any], ctx: _RoutingContext | None = prefer_tiers = {str(item).strip().lower() for item in select.get("prefer_tiers", [])} if not prefer_tiers and ctx is not None: - prefer_tiers = { - str(item).strip().lower() - for item in (ctx.profile_hints or {}).get("prefer_tiers", []) - } + prefer_tiers = {str(item).strip().lower() for item in (ctx.profile_hints or {}).get("prefer_tiers", [])} if "premium" in prefer_tiers: return "quality" @@ -1877,8 +1841,7 @@ def _fallback_relation_details( and primary_lane.get("canonical_model") == candidate_lane.get("canonical_model") ) same_cluster = bool( - primary_lane.get("cluster") - and primary_lane.get("cluster") == candidate_lane.get("cluster") + primary_lane.get("cluster") and primary_lane.get("cluster") == candidate_lane.get("cluster") ) same_benchmark_cluster = bool( primary_lane.get("benchmark_cluster") @@ -1919,13 +1882,9 @@ def _rank_fallback_candidates( ctx: _RoutingContext, ) -> tuple[list[str], list[dict[str, Any]]]: routing_posture = self._routing_posture({}, ctx) - diagnostics = { - name: self._provider_dimension_details(name, ctx, None, routing_posture) - for name in candidates - } + diagnostics = {name: self._provider_dimension_details(name, ctx, None, routing_posture) for name in candidates} relations = { - name: self._fallback_relation_details(primary_provider, name, routing_posture) - for name in candidates + name: self._fallback_relation_details(primary_provider, name, routing_posture) for name in candidates } ranked = sorted( candidates, @@ -1993,13 +1952,10 @@ def _enrich_decision_details( decision.details = details return decision - def _score_provider_candidates( - self, ctx: _RoutingContext, *, limit: int = 3 - ) -> list[dict[str, Any]]: + def _score_provider_candidates(self, ctx: _RoutingContext, *, limit: int = 3) -> list[dict[str, Any]]: routing_posture = self._routing_posture({}, ctx) diagnostics = { - name: self._provider_dimension_details(name, ctx, None, routing_posture) - for name in ctx.providers + name: self._provider_dimension_details(name, ctx, None, routing_posture) for name in ctx.providers } ranked = [ name @@ -2189,9 +2145,7 @@ def _match_heuristic(self, match: dict[str, Any], ctx: _RoutingContext) -> bool: matched, _ = self._evaluate_heuristic_match({"name": "", "match": match}, ctx) return matched - def _evaluate_heuristic_match( - self, rule: dict[str, Any], ctx: _RoutingContext - ) -> tuple[bool, dict[str, Any]]: + def _evaluate_heuristic_match(self, rule: dict[str, Any], ctx: _RoutingContext) -> tuple[bool, dict[str, Any]]: """Evaluate a heuristic match block.""" match = rule.get("match", {}) rule_name = str(rule.get("name") or "") @@ -2204,11 +2158,7 @@ def _evaluate_heuristic_match( hook_hints = dict(getattr(ctx, "hook_hints", {}) or {}) routing_mode = str(hook_hints.get("routing_mode") or "").strip() _EXPLICIT_MODES = {"eco", "premium", "free", "quality", "save", "cheap"} - if ( - ri.get("short_complex") - or hook_hints.get("prefer_providers") - or routing_mode in _EXPLICIT_MODES - ): + if ri.get("short_complex") or hook_hints.get("prefer_providers") or routing_mode in _EXPLICIT_MODES: return False, { "rule_name": rule_name, "fallthrough": True, @@ -2274,9 +2224,7 @@ def _evaluate_heuristic_match( # and would inflate every request to the reasoning tier. search_text = ctx.last_user_message.lower() matched_keywords = [ - str(kw).strip().lower() - for kw in keywords - if _keyword_matches_text(str(kw), search_text) + str(kw).strip().lower() for kw in keywords if _keyword_matches_text(str(kw), search_text) ] hit_count = len(matched_keywords) request_insights = dict(getattr(ctx, "request_insights", {}) or {}) @@ -2287,9 +2235,7 @@ def _evaluate_heuristic_match( if ( ctx.client_profile == "opencode" - and any( - _keyword_matches_text(term, search_text) for term in _OPENCODE_COMPLEXITY_HINTS - ) + and any(_keyword_matches_text(term, search_text) for term in _OPENCODE_COMPLEXITY_HINTS) and any(term in _OPENCODE_COMPLEXITY_RULE_KEYWORDS for term in matched_keywords) ): min_matches = max(1, int(min_matches) - 1) @@ -2299,8 +2245,7 @@ def _evaluate_heuristic_match( ctx.client_profile == "opencode" and complexity_profile == "high" and any( - token in rule_name.lower() - for token in ("complex", "reason", "debug", "review", "plan", "design") + token in rule_name.lower() for token in ("complex", "reason", "debug", "review", "plan", "design") ) and matched_keywords ): @@ -2311,8 +2256,7 @@ def _evaluate_heuristic_match( ctx.client_profile == "opencode" and bool(request_insights.get("short_complex")) and any( - token in rule_name.lower() - for token in ("complex", "reason", "debug", "review", "plan", "design") + token in rule_name.lower() for token in ("complex", "reason", "debug", "review", "plan", "design") ) and matched_keywords ): @@ -2414,8 +2358,7 @@ def _validate_health( if not fb_health.get("healthy", True): continue if required_capabilities and any( - not provider.get("capabilities", {}).get(capability) - for capability in required_capabilities + not provider.get("capabilities", {}).get(capability) for capability in required_capabilities ): continue if not self._provider_fits_request_dimensions(fallback, provider, ctx): diff --git a/faigate/updates.py b/faigate/updates.py index 537553d..f094e75 100644 --- a/faigate/updates.py +++ b/faigate/updates.py @@ -151,9 +151,7 @@ def apply_auto_update_guardrails( if providers_unhealthy > max_unhealthy_providers: result["eligible"] = False - result["blocked_reason"] = ( - f"Too many unhealthy providers ({providers_unhealthy} > {max_unhealthy_providers})" - ) + result["blocked_reason"] = f"Too many unhealthy providers ({providers_unhealthy} > {max_unhealthy_providers})" return result return result @@ -181,9 +179,7 @@ def apply_release_age_guardrail( return result if age_hours < min_release_age_hours: result["eligible"] = False - result["blocked_reason"] = ( - f"Release is too new ({age_hours:.1f}h < {min_release_age_hours}h)" - ) + result["blocked_reason"] = f"Release is too new ({age_hours:.1f}h < {min_release_age_hours}h)" return result @@ -238,9 +234,7 @@ def apply_maintenance_window_guardrail( return result if not hour_allowed: result["eligible"] = False - result["blocked_reason"] = ( - f"Outside maintenance window ({start_hour:02d}:00-{end_hour:02d}:00 {timezone_name})" - ) + result["blocked_reason"] = f"Outside maintenance window ({start_hour:02d}:00-{end_hour:02d}:00 {timezone_name})" return result @@ -310,9 +304,7 @@ def __init__( "enabled": bool((auto_update or {}).get("enabled", False)), "allow_major": bool((auto_update or {}).get("allow_major", False)), "rollout_ring": str((auto_update or {}).get("rollout_ring", "early")), - "require_healthy_providers": bool( - (auto_update or {}).get("require_healthy_providers", True) - ), + "require_healthy_providers": bool((auto_update or {}).get("require_healthy_providers", True)), "max_unhealthy_providers": int((auto_update or {}).get("max_unhealthy_providers", 0)), "min_release_age_hours": int((auto_update or {}).get("min_release_age_hours", 0)), "provider_scope": dict((auto_update or {}).get("provider_scope") or {}), @@ -373,9 +365,7 @@ def _auto_update_status( "allowed_update_types": allowed_types, "allow_major": allow_major, "rollout_ring": rollout_ring, - "require_healthy_providers": bool( - self.auto_update.get("require_healthy_providers", True) - ), + "require_healthy_providers": bool(self.auto_update.get("require_healthy_providers", True)), "max_unhealthy_providers": int(self.auto_update.get("max_unhealthy_providers", 0)), "min_release_age_hours": int(self.auto_update.get("min_release_age_hours", 0)), "provider_scope": dict(self.auto_update.get("provider_scope") or {}), @@ -487,9 +477,7 @@ async def get_status(self, *, force: bool = False) -> UpdateStatus: release_channel=self.release_channel, update_type=update_type, alert_level=alert_level, - recommended_action=( - "Upgrade to the latest release" if update_available else "No action needed" - ), + recommended_action=("Upgrade to the latest release" if update_available else "No action needed"), auto_update=apply_release_age_guardrail( self._auto_update_status( status="ok", diff --git a/faigate/wizard.py b/faigate/wizard.py index 2c61567..3cc699f 100644 --- a/faigate/wizard.py +++ b/faigate/wizard.py @@ -533,14 +533,10 @@ def _clone(value: Any) -> Any: def _load_env_values(env_file: str | Path | None = None) -> dict[str, str]: """Return environment values from one env file, ignoring empty entries.""" - values = { - key: value for key, value in os.environ.items() if isinstance(value, str) and value.strip() - } + values = {key: value for key, value in os.environ.items() if isinstance(value, str) and value.strip()} path = Path(env_file) if env_file is not None else Path.cwd() / ".env" if path.exists(): - values.update( - {k: v for k, v in dotenv_values(path).items() if isinstance(v, str) and v.strip()} - ) + values.update({k: v for k, v in dotenv_values(path).items() if isinstance(v, str) and v.strip()}) return values @@ -775,13 +771,9 @@ def _candidate_row( "official_source_url": catalog_entry.get("official_source_url", ""), "signup_url": (catalog_entry.get("discovery") or {}).get("signup_url", ""), "discovery_url": (catalog_entry.get("discovery") or {}).get("resolved_url", ""), - "discovery_link_source": ( - (catalog_entry.get("discovery") or {}).get("link_source", "official") - ), + "discovery_link_source": ((catalog_entry.get("discovery") or {}).get("link_source", "official")), "discovery_disclosure": ((catalog_entry.get("discovery") or {}).get("disclosure", "")), - "discovery_disclosure_required": bool( - (catalog_entry.get("discovery") or {}).get("disclosure_required", False) - ), + "discovery_disclosure_required": bool((catalog_entry.get("discovery") or {}).get("disclosure_required", False)), "discovery_env_var": ((catalog_entry.get("discovery") or {}).get("operator_env_var", "")), "notes": catalog_entry.get("notes", ""), "canonical_model": lane.get("canonical_model", ""), @@ -847,8 +839,7 @@ def build_interactive_candidate_sections( catalog=catalog, ) for name in detected - if name not in seen - and _PROVIDER_FACTORIES[name]["provider"].get("contract", "generic") == "generic" + if name not in seen and _PROVIDER_FACTORIES[name]["provider"].get("contract", "generic") == "generic" ] extra_ready.sort(key=lambda row: row["provider"]) ready_now.extend(extra_ready) @@ -890,10 +881,7 @@ def render_candidate_cards_text( if row["already_configured"]: badges.append("already in config") lines.append(f"- {row['provider']} ({' · '.join(badges)})") - lines.append( - " " - + f"model: {row['model']} | tier: {row['tier']} | source: {row['provider_type']}" - ) + lines.append(" " + f"model: {row['model']} | tier: {row['tier']} | source: {row['provider_type']}") if row["canonical_model"]: lines.append( " " @@ -919,10 +907,7 @@ def render_candidate_cards_text( lines.append("More options if you add keys") for row in available_with_key: lines.append(f"- {row['provider']} (needs {row['env']})") - lines.append( - " " - + f"model: {row['model']} | tier: {row['tier']} | source: {row['provider_type']}" - ) + lines.append(" " + f"model: {row['model']} | tier: {row['tier']} | source: {row['provider_type']}") if row["canonical_model"]: lines.append( " " @@ -953,9 +938,7 @@ def render_candidate_cards_text( lines.append("") lines.append("Tip: Press Enter in the wizard to use the recommended ready providers.") - lines.append( - "Tip: Use --json or --yaml with --list-candidates when you want the full metadata dump." - ) + lines.append("Tip: Use --json or --yaml with --list-candidates when you want the full metadata dump.") return "\n".join(lines) + "\n" @@ -1009,9 +992,7 @@ def render_known_provider_sources_text( if row["configured"]: status_bits.append("already in config") lines.append(f"- {row['provider']} ({' · '.join(status_bits)})") - lines.append( - " " + f"model: {row['model']} | tier: {row['tier']} | source: {row['provider_type']}" - ) + lines.append(" " + f"model: {row['model']} | tier: {row['tier']} | source: {row['provider_type']}") if row["notes"]: lines.append(" " + f"why: {row['notes']}") lines.append("") @@ -1027,11 +1008,7 @@ def render_current_provider_sources_text( env_values = _load_env_values(env_file) configured = _load_existing_provider_configs(config_path) if not configured: - return ( - "Current provider sources\n\n" - "- none yet\n" - " why: config.yaml does not contain any provider blocks yet.\n" - ) + return "Current provider sources\n\n- none yet\n why: config.yaml does not contain any provider blocks yet.\n" lines = ["Current provider sources", ""] for name in sorted(configured): @@ -1043,11 +1020,7 @@ def render_current_provider_sources_text( match = api_key[2:-1].split(":-", 1)[0].split(":", 1)[0] if match: env_name = match - status = ( - "ready" - if (env_name and env_values.get(env_name)) or (api_key and not env_name) - else "missing key" - ) + status = "ready" if (env_name and env_values.get(env_name)) or (api_key and not env_name) else "missing key" contract = str(provider.get("contract", "generic") or "generic") tier = str(provider.get("tier", "default") or "default") lines.append(f"- {name} ({status} · {contract})") @@ -1072,9 +1045,7 @@ def apply_provider_setup( if config_path is None: raise ValueError("config_path is required") - existing_config = ( - _load_existing_config(config_path) if Path(config_path).exists() else {"providers": {}} - ) + existing_config = _load_existing_config(config_path) if Path(config_path).exists() else {"providers": {}} providers = dict(existing_config.get("providers") or {}) env_updates: dict[str, str] = {} added_providers: list[str] = [] @@ -1120,16 +1091,10 @@ def track_env_var(name: str, value: str) -> None: "canonical_model": str(custom_provider.get("canonical_model", name) or name), "route_type": "direct", "cluster": str(custom_provider.get("cluster", "custom") or "custom"), - "benchmark_cluster": str( - custom_provider.get("benchmark_cluster", "custom") or "custom" - ), + "benchmark_cluster": str(custom_provider.get("benchmark_cluster", "custom") or "custom"), "quality_tier": str(custom_provider.get("quality_tier", "custom") or "custom"), - "reasoning_strength": str( - custom_provider.get("reasoning_strength", "custom") or "custom" - ), - "context_strength": str( - custom_provider.get("context_strength", "custom") or "custom" - ), + "reasoning_strength": str(custom_provider.get("reasoning_strength", "custom") or "custom"), + "context_strength": str(custom_provider.get("context_strength", "custom") or "custom"), "tool_strength": str(custom_provider.get("tool_strength", "custom") or "custom"), "same_model_group": str(custom_provider.get("same_model_group", name) or name), "degrade_to": list(custom_provider.get("degrade_to", []) or []), @@ -1167,9 +1132,7 @@ def track_env_var(name: str, value: str) -> None: "cluster": "local-worker", "benchmark_cluster": "local-worker", "quality_tier": "local", - "reasoning_strength": str( - local_worker.get("reasoning_strength", "custom") or "custom" - ), + "reasoning_strength": str(local_worker.get("reasoning_strength", "custom") or "custom"), "context_strength": str(local_worker.get("context_strength", "custom") or "custom"), "tool_strength": str(local_worker.get("tool_strength", "custom") or "custom"), "same_model_group": str(local_worker.get("same_model_group", name) or name), @@ -1302,9 +1265,7 @@ def build_provider_probe_report( status_reason = str(request_readiness.get("reason") or "route is not request-ready") elif request_readiness and bool(request_readiness.get("ready")): status = str(request_readiness.get("status") or "ready") - status_reason = str( - request_readiness.get("reason") or "route looks request-ready from runtime state" - ) + status_reason = str(request_readiness.get("reason") or "route looks request-ready from runtime state") ready_count += 1 elif health_payload is None: status = "configured" @@ -1345,9 +1306,7 @@ def build_provider_probe_report( next_action = operator_hint or _default_probe_action_hint( action_group=action_group, provider_name=name, - family=str( - (provider.get("lane") or {}).get("family") or lane_binding.get("family") or "" - ), + family=str((provider.get("lane") or {}).get("family") or lane_binding.get("family") or ""), ) lane = dict(lane_binding or {}) lane.update(dict(provider.get("lane") or {})) @@ -1405,14 +1364,10 @@ def build_provider_probe_report( or "" ), "billing_mode": str( - request_readiness.get("billing_mode") - or (provider.get("transport") or {}).get("billing_mode") - or "" + request_readiness.get("billing_mode") or (provider.get("transport") or {}).get("billing_mode") or "" ), "quota_group": str( - request_readiness.get("quota_group") - or (provider.get("transport") or {}).get("quota_group") - or "" + request_readiness.get("quota_group") or (provider.get("transport") or {}).get("quota_group") or "" ), "quota_isolated": bool( request_readiness.get("quota_isolated") @@ -1447,12 +1402,8 @@ def build_provider_probe_report( recommendation = _pick_probe_recommendation(row=row, rows=rows) row["recommended_route"] = recommendation["provider"] row["recommended_strategy"] = recommendation["strategy"] - recommendation_counts[recommendation["strategy"]] = ( - recommendation_counts.get(recommendation["strategy"], 0) + 1 - ) - row["family_summary"] = dict( - family_summaries.get(str(row.get("lane_family") or "unclassified")) or {} - ) + recommendation_counts[recommendation["strategy"]] = recommendation_counts.get(recommendation["strategy"], 0) + 1 + row["family_summary"] = dict(family_summaries.get(str(row.get("lane_family") or "unclassified")) or {}) add_recommendations = get_route_add_recommendations( configured_provider_names=configured_names, canonical_model=str(row.get("canonical_model") or ""), @@ -1466,9 +1417,7 @@ def build_provider_probe_report( row["recommended_add_strategy"] = ( str(add_recommendations[0].get("strategy") or "") if add_recommendations else "none" ) - add_counts[row["recommended_add_strategy"]] = ( - add_counts.get(row["recommended_add_strategy"], 0) + 1 - ) + add_counts[row["recommended_add_strategy"]] = add_counts.get(row["recommended_add_strategy"], 0) + 1 row["next_action"] = _combine_probe_next_action( current_hint=str(row.get("next_action") or ""), action_group=str(row.get("action_group") or "inspect"), @@ -1481,12 +1430,8 @@ def build_provider_probe_report( refresh_guidance = _refresh_guidance_from_rows(rows) refresh_counts = { - "refresh-now": sum( - 1 for item in refresh_guidance if str(item.get("action") or "") == "refresh-now" - ), - "review-soon": sum( - 1 for item in refresh_guidance if str(item.get("action") or "") == "review-soon" - ), + "refresh-now": sum(1 for item in refresh_guidance if str(item.get("action") or "") == "refresh-now"), + "review-soon": sum(1 for item in refresh_guidance if str(item.get("action") or "") == "review-soon"), } return { @@ -1525,62 +1470,43 @@ def _provider_probe_priority_next(report: dict[str, Any]) -> dict[str, str]: if int(actions.get("fix-now") or 0) > 0: return { "path": "API Keys or Provider Setup", - "why": ( - "at least one configured route still needs credentials, " - "endpoint fixes, or model cleanup." - ), + "why": ("at least one configured route still needs credentials, endpoint fixes, or model cleanup."), } if int(summary.get("mirror_gaps") or 0) > 0: return { "path": "Provider Setup -> Guided Route Additions", - "why": ( - "known same-lane or cluster mirrors are still missing from " - "the configured route inventory." - ), + "why": ("known same-lane or cluster mirrors are still missing from the configured route inventory."), } if int(refresh_actions.get("refresh-now") or 0) > 0: return { "path": "Dashboard -> Provider detail", "why": ( - "one or more active routes rely on stale benchmark and cost " - "assumptions that should be refreshed now." + "one or more active routes rely on stale benchmark and cost assumptions that should be refreshed now." ), } if int(actions.get("hold") or 0) > 0 or int(actions.get("watch") or 0) > 0: return { "path": "Doctor or Dashboard -> Provider detail", "why": ( - "runtime cooldown or recovery pressure is active and should be " - "checked before routing heavier traffic." + "runtime cooldown or recovery pressure is active and should be checked before routing heavier traffic." ), } if int(summary.get("ready") or 0) > 0: return { "path": "Client Scenarios or Client Quickstarts", - "why": ( - "the provider layer looks ready enough to move on to client " - "defaults and real client wiring." - ), + "why": ("the provider layer looks ready enough to move on to client defaults and real client wiring."), } return { "path": "Validate", - "why": ( - "the probe ran, but the next operator action is still to tighten " - "the current config and env state." - ), + "why": ("the probe ran, but the next operator action is still to tighten the current config and env state."), } def render_provider_probe_text(report: dict[str, Any]) -> str: lines = ["Provider probe", ""] summary = report.get("summary") or {} - lines.append( - f"Configured: {summary.get('configured', 0)} | Ready now: {summary.get('ready', 0)}" - ) - lines.append( - "Live health: " - + ("available" if summary.get("health_live") else "not available; config/env only") - ) + lines.append(f"Configured: {summary.get('configured', 0)} | Ready now: {summary.get('ready', 0)}") + lines.append("Live health: " + ("available" if summary.get("health_live") else "not available; config/env only")) if summary.get("live_probe"): lines.append("Live probe: enabled (using transport-specific shallow request probes)") actions = summary.get("actions") or {} @@ -1654,9 +1580,7 @@ def render_provider_probe_text(report: dict[str, Any]) -> str: lines.append("") for row in report.get("providers", []): lines.append(f"- {row['provider']} ({row['status']})") - lines.append( - " " + f"model: {row['model']} | tier: {row['tier']} | contract: {row['contract']}" - ) + lines.append(" " + f"model: {row['model']} | tier: {row['tier']} | contract: {row['contract']}") if row.get("lane_family") or row.get("action_group"): family = row.get("lane_family") or "unclassified" action = row.get("action_group") or "inspect" @@ -1677,11 +1601,7 @@ def render_provider_probe_text(report: dict[str, Any]) -> str: " " + "transport: " + f"{row['transport_profile']} | {row.get('transport_compatibility') or 'n/a'}" - + ( - f" | confidence: {row.get('transport_confidence')}" - if row.get("transport_confidence") - else "" - ) + + (f" | confidence: {row.get('transport_confidence')}" if row.get("transport_confidence") else "") + (f" | strategy: {row.get('probe_strategy')}" if row.get("probe_strategy") else "") ) quota_summary, quota_note = _describe_quota_domain( @@ -1712,9 +1632,7 @@ def render_provider_probe_text(report: dict[str, Any]) -> str: + f"({row.get('recommended_strategy') or 'fallback'})" ) if row.get("known_mirror_gaps"): - lines.append( - " " + "known mirrors not configured: " + ", ".join(row["known_mirror_gaps"][:3]) - ) + lines.append(" " + "known mirrors not configured: " + ", ".join(row["known_mirror_gaps"][:3])) if row.get("recommended_add_provider"): lines.append( " " @@ -1749,13 +1667,8 @@ def render_provider_probe_text(report: dict[str, Any]) -> str: if item.get("reason"): lines.append(" " + f"why: {item['reason']}") lines.append("") - lines.append( - "Tip: Ready means config, env, and the current /health " - "request-readiness payload all line up." - ) - lines.append( - "Tip: Missing-key or model-unavailable states should be fixed before client rollout." - ) + lines.append("Tip: Ready means config, env, and the current /health request-readiness payload all line up.") + lines.append("Tip: Missing-key or model-unavailable states should be fixed before client rollout.") return "\n".join(lines) + "\n" @@ -1776,17 +1689,9 @@ def _classify_probe_action( "model-unavailable", }: return "fix-now" - if ( - runtime_cooldown_active - or runtime_window_state == "cooldown" - or status in {"quota-exhausted", "rate-limited"} - ): + if runtime_cooldown_active or runtime_window_state == "cooldown" or status in {"quota-exhausted", "rate-limited"}: return "hold" - if ( - runtime_recovered_recently - or runtime_window_state == "degraded" - or status == "ready-recovered" - ): + if runtime_recovered_recently or runtime_window_state == "degraded" or status == "ready-recovered": return "watch" if ready or status in {"ready", "ready-verified", "ready-compat"}: return "route" @@ -1796,16 +1701,11 @@ def _classify_probe_action( def _default_probe_action_hint(*, action_group: str, provider_name: str, family: str) -> str: family_label = family or "this route" if action_group == "fix-now": - return ( - f"fix credentials, model mapping, or endpoint settings before routing {provider_name}" - ) + return f"fix credentials, model mapping, or endpoint settings before routing {provider_name}" if action_group == "hold": return f"hold {provider_name} out of primary traffic until the cooldown pressure clears" if action_group == "watch": - return ( - f"keep {provider_name} in light traffic while the {family_label} " - "recovery window stays open" - ) + return f"keep {provider_name} in light traffic while the {family_label} recovery window stays open" if action_group == "route": return f"route can carry live traffic for the {family_label} lane" return f"inspect runtime hints for {provider_name} before making it a primary lane" @@ -1844,9 +1744,7 @@ def _build_probe_family_summaries(rows: list[dict[str, Any]]) -> dict[str, dict[ ) -def _pick_probe_recommendation( - *, row: dict[str, Any], rows: list[dict[str, Any]] -) -> dict[str, str]: +def _pick_probe_recommendation(*, row: dict[str, Any], rows: list[dict[str, Any]]) -> dict[str, str]: action_group = str(row.get("action_group") or "inspect") if action_group == "route": return {"provider": "", "strategy": "none"} @@ -1881,9 +1779,7 @@ def candidate_pool(*, matcher) -> list[dict[str, Any]]: degrade_to = [str(item) for item in (row.get("degrade_to") or []) if str(item)] if degrade_to: - candidates = candidate_pool( - matcher=lambda candidate: str(candidate.get("canonical_model") or "") in degrade_to - ) + candidates = candidate_pool(matcher=lambda candidate: str(candidate.get("canonical_model") or "") in degrade_to) if candidates: return { "provider": str(candidates[0].get("provider") or ""), @@ -1892,9 +1788,7 @@ def candidate_pool(*, matcher) -> list[dict[str, Any]]: family = str(row.get("lane_family") or "") if family: - candidates = candidate_pool( - matcher=lambda candidate: str(candidate.get("lane_family") or "") == family - ) + candidates = candidate_pool(matcher=lambda candidate: str(candidate.get("lane_family") or "") == family) if candidates: return { "provider": str(candidates[0].get("provider") or ""), @@ -1932,30 +1826,20 @@ def _combine_probe_next_action( f"for {traffic_label} traffic meanwhile" ) if action_group == "watch": - return ( - f"{current_hint}; favor {preferred_route} as the {strategy_label} " - f"for steady {traffic_label} traffic" - ) + return f"{current_hint}; favor {preferred_route} as the {strategy_label} for steady {traffic_label} traffic" if action_group in {"fix-now", "inspect"}: return ( f"{current_hint}; route {traffic_label} traffic through {preferred_route} " f"as the {strategy_label} until fixed" ) if add_provider and action_group in {"hold", "watch", "fix-now", "inspect"}: - return ( - f"{current_hint}; add {add_provider} as a {add_strategy_label} " - f"for {traffic_label} traffic" - ) + return f"{current_hint}; add {add_provider} as a {add_strategy_label} for {traffic_label} traffic" return current_hint def _scenario_provider_selection(*, purpose: str, client: str) -> list[str]: preferred = _preferred_provider_set(list(_PROVIDER_FACTORIES), purpose=purpose, client=client) - return [ - name - for name in preferred - if _PROVIDER_FACTORIES[name]["provider"].get("contract", "generic") == "generic" - ] + return [name for name in preferred if _PROVIDER_FACTORIES[name]["provider"].get("contract", "generic") == "generic"] def _scenario_provider_selection_for_spec(spec: dict[str, Any]) -> list[str]: @@ -2039,8 +1923,7 @@ def _provider_route_registry_summary(provider_name: str) -> dict[str, Any]: mirror_providers = [ str(route.get("provider_name") or "") for route in routes - if str(route.get("provider_name") or "") - and str(route.get("provider_name") or "") != provider_name + if str(route.get("provider_name") or "") and str(route.get("provider_name") or "") != provider_name ] return { "canonical_model": canonical_model, @@ -2099,9 +1982,7 @@ def _scenario_family_coverage(provider_names: list[str]) -> list[str]: if "anthropic-claude" in provider_set: coverage.append("Anthropic: quality lane active; workhorse/fast lane not configured yet") if "openai-gpt4o" in provider_set: - coverage.append( - "OpenAI: balanced lane active; faster or cheaper family split not configured yet" - ) + coverage.append("OpenAI: balanced lane active; faster or cheaper family split not configured yet") if "deepseek-reasoner" in provider_set and "deepseek-chat" in provider_set: coverage.append("DeepSeek: reasoning + workhorse lanes active") if {"kilo-opus", "kilo-sonnet", "kilocode"} <= provider_set: @@ -2352,10 +2233,7 @@ def render_route_add_setup_plan_text(plan: dict[str, Any]) -> str: [ "Priority next", f"- path: Review remaining env inputs ({len(manual)} route addition(s) blocked)", - ( - "- why : the recommended mirrors are known, but at least " - "one required env value is still missing." - ), + ("- why : the recommended mirrors are known, but at least one required env value is still missing."), "", ] ) @@ -2369,8 +2247,7 @@ def _render_item(item: dict[str, Any]) -> None: if item.get("base_url_env") and not item.get("base_url_present"): status_bits.append(f"optional {item['base_url_env']}") lines.append( - f"- {item['setup_provider_name']} " - + f"({' · '.join(status_bits) if status_bits else 'ready to add'})" + f"- {item['setup_provider_name']} " + f"({' · '.join(status_bits) if status_bits else 'ready to add'})" ) lines.append( " " @@ -2396,9 +2273,7 @@ def _render_item(item: dict[str, Any]) -> None: lines.append(f"Tip: {len(auto_apply)} route addition(s) can be applied") lines.append(" in one pass with the current env.") if manual: - lines.append( - f"Tip: {len(manual)} route addition(s) still need input before they can be written." - ) + lines.append(f"Tip: {len(manual)} route addition(s) still need input before they can be written.") refresh_guidance = list(plan.get("refresh_guidance") or []) if refresh_guidance: lines.extend(["", "Refresh guidance"]) @@ -2515,10 +2390,7 @@ def render_client_scenarios_text( for coverage_line in item["family_coverage"]: lines.append(" " + f"family coverage: {coverage_line}") lines.append("") - lines.append( - "Tip: Apply one scenario when you want a client-specific default " - "without hand-editing profile modes." - ) + lines.append("Tip: Apply one scenario when you want a client-specific default without hand-editing profile modes.") lines.append( "Tip: Scenario lanes describe the current configured provider inventory. " "If you want separate Opus / Sonnet / Haiku or similar family lanes, " @@ -2537,9 +2409,7 @@ def apply_client_scenario( raise ValueError(f"Unsupported client scenario '{scenario_id}'") spec = _CLIENT_SCENARIOS[scenario_id] available = detect_wizard_providers(env_file=env_file) - selected = [ - name for name in _scenario_provider_selection_for_spec(spec) if name in set(available) - ] + selected = [name for name in _scenario_provider_selection_for_spec(spec) if name in set(available)] suggestion = build_initial_config( env_file=env_file, purpose=spec["purpose"], @@ -2553,9 +2423,7 @@ def apply_client_scenario( profiles[spec["client"]] = profile merged_provider_names = set((merged.get("providers") or {}).keys()) active_provider_names = [ - name - for name in _scenario_provider_selection_for_spec(spec) - if name in merged_provider_names + name for name in _scenario_provider_selection_for_spec(spec) if name in merged_provider_names ] route_add_setup_plan = build_route_add_setup_plan( config_path=config_path, @@ -2616,17 +2484,12 @@ def render_client_scenario_summary(payload: dict[str, Any]) -> str: lines.append("- add providers: " + ", ".join(summary["added_providers"])) if summary.get("replaced_models"): for item in summary["replaced_models"]: - lines.append( - "- replace model: " - + f"{item['provider']} {item['from_model']} -> {item['to_model']}" - ) + lines.append("- replace model: " + f"{item['provider']} {item['from_model']} -> {item['to_model']}") if summary.get("fallback_additions"): lines.append("- fallback additions: " + ", ".join(summary["fallback_additions"])) if summary.get("changed_profile_modes"): for item in summary["changed_profile_modes"]: - lines.append( - "- profile mode: " + f"{item['profile']} {item['from_mode']} -> {item['to_mode']}" - ) + lines.append("- profile mode: " + f"{item['profile']} {item['from_mode']} -> {item['to_mode']}") if lines[-1] == "Change preview": lines.append("- no config changes beyond confirming the current scenario") if routing_rationale: @@ -2639,14 +2502,12 @@ def render_client_scenario_summary(payload: dict[str, Any]) -> str: if actionable_additions: priority_path = "Provider Setup -> Guided Route Additions" priority_reason = ( - "recommended same-lane or cluster mirrors are still missing for " - "the scenario you just selected." + "recommended same-lane or cluster mirrors are still missing for the scenario you just selected." ) elif refresh_guidance_lines: priority_path = "Provider Probe or Dashboard -> Provider detail" priority_reason = ( - "the scenario is usable, but route freshness should be reviewed " - "before heavier traffic leans on it." + "the scenario is usable, but route freshness should be reviewed before heavier traffic leans on it." ) lines.extend(["", "Priority next", f"- path: {priority_path}", f"- why : {priority_reason}"]) if refresh_guidance_lines: @@ -2658,9 +2519,7 @@ def render_client_scenario_summary(payload: dict[str, Any]) -> str: for item in actionable_additions[:3]: setup_provider = str(item.get("setup_provider_name") or item.get("provider_name") or "") lines.append( - "- add route: " - + f"{setup_provider} for {item.get('provider_name')} " - + f"({item.get('strategy')})" + "- add route: " + f"{setup_provider} for {item.get('provider_name')} " + f"({item.get('strategy')})" ) lines.append("- next path : Provider Setup -> Guided Route Additions") if route_additions: @@ -2933,9 +2792,7 @@ def apply_update_suggestions( merged = merge_initial_config(config_path=config_path, suggestion=suggestion) if "recommended_mode_changes" in groups: - profile_names = selected_profiles or [ - item["profile"] for item in suggestions["recommended_mode_changes"] - ] + profile_names = selected_profiles or [item["profile"] for item in suggestions["recommended_mode_changes"]] profiles = merged.setdefault("client_profiles", {}).setdefault("profiles", {}) for item in suggestions["recommended_mode_changes"]: if item["profile"] not in profile_names: @@ -2968,9 +2825,7 @@ def build_config_change_summary( from_model = str(current.get("model", "") or "").strip() to_model = str(provider.get("model", "") or "").strip() if from_model and to_model and from_model != to_model: - replaced_models.append( - {"provider": name, "from_model": from_model, "to_model": to_model} - ) + replaced_models.append({"provider": name, "from_model": from_model, "to_model": to_model}) existing_profiles = ((existing.get("client_profiles") or {}).get("profiles")) or {} updated_profiles = ((updated_config.get("client_profiles") or {}).get("profiles")) or {} @@ -2982,9 +2837,7 @@ def build_config_change_summary( from_mode = str(current.get("routing_mode", "") or "").strip() to_mode = str(profile.get("routing_mode", "") or "").strip() if from_mode and to_mode and from_mode != to_mode: - changed_profile_modes.append( - {"profile": name, "from_mode": from_mode, "to_mode": to_mode} - ) + changed_profile_modes.append({"profile": name, "from_mode": from_mode, "to_mode": to_mode}) existing_fallback = list(existing.get("fallback_chain", []) or []) updated_fallback = list(updated_config.get("fallback_chain", []) or []) @@ -3034,10 +2887,7 @@ def _resolve_selected_providers( if selected_providers: unknown = [name for name in selected_providers if name not in available] if unknown: - raise ValueError( - "Unsupported or unavailable wizard provider selection: " - + ", ".join(sorted(unknown)) - ) + raise ValueError("Unsupported or unavailable wizard provider selection: " + ", ".join(sorted(unknown))) return list(dict.fromkeys(selected_providers)) return _preferred_provider_set(available, purpose=purpose, client=client) @@ -3067,9 +2917,7 @@ def _provider_payload_with_lane(name: str) -> dict[str, Any]: def _premium_targets(available: list[str]) -> list[str]: return [ - name - for name in ("anthropic-claude", "openai-gpt4o", "deepseek-reasoner", "gemini-flash") - if name in available + name for name in ("anthropic-claude", "openai-gpt4o", "deepseek-reasoner", "gemini-flash") if name in available ] @@ -3187,15 +3035,12 @@ def merge_initial_config( merged["providers"].update(_clone(_mapping_or_empty(suggestion.get("providers")))) merged["fallback_chain"] = _unique_preserve_order( - list(_list_or_empty(merged.get("fallback_chain"))) - + list(_list_or_empty(suggestion.get("fallback_chain"))) + list(_list_or_empty(merged.get("fallback_chain"))) + list(_list_or_empty(suggestion.get("fallback_chain"))) ) existing_modes = _mapping_or_empty(merged.get("routing_modes")) suggested_modes = _mapping_or_empty(suggestion.get("routing_modes")) - existing_modes["enabled"] = bool( - existing_modes.get("enabled", suggested_modes.get("enabled", True)) - ) + existing_modes["enabled"] = bool(existing_modes.get("enabled", suggested_modes.get("enabled", True))) existing_modes["default"] = existing_modes.get( "default", suggested_modes.get("default", "auto"), @@ -3208,9 +3053,7 @@ def merge_initial_config( existing_shortcuts = _mapping_or_empty(merged.get("model_shortcuts")) suggested_shortcuts = _mapping_or_empty(suggestion.get("model_shortcuts")) - existing_shortcuts["enabled"] = bool( - existing_shortcuts.get("enabled", suggested_shortcuts.get("enabled", True)) - ) + existing_shortcuts["enabled"] = bool(existing_shortcuts.get("enabled", suggested_shortcuts.get("enabled", True))) existing_shortcuts["shortcuts"] = _merge_mapping( _mapping_or_empty(existing_shortcuts.get("shortcuts")), _mapping_or_empty(suggested_shortcuts.get("shortcuts")), @@ -3219,15 +3062,10 @@ def merge_initial_config( existing_profiles = _mapping_or_empty(merged.get("client_profiles")) suggested_profiles = _mapping_or_empty(suggestion.get("client_profiles")) - existing_profiles["enabled"] = bool( - existing_profiles.get("enabled", suggested_profiles.get("enabled", True)) - ) - existing_profiles["default"] = existing_profiles.get( - "default", suggested_profiles.get("default", "generic") - ) + existing_profiles["enabled"] = bool(existing_profiles.get("enabled", suggested_profiles.get("enabled", True))) + existing_profiles["default"] = existing_profiles.get("default", suggested_profiles.get("default", "generic")) existing_profiles["presets"] = _unique_preserve_order( - list(_list_or_empty(existing_profiles.get("presets"))) - + list(_list_or_empty(suggested_profiles.get("presets"))) + list(_list_or_empty(existing_profiles.get("presets"))) + list(_list_or_empty(suggested_profiles.get("presets"))) ) profiles = dict(_mapping_or_empty(existing_profiles.get("profiles"))) for name, profile in _mapping_or_empty(suggested_profiles.get("profiles")).items(): @@ -3237,9 +3075,7 @@ def merge_initial_config( ) existing_profiles["profiles"] = profiles rules_by_profile = { - rule.get("profile"): rule - for rule in _list_or_empty(existing_profiles.get("rules")) - if isinstance(rule, dict) + rule.get("profile"): rule for rule in _list_or_empty(existing_profiles.get("rules")) if isinstance(rule, dict) } for rule in _list_or_empty(suggested_profiles.get("rules")): if not isinstance(rule, dict): @@ -3250,13 +3086,9 @@ def merge_initial_config( existing_policies = _mapping_or_empty(merged.get("routing_policies")) suggested_policies = _mapping_or_empty(suggestion.get("routing_policies")) - existing_policies["enabled"] = bool( - existing_policies.get("enabled", suggested_policies.get("enabled", True)) - ) + existing_policies["enabled"] = bool(existing_policies.get("enabled", suggested_policies.get("enabled", True))) rules_by_name = { - rule.get("name"): rule - for rule in _list_or_empty(existing_policies.get("rules")) - if isinstance(rule, dict) + rule.get("name"): rule for rule in _list_or_empty(existing_policies.get("rules")) if isinstance(rule, dict) } for rule in _list_or_empty(suggested_policies.get("rules")): if not isinstance(rule, dict): @@ -3267,15 +3099,10 @@ def merge_initial_config( existing_hooks = _mapping_or_empty(merged.get("request_hooks")) suggested_hooks = _mapping_or_empty(suggestion.get("request_hooks")) - existing_hooks["enabled"] = bool( - existing_hooks.get("enabled", suggested_hooks.get("enabled", True)) - ) - existing_hooks["on_error"] = existing_hooks.get( - "on_error", suggested_hooks.get("on_error", "continue") - ) + existing_hooks["enabled"] = bool(existing_hooks.get("enabled", suggested_hooks.get("enabled", True))) + existing_hooks["on_error"] = existing_hooks.get("on_error", suggested_hooks.get("on_error", "continue")) existing_hooks["hooks"] = _unique_preserve_order( - list(_list_or_empty(existing_hooks.get("hooks"))) - + list(_list_or_empty(suggested_hooks.get("hooks"))) + list(_list_or_empty(existing_hooks.get("hooks"))) + list(_list_or_empty(suggested_hooks.get("hooks"))) ) merged["request_hooks"] = existing_hooks return merged diff --git a/hooks/adapters/grok_api_adapter.py b/hooks/adapters/grok_api_adapter.py index e609755..259f370 100644 --- a/hooks/adapters/grok_api_adapter.py +++ b/hooks/adapters/grok_api_adapter.py @@ -50,11 +50,7 @@ # ── Grok-Api path resolution ──────────────────────────────────────────────── -_GROK_API_DIR = ( - pathlib.Path(os.environ.get("GROK_API_DIR", "~/.config/faigate/grok-api")) - .expanduser() - .resolve() -) +_GROK_API_DIR = pathlib.Path(os.environ.get("GROK_API_DIR", "~/.config/faigate/grok-api")).expanduser().resolve() if str(_GROK_API_DIR) not in sys.path: sys.path.insert(0, str(_GROK_API_DIR)) diff --git a/hooks/community/claude_code_router.py b/hooks/community/claude_code_router.py index 1bd6a2f..b4ffa2b 100644 --- a/hooks/community/claude_code_router.py +++ b/hooks/community/claude_code_router.py @@ -84,12 +84,7 @@ def _resolve_profile(metadata: dict[str, Any], headers: dict[str, str]) -> str: def _normalized_source(metadata: dict[str, Any], headers: dict[str, str]) -> str: return ( - str( - metadata.get("source") - or headers.get("x-faigate-client") - or headers.get("anthropic-client") - or "" - ) + str(metadata.get("source") or headers.get("x-faigate-client") or headers.get("anthropic-client") or "") .strip() .lower() ) @@ -97,12 +92,7 @@ def _normalized_source(metadata: dict[str, Any], headers: dict[str, str]) -> str def _normalized_surface(metadata: dict[str, Any], headers: dict[str, str]) -> str: return ( - str( - metadata.get("bridge_surface") - or metadata.get("surface") - or headers.get("x-faigate-surface") - or "" - ) + str(metadata.get("bridge_surface") or metadata.get("surface") or headers.get("x-faigate-surface") or "") .strip() .lower() ) diff --git a/hooks/grok-wrapper.py b/hooks/grok-wrapper.py index e630db0..c01b05e 100644 --- a/hooks/grok-wrapper.py +++ b/hooks/grok-wrapper.py @@ -149,8 +149,7 @@ def register(register_fn, register_provider_fn=None) -> None: # noqa: ANN001 "freshness_status": "fresh", "review_age_days": 0, "freshness_hint": ( - "Virtual provider via grok_api_adapter — no XAI API key required. " - f"Adapter at {adapter_url}" + f"Virtual provider via grok_api_adapter — no XAI API key required. Adapter at {adapter_url}" ), }, }, diff --git a/pyproject.toml b/pyproject.toml index 1f72887..e4187c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ faigate = [ ] [tool.ruff] -line-length = 100 +line-length = 120 target-version = "py312" [tool.ruff.lint] diff --git a/tests/test_api_hardening.py b/tests/test_api_hardening.py index ff72eac..f0c1d0b 100644 --- a/tests/test_api_hardening.py +++ b/tests/test_api_hardening.py @@ -376,9 +376,7 @@ def test_route_preview_sanitizes_header_values(api_client): assert body["client_tag"] == "cli-agent-wi" -def test_route_preview_includes_route_summary_for_opencode_complexity( - api_client, monkeypatch, tmp_path -): +def test_route_preview_includes_route_summary_for_opencode_complexity(api_client, monkeypatch, tmp_path): cfg = load_config( _write_config( tmp_path, @@ -518,14 +516,8 @@ def test_route_preview_includes_route_summary_for_opencode_complexity( assert body["route_summary"]["selected"]["estimated_request_cost_usd"] > 0 assert body["route_summary"]["selected"]["freshness_status"] assert any("Opencode complexity bias" in item for item in body["route_summary"]["why_selected"]) - assert any( - "Benchmark fit favored reasoning-coding" in item - for item in body["route_summary"]["why_selected"] - ) - assert any( - "Benchmark/cost assumptions are currently" in item - for item in body["route_summary"]["why_selected"] - ) + assert any("Benchmark fit favored reasoning-coding" in item for item in body["route_summary"]["why_selected"]) + assert any("Benchmark/cost assumptions are currently" in item for item in body["route_summary"]["why_selected"]) assert body["route_summary"]["alternatives"][0]["provider"] == "gemini-flash-lite" assert body["route_summary"]["alternatives"][0]["estimated_request_cost_usd"] > 0 assert body["route_summary"]["alternatives"][0]["why_not_selected"] @@ -675,9 +667,7 @@ def test_route_preview_explains_kilo_frontier_lane_choice(api_client, monkeypatc "messages": [ { "role": "user", - "content": ( - "Design a rollback-safe architecture plan for this refactor under load." - ), + "content": ("Design a rollback-safe architecture plan for this refactor under load."), } ], }, @@ -688,10 +678,7 @@ def test_route_preview_explains_kilo_frontier_lane_choice(api_client, monkeypatc assert body["decision"]["provider"] == "kilo-opus" assert body["route_summary"]["selected"]["canonical_model"] == "anthropic/opus-4.6" assert body["route_summary"]["selected"]["kilo_mode"] == "frontier-premium" - assert any( - "Kilo frontier fit favored frontier-premium" in item - for item in body["route_summary"]["why_selected"] - ) + assert any("Kilo frontier fit favored frontier-premium" in item for item in body["route_summary"]["why_selected"]) assert any("premium Kilo Opus lane" in item for item in body["route_summary"]["why_selected"]) assert body["route_summary"]["alternatives"] assert any( diff --git a/tests/test_config.py b/tests/test_config.py index da1beb2..7ac9a6f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -276,9 +276,7 @@ def test_provider_transport_metadata_is_normalized(tmp_path): assert cfg.providers["cloud-default"]["transport"]["auth_mode"] == "bearer" assert cfg.providers["cloud-default"]["transport"]["profile"] == "openai-compatible" assert cfg.providers["cloud-default"]["transport"]["compatibility"] == "native" - assert ( - cfg.providers["cloud-default"]["transport"]["probe_payload_kind"] == "openai-chat-minimal" - ) + assert cfg.providers["cloud-default"]["transport"]["probe_payload_kind"] == "openai-chat-minimal" assert cfg.providers["cloud-default"]["transport"]["probe_payload_text"] == "ping" assert cfg.providers["cloud-default"]["transport"]["probe_payload_max_tokens"] == 1 assert cfg.providers["cloud-default"]["transport"]["billing_mode"] == "" @@ -680,9 +678,7 @@ def test_legacy_provider_reference_aliases_load_for_upgrade_configs(tmp_path): cfg = load_config(path) assert cfg.fallback_chain == ["gemini-pro-high"] - assert cfg.routing_modes["modes"]["premium"]["select"]["prefer_providers"] == [ - "gemini-pro-high" - ] + assert cfg.routing_modes["modes"]["premium"]["select"]["prefer_providers"] == ["gemini-pro-high"] assert cfg.model_shortcuts["shortcuts"]["default-pro"]["target"] == "gemini-pro-high" assert cfg.anthropic_bridge["model_aliases"]["claude-opus-4-6"] == "gemini-pro-high" assert cfg.auto_update["provider_scope"]["allow_providers"] == ["gemini-pro-high"] diff --git a/tests/test_dashboard_provider_catalog.py b/tests/test_dashboard_provider_catalog.py index 39fa07e..83b2e6e 100644 --- a/tests/test_dashboard_provider_catalog.py +++ b/tests/test_dashboard_provider_catalog.py @@ -37,9 +37,7 @@ def test_dashboard_surfaces_provider_catalog_alerts(tmp_path): "field_name": "input_cost", "old_value": "5.0", "new_value": "6.0", - "message": ( - "kilo: input_cost for 'anthropic/claude-opus-4.6' changed from 5.0 to 6.0." - ), + "message": ("kilo: input_cost for 'anthropic/claude-opus-4.6' changed from 5.0 to 6.0."), } ] ) diff --git a/tests/test_menu_helpers.py b/tests/test_menu_helpers.py index 01e3c6b..a1a1493 100644 --- a/tests/test_menu_helpers.py +++ b/tests/test_menu_helpers.py @@ -447,12 +447,9 @@ def test_faigate_doctor_reports_request_readiness_when_health_is_live(tmp_path: assert "request-ready: deepseek-chat -> ready" in result.stdout assert "request readiness summary: 1/1 provider routes look request-ready" in result.stdout assert ( - "request-ready action: deepseek-chat -> route " - "[deepseek | deepseek/chat | balanced-workhorse | freshness=" + "request-ready action: deepseek-chat -> route [deepseek | deepseek/chat | balanced-workhorse | freshness=" ) in result.stdout - assert ( - "request-ready actions: fix-now=0 | hold=0 | watch=0 | route=1 | inspect=0" in result.stdout - ) + assert "request-ready actions: fix-now=0 | hold=0 | watch=0 | route=1 | inspect=0" in result.stdout assert "[openai-compatible | native | confidence=high]" in result.stdout assert "request-ready payload: deepseek-chat -> openai-chat-minimal" in result.stdout assert "request-ready next step: deepseek-chat -> route can carry live traffic" in result.stdout @@ -489,16 +486,14 @@ def test_faigate_doctor_reports_runtime_cooldown_windows(tmp_path: Path): "ready": False, "status": "rate-limited", "reason": ( - "route is in runtime cooldown for another 120s " - "after recent rate limited failures" + "route is in runtime cooldown for another 120s after recent rate limited failures" ), "profile": "openai-compatible", "compatibility": "native", "probe_confidence": "high", "probe_payload": "openai-chat-minimal | user='ping' | max_tokens=1", "operator_hint": ( - "keep this route out of primary traffic until " - "the cooldown pressure drops" + "keep this route out of primary traffic until the cooldown pressure drops" ), "runtime_penalty": 24, "runtime_issue_type": "rate-limited", @@ -533,16 +528,13 @@ def test_faigate_doctor_reports_runtime_cooldown_windows(tmp_path: Path): assert "request-ready: deepseek-chat -> rate-limited" in result.stdout assert ( - "request-ready action: deepseek-chat -> hold " - "[deepseek | deepseek/chat | balanced-workhorse | freshness=" + "request-ready action: deepseek-chat -> hold [deepseek | deepseek/chat | balanced-workhorse | freshness=" ) in result.stdout assert ( "request-ready runtime: deepseek-chat -> penalty=24 | issue=rate-limited " "| cooldown active | cooldown 120s left" in result.stdout ) - assert ( - "request-ready actions: fix-now=0 | hold=1 | watch=0 | route=0 | inspect=0" in result.stdout - ) + assert "request-ready actions: fix-now=0 | hold=1 | watch=0 | route=0 | inspect=0" in result.stdout def test_faigate_doctor_reports_recent_route_recovery(tmp_path: Path): @@ -621,16 +613,10 @@ def test_faigate_doctor_reports_recent_route_recovery(tmp_path: Path): assert "request-ready: deepseek-chat -> ready-recovered" in result.stdout assert ( - "request-ready action: deepseek-chat -> watch " - "[deepseek | deepseek/chat | balanced-workhorse | freshness=" + "request-ready action: deepseek-chat -> watch [deepseek | deepseek/chat | balanced-workhorse | freshness=" ) in result.stdout - assert ( - "request-ready recovery: deepseek-chat -> recovered from rate-limited | watch 240s" - in result.stdout - ) - assert ( - "request-ready actions: fix-now=0 | hold=0 | watch=1 | route=0 | inspect=0" in result.stdout - ) + assert "request-ready recovery: deepseek-chat -> recovered from rate-limited | watch 240s" in result.stdout + assert "request-ready actions: fix-now=0 | hold=0 | watch=1 | route=0 | inspect=0" in result.stdout def test_faigate_doctor_reports_refresh_guidance_for_stale_routes(tmp_path: Path): @@ -664,9 +650,7 @@ def test_faigate_doctor_reports_refresh_guidance_for_stale_routes(tmp_path: Path "cluster": "balanced-workhorse", "freshness_status": "stale", "review_age_days": 29, - "freshness_hint": ( - "review this route before trusting benchmark assumptions" - ), + "freshness_hint": ("review this route before trusting benchmark assumptions"), }, "request_readiness": { "ready": True, @@ -1239,9 +1223,7 @@ def test_faigate_menu_status_models_flow_formats_model_list(tmp_path: Path): "summary": {"providers_total": 1, "providers_healthy": 1}, } ), - "/v1/models": json.dumps( - {"data": [{"id": "auto"}, {"id": "deepseek-chat"}, {"id": "gemini-flash"}]} - ), + "/v1/models": json.dumps({"data": [{"id": "auto"}, {"id": "deepseek-chat"}, {"id": "gemini-flash"}]}), }, ) @@ -1999,9 +1981,7 @@ def test_faigate_dashboard_provider_detail_shows_canonical_lane(tmp_path: Path): "benchmark_cluster": "balanced-coding", "freshness_status": "fresh", "review_age_days": 1, - "freshness_hint": ( - "benchmark and cost assumptions were reviewed recently" - ), + "freshness_hint": ("benchmark and cost assumptions were reviewed recently"), }, "capabilities": {"cost_tier": "standard"}, "transport": { @@ -2051,15 +2031,10 @@ def test_faigate_dashboard_provider_detail_shows_canonical_lane(tmp_path: Path): assert "Lane cluster balanced-workhorse" in result.stdout assert "Benchmark focus balanced-coding" in result.stdout assert "Cost tier standard" in result.stdout - assert ( - "Routing fit balanced-coding with a standard cost posture over direct routing" - in result.stdout - ) + assert "Routing fit balanced-coding with a standard cost posture over direct routing" in result.stdout assert "Freshness fresh" in result.stdout assert "Review age 1d" in result.stdout - assert ( - "Freshness hint benchmark and cost assumptions were reviewed recently" in result.stdout - ) + assert "Freshness hint benchmark and cost assumptions were reviewed recently" in result.stdout assert "Request-ready ready-verified" in result.stdout assert "Verified via models" in result.stdout assert "Probe payload openai-chat-minimal | user='ping' | max_tokens=1" in result.stdout @@ -2144,8 +2119,7 @@ def test_faigate_dashboard_provider_detail_shows_refresh_guidance_for_stale_lane "freshness_status": "stale", "review_age_days": 24, "freshness_hint": ( - "benchmark and cost assumptions are stale; " - "review before trusting them heavily" + "benchmark and cost assumptions are stale; review before trusting them heavily" ), }, "capabilities": {"cost_tier": "standard"}, @@ -2238,9 +2212,7 @@ def test_faigate_dashboard_activity_and_alerts_show_family_and_path_summaries(tm "client_totals": [], "client_highlights": {}, "operator_actions": [], - "hourly": [ - {"hour_offset": 0, "requests": 20, "cost_usd": 0.6, "tokens": 11000} - ], + "hourly": [{"hour_offset": 0, "requests": 20, "cost_usd": 0.6, "tokens": 11000}], "daily": [ { "day": "2026-03-20", @@ -2284,10 +2256,7 @@ def test_faigate_dashboard_activity_and_alerts_show_family_and_path_summaries(tm assert "Selection paths" in activity.stdout assert "Priority next" in activity.stdout assert "Providers or Clients" in activity.stdout - assert ( - "same-lane-route: 6 req / $0.18 / 410ms " - "[deepseek | cooldown | recovery-watch]" in activity.stdout - ) + assert "same-lane-route: 6 req / $0.18 / 410ms [deepseek | cooldown | recovery-watch]" in activity.stdout assert "Lane families" in alerts.stdout assert "Selection paths" in alerts.stdout assert "Priority next" in alerts.stdout @@ -2552,10 +2521,7 @@ def test_faigate_doctor_prefers_same_lane_route_before_cluster_degrade(tmp_path: "request_readiness": { "ready": False, "status": "rate-limited", - "reason": ( - "route is in runtime cooldown after recent " - "rate limited failures" - ), + "reason": ("route is in runtime cooldown after recent rate limited failures"), "runtime_cooldown_active": True, "runtime_window_state": "cooldown", }, @@ -2610,12 +2576,10 @@ def test_faigate_doctor_prefers_same_lane_route_before_cluster_degrade(tmp_path: ) assert ( - "request-ready action: anthropic-claude -> hold " - "[anthropic | anthropic/opus-4.6 | elite-reasoning | freshness=" + "request-ready action: anthropic-claude -> hold [anthropic | anthropic/opus-4.6 | elite-reasoning | freshness=" ) in result.stdout assert ( - "request-ready preferred route: anthropic-claude -> " - "openrouter-anthropic-opus (same-lane-route)" + "request-ready preferred route: anthropic-claude -> openrouter-anthropic-opus (same-lane-route)" ) in result.stdout assert "request-ready fallback guidance: same-lane=1 | cluster=0 | family=0" in result.stdout @@ -2808,10 +2772,7 @@ def test_faigate_doctor_surfaces_provider_source_priority_actions(tmp_path: Path assert "provider source alert summary: status=intervention-needed" in result.stdout assert "fix-now=2 | review-now=0 | inspect=0" in result.stdout - assert ( - "provider source alert: kilo -> Provider source refresh failing for kilo (fix-now)" - in result.stdout - ) + assert "provider source alert: kilo -> Provider source refresh failing for kilo (fix-now)" in result.stdout assert "provider source priority next: Provider Catalog Refresh" in result.stdout @@ -2853,10 +2814,7 @@ def test_faigate_doctor_prefers_family_route_when_route_is_on_hold(tmp_path: Pat "request_readiness": { "ready": False, "status": "rate-limited", - "reason": ( - "route is in runtime cooldown after recent " - "rate limited failures" - ), + "reason": ("route is in runtime cooldown after recent rate limited failures"), "runtime_cooldown_active": True, "runtime_window_state": "cooldown", }, @@ -2885,13 +2843,9 @@ def test_faigate_doctor_prefers_family_route_when_route_is_on_hold(tmp_path: Pat ) assert ( - "request-ready action: deepseek-reasoner -> hold " - "[deepseek | deepseek/reasoner | elite-reasoning | freshness=" + "request-ready action: deepseek-reasoner -> hold [deepseek | deepseek/reasoner | elite-reasoning | freshness=" ) in result.stdout - assert ( - "request-ready preferred route: deepseek-reasoner -> deepseek-chat " - "(cluster-degrade)" in result.stdout - ) + assert "request-ready preferred route: deepseek-reasoner -> deepseek-chat (cluster-degrade)" in result.stdout def test_faigate_doctor_reports_known_mirror_gaps(tmp_path: Path): @@ -2966,13 +2920,8 @@ def test_faigate_doctor_reports_known_mirror_gaps(tmp_path: Path): check=True, ) - assert ( - "request-ready mirror gap: anthropic-claude -> openrouter-anthropic-opus" in result.stdout - ) - assert ( - "request-ready add route: anthropic-claude -> openrouter-anthropic-opus (same-lane-add)" - in result.stdout - ) + assert "request-ready mirror gap: anthropic-claude -> openrouter-anthropic-opus" in result.stdout + assert "request-ready add route: anthropic-claude -> openrouter-anthropic-opus (same-lane-add)" in result.stdout assert "request-ready add guidance: same-lane=1 | cluster=0 | family=0" in result.stdout assert "request-ready mirror gaps: 1 routes have known mirrors not configured" in result.stdout @@ -3070,9 +3019,7 @@ def test_faigate_client_scenarios_write_shows_client_specific_next_steps(tmp_pat ) assert "Client scenario applied." in result.stdout - assert ( - "Recommended next: open Provider Setup -> Guided Route Additions before restart work" - ) in result.stdout + assert ("Recommended next: open Provider Setup -> Guided Route Additions before restart work") in result.stdout assert "new opencode profile default" in result.stdout assert "drill into opencode Details" in result.stdout diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 3c0df83..1671530 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -64,10 +64,7 @@ def test_onboarding_report_marks_missing_api_keys_and_presets(tmp_path: Path): assert report["integrations"]["openclaw"]["recommended"] is True assert report["integrations"]["n8n"]["recommended"] is False assert report["provider_catalog"]["alert_count"] == 0 - assert ( - "Keep auto_update disabled until the provider and client set is stable." - in report["suggestions"] - ) + assert "Keep auto_update disabled until the provider and client set is stable." in report["suggestions"] def test_onboarding_report_marks_local_worker_ready(tmp_path: Path): @@ -126,9 +123,7 @@ def test_onboarding_report_marks_local_worker_ready(tmp_path: Path): assert "`local-worker`" in markdown -def test_onboarding_validation_blocks_missing_env_and_unready_providers( - tmp_path: Path, monkeypatch -): +def test_onboarding_validation_blocks_missing_env_and_unready_providers(tmp_path: Path, monkeypatch): monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False) monkeypatch.delenv("GEMINI_API_KEY", raising=False) @@ -176,10 +171,7 @@ def test_onboarding_validation_blocks_missing_env_and_unready_providers( assert "Environment file is missing." in validation["blockers"] assert "No configured provider is ready." in validation["blockers"] assert "Fallback chain is empty for a multi-provider setup." in validation["blockers"] - assert ( - "No ready primary provider is available for a staged multi-provider rollout." - in validation["blockers"] - ) + assert "No ready primary provider is available for a staged multi-provider rollout." in validation["blockers"] assert "Client profiles are disabled." in validation["warnings"] assert "Request hooks are enabled but no hooks are configured." in validation["warnings"] assert "Status: blocked" in text @@ -279,9 +271,7 @@ def test_onboarding_report_includes_provider_discovery_links(tmp_path: Path, mon assert "provider discovery:" in text assert "openrouter-fallback: disclosed link -> https://go.example.test/openrouter" in text assert "Policy: provider links affect ranking = `False`" in markdown - assert ( - "`openrouter-fallback`: disclosed link -> `https://go.example.test/openrouter`" in markdown - ) + assert "`openrouter-fallback`: disclosed link -> `https://go.example.test/openrouter`" in markdown def test_onboarding_validation_passes_for_ready_multi_provider_setup(tmp_path: Path): @@ -707,13 +697,7 @@ def test_onboarding_report_includes_provider_rollout_stages_and_gaps(tmp_path: P assert report["provider_rollout"]["fallback_targets"] == [ {"name": "image-worker", "configured": True, "ready": False} ] - assert ( - "Image-capable providers are configured, but none are ready yet." - in report["provider_rollout"]["gaps"] - ) - assert ( - "Fallback chain is configured, but none of its targets are currently ready." - in validation["warnings"] - ) + assert "Image-capable providers are configured, but none are ready yet." in report["provider_rollout"]["gaps"] + assert "Fallback chain is configured, but none of its targets are currently ready." in validation["warnings"] assert "- stage 1 primary: primary-chat" in text assert "- fallback targets:" in text diff --git a/tests/test_provider_catalog.py b/tests/test_provider_catalog.py index 6262c5d..6170346 100644 --- a/tests/test_provider_catalog.py +++ b/tests/test_provider_catalog.py @@ -234,9 +234,7 @@ def test_provider_discovery_view_filters_to_resolved_links(tmp_path: Path): assert view["providers"][0]["resolved_url"].startswith("https://") -def test_provider_discovery_view_supports_link_source_and_offer_track_filters( - tmp_path: Path, monkeypatch -): +def test_provider_discovery_view_supports_link_source_and_offer_track_filters(tmp_path: Path, monkeypatch): monkeypatch.setenv( "FAIGATE_PROVIDER_LINK_OPENROUTER_FALLBACK_URL", "https://go.example.test/openrouter", @@ -304,9 +302,7 @@ def test_build_provider_refresh_guidance_prefers_stale_entries(): assert guidance[1]["action"] == "review-soon" -def test_provider_catalog_report_can_track_provider_from_external_snapshot( - tmp_path: Path, monkeypatch -): +def test_provider_catalog_report_can_track_provider_from_external_snapshot(tmp_path: Path, monkeypatch): snapshot = tmp_path / "provider-catalog.json" snapshot.write_text( """ @@ -364,9 +360,7 @@ def test_provider_catalog_report_can_track_provider_from_external_snapshot( assert report["items"][0]["recommended_model"] == "claude-3-5-haiku-latest" -def test_provider_catalog_external_snapshot_can_override_embedded_entry( - tmp_path: Path, monkeypatch -): +def test_provider_catalog_external_snapshot_can_override_embedded_entry(tmp_path: Path, monkeypatch): snapshot = tmp_path / "provider-catalog.json" snapshot.write_text( """ diff --git a/tests/test_provider_catalog_api.py b/tests/test_provider_catalog_api.py index 7e32642..f0d1f13 100644 --- a/tests/test_provider_catalog_api.py +++ b/tests/test_provider_catalog_api.py @@ -84,9 +84,7 @@ def provider_catalog_api_state(tmp_path: Path, monkeypatch): "field_name": "model_id", "old_value": "x-ai/grok-code-fast-1:free", "new_value": "", - "message": ( - "blackbox: model 'x-ai/grok-code-fast-1:free' disappeared from pricing." - ), + "message": ("blackbox: model 'x-ai/grok-code-fast-1:free' disappeared from pricing."), } ] ) @@ -109,6 +107,5 @@ def test_provider_catalog_endpoint_includes_source_alerts(provider_catalog_api_s assert body["source_catalog"]["alerts"][0]["kind"] == "source-refresh-error" assert any(alert["kind"] == "catalog-change" for alert in body["source_alerts"]) assert any( - "verify the source URL, parser, or auth assumptions" in alert["suggestion"] - for alert in body["source_alerts"] + "verify the source URL, parser, or auth assumptions" in alert["suggestion"] for alert in body["source_alerts"] ) diff --git a/tests/test_request_hooks.py b/tests/test_request_hooks.py index 9019979..0377c6f 100644 --- a/tests/test_request_hooks.py +++ b/tests/test_request_hooks.py @@ -341,10 +341,7 @@ async def test_claude_code_router_prefers_coding_ready_routes(self, tmp_path, mo assert hook_state.routing_hints["capability_values"]["tools"] == [True] assert hook_state.routing_hints["capability_values"]["long_context"] == [True] assert hook_state.routing_hints["prefer_tiers"] == ["default", "reasoning"] - assert any( - "Claude Code router hook applied profile: coding-default" in note - for note in hook_state.notes - ) + assert any("Claude Code router hook applied profile: coding-default" in note for note in hook_state.notes) @pytest.mark.asyncio async def test_claude_code_router_supports_premium_profile(self, tmp_path, monkeypatch): @@ -436,9 +433,7 @@ async def test_claude_code_router_supports_premium_profile(self, tmp_path, monke assert hook_state.applied_hooks == ["claude-code-router"] assert hook_state.routing_hints["routing_mode"] == "premium" assert hook_state.routing_hints["prefer_tiers"] == ["reasoning", "default"] - assert any( - "Claude Code router hook applied profile: premium" in note for note in hook_state.notes - ) + assert any("Claude Code router hook applied profile: premium" in note for note in hook_state.notes) @pytest.mark.asyncio async def test_locality_and_profile_hooks_shape_one_request(self, hook_config): @@ -475,9 +470,7 @@ async def test_locality_and_profile_hooks_shape_one_request(self, hook_config): class TestRequestHookHardening: def test_hook_error_response_hides_exception_text(self): - response = _request_hook_error_response( - HookExecutionError("traceback: provider token leaked in hook output") - ) + response = _request_hook_error_response(HookExecutionError("traceback: provider token leaked in hook output")) assert response.status_code == 500 assert b"traceback" not in response.body @@ -529,14 +522,10 @@ def _unsafe_hook(_context): assert applied.routing_hints["prefer_providers"] == ["local-worker"] assert applied.routing_hints["capability_values"]["local"] == [True] assert "unsafe-test" in applied.applied_hooks - assert any( - "unsupported hook body update field 'internal_flag'" in note for note in applied.notes - ) + assert any("unsupported hook body update field 'internal_flag'" in note for note in applied.notes) assert any("invalid profile override" in error for error in applied.errors) assert any("routing_hints.prefer_providers" in error for error in applied.errors) - assert any( - "unsupported routing_hints field 'unexpected'" in error for error in applied.errors - ) + assert any("unsupported routing_hints field 'unexpected'" in error for error in applied.errors) @pytest.mark.asyncio async def test_hook_fail_mode_raises(self, monkeypatch): diff --git a/tests/test_route_introspection.py b/tests/test_route_introspection.py index e4b7804..fcc0fe2 100644 --- a/tests/test_route_introspection.py +++ b/tests/test_route_introspection.py @@ -599,9 +599,7 @@ async def test_image_route_preview_endpoint_reports_modality(self, preview_confi assert response["selected_provider"]["contract"] == "image-provider" @pytest.mark.asyncio - async def test_image_route_preview_prefers_provider_that_fits_size_and_count( - self, preview_config - ): + async def test_image_route_preview_prefers_provider_that_fits_size_and_count(self, preview_config): response = await preview_image_route( _json_request( "/api/route/image", @@ -765,9 +763,7 @@ async def test_health_reports_capability_coverage(self, preview_config): assert response["providers"]["image-cloud"]["image"]["max_outputs"] == 1 @pytest.mark.asyncio - async def test_health_request_readiness_enters_cooldown_under_runtime_pressure( - self, preview_config - ): + async def test_health_request_readiness_enters_cooldown_under_runtime_pressure(self, preview_config): main_module._adaptive_state.record_failure("cloud-default", error="429 rate limit") main_module._adaptive_state.record_failure("cloud-default", error="429 rate limit") @@ -798,14 +794,10 @@ async def test_health_request_readiness_marks_timeout_routes_as_degraded(self, p assert readiness["status"] == "ready-degraded" assert readiness["runtime_window_state"] == "degraded" assert readiness["runtime_degraded_remaining_s"] > 0 - assert ( - readiness["operator_hint"] == "prefer lower-pressure siblings while this route recovers" - ) + assert readiness["operator_hint"] == "prefer lower-pressure siblings while this route recovers" @pytest.mark.asyncio - async def test_health_request_readiness_blocks_auth_invalid_routes_immediately( - self, preview_config - ): + async def test_health_request_readiness_blocks_auth_invalid_routes_immediately(self, preview_config): main_module._adaptive_state.record_failure("cloud-default", error="401 invalid api key") response = await health() diff --git a/tests/test_updates.py b/tests/test_updates.py index 1aff319..e34435d 100644 --- a/tests/test_updates.py +++ b/tests/test_updates.py @@ -496,9 +496,7 @@ async def test_min_release_age_blocks_auto_update_until_release_has_aged(): { "tag_name": "v0.6.1", "html_url": "https://github.com/fusionAIze/faigate/releases/tag/v0.6.1", - "published_at": (datetime.now(UTC) - timedelta(hours=1)) - .isoformat() - .replace("+00:00", "Z"), + "published_at": (datetime.now(UTC) - timedelta(hours=1)).isoformat().replace("+00:00", "Z"), }, ) ) diff --git a/tests/test_wizard.py b/tests/test_wizard.py index 8b4ae5f..528271a 100644 --- a/tests/test_wizard.py +++ b/tests/test_wizard.py @@ -1267,15 +1267,9 @@ def test_list_client_scenarios_exposes_opencode_quality_path(tmp_path: Path): assert by_id["opencode-quality"]["routing_mode"] == "premium" assert "anthropic-claude" in by_id["opencode-quality"]["ready_providers"] assert any("anthropic/opus-4.6:" in line for line in by_id["opencode-quality"]["route_mirrors"]) - assert any( - line.startswith("anthropic/opus-4.6 ->") - for line in by_id["opencode-quality"]["degrade_chains"] - ) + assert any(line.startswith("anthropic/opus-4.6 ->") for line in by_id["opencode-quality"]["degrade_chains"]) assert by_id["opencode-quality"]["route_additions"] - assert ( - by_id["opencode-quality"]["route_additions"][0]["provider_name"] - == "openrouter-anthropic-opus" - ) + assert by_id["opencode-quality"]["route_additions"][0]["provider_name"] == "openrouter-anthropic-opus" def test_apply_client_scenario_sets_client_profile_mode_and_adds_providers(tmp_path: Path): @@ -1330,9 +1324,7 @@ def test_apply_client_scenario_sets_client_profile_mode_and_adds_providers(tmp_p assert "add route:" in summary -def test_render_client_scenario_summary_includes_refresh_guidance( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -): +def test_render_client_scenario_summary_includes_refresh_guidance(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): config_path = tmp_path / "config.yaml" config_path.write_text( """ @@ -1508,9 +1500,7 @@ def test_build_route_add_setup_plan_separates_ready_and_manual_additions(tmp_pat assert "deepseek-chat" in rendered -def test_render_route_add_setup_plan_text_includes_refresh_guidance( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -): +def test_render_route_add_setup_plan_text_includes_refresh_guidance(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): config_path = tmp_path / "config.yaml" config_path.write_text( """