mirror of
https://github.com/invariantlabs-ai/invariant-gateway.git
synced 2026-08-17 14:27:13 +02:00
Support passing a policy file to the proxy on startup.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Common Configurations for the Proxy Server."""
|
||||
|
||||
import os
|
||||
import threading
|
||||
|
||||
from invariant.analyzer import Policy
|
||||
|
||||
|
||||
class ProxyConfig:
|
||||
"""Common configurations for the Proxy Server."""
|
||||
|
||||
def __init__(self):
|
||||
print("hello in init of ProxyConfig")
|
||||
self.policies = self._load_policies()
|
||||
|
||||
def _load_policies(self) -> str:
|
||||
"""
|
||||
Loads and validates policies from the file specified in POLICIES_FILE_PATH.
|
||||
Returns the policy file content as a string if valid; otherwise, raises an error.
|
||||
"""
|
||||
print("hello in load_policies")
|
||||
policies_file = os.getenv("POLICIES_FILE_PATH", "")
|
||||
|
||||
if not policies_file:
|
||||
print("Warning: POLICIES_FILE_PATH is not set. Using empty policies.")
|
||||
return ""
|
||||
|
||||
try:
|
||||
with open(policies_file, "r", encoding="utf-8") as f:
|
||||
policy_file_content = f.read()
|
||||
_ = Policy.from_string(policy_file_content)
|
||||
return policy_file_content
|
||||
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
raise ValueError(
|
||||
f"Error: Unable to read policies file ({policies_file}): {e}"
|
||||
) from e
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid policy content in {policies_file}: {e}") from e
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ProxyConfig(policies={repr(self.policies)})"
|
||||
|
||||
|
||||
class ProxyConfigManager:
|
||||
"""Manager for Proxy Configuration."""
|
||||
|
||||
_config_instance = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
@classmethod
|
||||
def get_config(cls):
|
||||
"""Initializes and returns the proxy configuration using double-checked locking."""
|
||||
local_config = cls._config_instance
|
||||
|
||||
if local_config is None:
|
||||
with cls._lock:
|
||||
local_config = cls._config_instance
|
||||
if local_config is None:
|
||||
local_config = ProxyConfig()
|
||||
cls._config_instance = local_config
|
||||
return local_config
|
||||
@@ -1,5 +1,6 @@
|
||||
fastapi==0.115.7
|
||||
httpx==0.28.1
|
||||
invariant-ai>=0.2.1
|
||||
invariant-sdk>=0.0.10
|
||||
starlette-compress==1.4.0
|
||||
uvicorn==0.34.0
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
from common.config_manager import ProxyConfig, ProxyConfigManager
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response
|
||||
from starlette.responses import StreamingResponse
|
||||
from utils.constants import CLIENT_TIMEOUT, IGNORED_HEADERS
|
||||
@@ -43,6 +44,7 @@ def validate_headers(x_api_key: str = Header(None)):
|
||||
async def anthropic_v1_messages_proxy(
|
||||
request: Request,
|
||||
dataset_name: str = None,
|
||||
config: ProxyConfig = Depends(ProxyConfigManager.get_config), # pylint: disable=unused-argument
|
||||
):
|
||||
"""Proxy calls to the Anthropic APIs"""
|
||||
headers = {
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
from common.config_manager import ProxyConfig, ProxyConfigManager
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from utils.constants import CLIENT_TIMEOUT, IGNORED_HEADERS
|
||||
@@ -33,9 +34,9 @@ def validate_headers(authorization: str = Header(None)):
|
||||
async def openai_chat_completions_proxy(
|
||||
request: Request,
|
||||
dataset_name: str = None,
|
||||
config: ProxyConfig = Depends(ProxyConfigManager.get_config), # pylint: disable=unused-argument
|
||||
) -> Response:
|
||||
"""Proxy calls to the OpenAI APIs"""
|
||||
|
||||
headers = {
|
||||
k: v for k, v in request.headers.items() if k.lower() not in IGNORED_HEADERS
|
||||
}
|
||||
|
||||
+10
-2
@@ -1,8 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# if DEV_MODE is true, then run the app with auto-reload
|
||||
# Validate configuration
|
||||
python validate_config.py
|
||||
|
||||
# Check exit code of validation script
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Configuration validation failed. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$DEV_MODE" = "true" ]; then
|
||||
uvicorn serve:app --host 0.0.0.0 --port 8000 --reload
|
||||
else
|
||||
uvicorn serve:app --host 0.0.0.0 --port 8000
|
||||
fi
|
||||
fi
|
||||
+2
-2
@@ -19,7 +19,7 @@ router = fastapi.APIRouter(prefix="/api/v1")
|
||||
@router.get("/proxy/health", tags=["health_check"], include_in_schema=False)
|
||||
async def get_proxy_info():
|
||||
"""Health check"""
|
||||
return {"message": "Hello v1/proxy"}
|
||||
return {"message": "Hello from Invariant v1/proxy"}
|
||||
|
||||
|
||||
router.include_router(open_ai_proxy, prefix="/proxy", tags=["open_ai_proxy"])
|
||||
@@ -28,6 +28,6 @@ router.include_router(anthropic_proxy, prefix="/proxy", tags=["anthropic_proxy"]
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
# Serve the API
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Validates the ProxyConfigManager configuration."""
|
||||
|
||||
import sys
|
||||
|
||||
from common.config_manager import ProxyConfigManager
|
||||
|
||||
try:
|
||||
_ = ProxyConfigManager.get_config()
|
||||
print("ProxyConfig validated successfully.")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"Error loading ProxyConfig error: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user