mirror of
https://github.com/invariantlabs-ai/invariant-gateway.git
synced 2026-07-08 20:07:54 +02:00
Small changes related to constants and sorting order of imports.
This commit is contained in:
@@ -10,7 +10,6 @@ from httpx import HTTPStatusError
|
||||
|
||||
from gateway.common.guardrails import Guardrail, GuardrailAction, GuardrailRuleSet
|
||||
|
||||
|
||||
def extract_policy_from_headers(request: Optional[fastapi.Request]) -> Optional[str]:
|
||||
"""
|
||||
Extracts the guardrailing policy from the request headers if present.
|
||||
|
||||
@@ -16,3 +16,6 @@ IGNORED_HEADERS = [
|
||||
|
||||
CLIENT_TIMEOUT = 60.0
|
||||
|
||||
CONTENT_TYPE_HEADER = "content-type"
|
||||
CONTENT_TYPE_JSON = "application/json"
|
||||
CONTENT_TYPE_EVENT_STREAM = "text/event-stream"
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"""Common guardrails data class."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class GuardrailAction(str, Enum):
|
||||
"""Enum representing the action to be taken for guardrail rules."""
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ from typing import Any, Dict, Optional
|
||||
|
||||
import fastapi
|
||||
|
||||
from gateway.common.config_manager import GatewayConfig
|
||||
from gateway.common.guardrails import GuardrailRuleSet, Guardrail, GuardrailAction
|
||||
from gateway.common.authorization import (
|
||||
extract_guardrail_service_authorization_from_headers,
|
||||
)
|
||||
from gateway.common.config_manager import GatewayConfig
|
||||
from gateway.common.guardrails import GuardrailRuleSet, Guardrail, GuardrailAction
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -25,7 +25,7 @@ class RequestContext:
|
||||
# the set of guardrails to enforce for this request
|
||||
guardrails: Optional[GuardrailRuleSet] = None
|
||||
config: Dict[str, Any] = None
|
||||
|
||||
|
||||
# extra parameters available as input.<key> during guardrail evaluation
|
||||
guardrails_parameters: Optional[Dict[str, Any]] = None
|
||||
|
||||
@@ -100,7 +100,7 @@ class RequestContext:
|
||||
guardrails=guardrails,
|
||||
config=context_config,
|
||||
_created_via_factory=True,
|
||||
guardrails_parameters=guardrails_parameters
|
||||
guardrails_parameters=guardrails_parameters,
|
||||
)
|
||||
|
||||
def get_guardrailing_authorization(self) -> Optional[str]:
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
|
||||
from gateway.common.constants import DEFAULT_API_URL
|
||||
@@ -12,8 +13,6 @@ from invariant_sdk.async_client import AsyncClient
|
||||
from invariant_sdk.types.push_traces import PushTracesRequest, PushTracesResponse
|
||||
from invariant_sdk.types.annotations import AnnotationCreate
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def create_annotations_from_guardrails_errors(
|
||||
guardrails_errors: List[dict],
|
||||
|
||||
@@ -376,11 +376,14 @@ async def check_guardrails(
|
||||
if result.status_code == 401:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="The provided Invariant API key is not valid for guardrail checking. Please ensure you are using the correct API key or pass an alternative API key for guardrail checking specifically via the '{}' header.".format(
|
||||
INVARIANT_GUARDRAIL_SERVICE_AUTHORIZATION_HEADER
|
||||
detail=(
|
||||
"The provided Invariant API key is not valid for guardrail checking. "
|
||||
"Please ensure you are using the correct API key or pass an "
|
||||
"alternative API key for guardrail checking specifically via the "
|
||||
f"'{INVARIANT_GUARDRAIL_SERVICE_AUTHORIZATION_HEADER}' header."
|
||||
),
|
||||
)
|
||||
raise Exception(
|
||||
raise Exception( # pylint: disable=broad-exception-raised
|
||||
f"Guardrails check failed: {result.status_code} - {result.text}"
|
||||
)
|
||||
guardrails_result = result.json()
|
||||
@@ -412,7 +415,7 @@ async def check_guardrails(
|
||||
return aggregated_errors
|
||||
except HTTPException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"Failed to verify guardrails: {e}")
|
||||
# make sure runtime errors are also visible in e.g. Explorer
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from builtins import print as builtins_print
|
||||
|
||||
os.makedirs(os.path.join(os.path.expanduser("~"), ".invariant"), exist_ok=True)
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ from httpx_sse import aconnect_sse, ServerSentEvent
|
||||
from fastapi import APIRouter, HTTPException, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from gateway.common.constants import CLIENT_TIMEOUT
|
||||
from gateway.common.constants import CLIENT_TIMEOUT, CONTENT_TYPE_EVENT_STREAM
|
||||
from gateway.mcp.constants import MCP_CUSTOM_HEADER_PREFIX, UTF_8
|
||||
from gateway.mcp.mcp_sessions_manager import (
|
||||
McpSessionsManager,
|
||||
@@ -293,7 +293,7 @@ class SseTransport(McpTransportBase):
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
media_type=CONTENT_TYPE_EVENT_STREAM,
|
||||
headers={"X-Proxied-By": "mcp-gateway", **response_headers},
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,12 @@ from httpx_sse import aconnect_sse
|
||||
from fastapi import APIRouter, HTTPException, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from gateway.common.constants import CLIENT_TIMEOUT
|
||||
from gateway.common.constants import (
|
||||
CLIENT_TIMEOUT,
|
||||
CONTENT_TYPE_HEADER,
|
||||
CONTENT_TYPE_JSON,
|
||||
CONTENT_TYPE_EVENT_STREAM,
|
||||
)
|
||||
from gateway.mcp.constants import (
|
||||
INVARIANT_SESSION_ID_PREFIX,
|
||||
MCP_CUSTOM_HEADER_PREFIX,
|
||||
@@ -23,9 +28,6 @@ from gateway.mcp.mcp_transport_base import McpTransportBase
|
||||
gateway = APIRouter()
|
||||
mcp_sessions_manager = McpSessionsManager()
|
||||
|
||||
CONTENT_TYPE_JSON = "application/json"
|
||||
CONTENT_TYPE_SSE = "text/event-stream"
|
||||
CONTENT_TYPE_HEADER = "content-type"
|
||||
MCP_SESSION_ID_HEADER = "mcp-session-id"
|
||||
MCP_SERVER_POST_AND_DELETE_HEADERS = {
|
||||
"connection",
|
||||
@@ -187,7 +189,7 @@ class StreamableTransport(McpTransportBase):
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type=CONTENT_TYPE_SSE,
|
||||
media_type=CONTENT_TYPE_EVENT_STREAM,
|
||||
headers={"X-Proxied-By": "mcp-gateway", **response_headers},
|
||||
)
|
||||
|
||||
@@ -382,7 +384,7 @@ class StreamableTransport(McpTransportBase):
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type=CONTENT_TYPE_SSE,
|
||||
media_type=CONTENT_TYPE_EVENT_STREAM,
|
||||
headers=response_headers,
|
||||
)
|
||||
|
||||
|
||||
+27
-10
@@ -14,7 +14,12 @@ from gateway.common.config_manager import (
|
||||
GatewayConfigManager,
|
||||
extract_guardrails_from_header,
|
||||
)
|
||||
from gateway.common.constants import CLIENT_TIMEOUT, IGNORED_HEADERS
|
||||
from gateway.common.constants import (
|
||||
CLIENT_TIMEOUT,
|
||||
CONTENT_TYPE_JSON,
|
||||
CONTENT_TYPE_EVENT_STREAM,
|
||||
IGNORED_HEADERS,
|
||||
)
|
||||
from gateway.common.guardrails import GuardrailAction, GuardrailRuleSet
|
||||
from gateway.common.request_context import RequestContext
|
||||
from gateway.converters.anthropic_to_invariant import (
|
||||
@@ -218,7 +223,10 @@ class InstrumentedAnthropicResponse(InstrumentedResponse):
|
||||
self.guardrails_execution_result = {}
|
||||
|
||||
async def on_start(self):
|
||||
"""Check guardrails in a pipelined fashion, before processing the first chunk (for input guardrailing)."""
|
||||
"""
|
||||
Check guardrails in a pipelined fashion, before processing the first
|
||||
chunk (for input guardrailing).
|
||||
"""
|
||||
if self.context.guardrails:
|
||||
self.guardrails_execution_result = await get_guardrails_check_result(
|
||||
self.context, action=GuardrailAction.BLOCK, response_json={}
|
||||
@@ -249,8 +257,8 @@ class InstrumentedAnthropicResponse(InstrumentedResponse):
|
||||
Response(
|
||||
content=error_chunk,
|
||||
status_code=400,
|
||||
media_type="application/json",
|
||||
headers={"content-type": "application/json"},
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
headers={"content-type": CONTENT_TYPE_JSON},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -263,7 +271,10 @@ class InstrumentedAnthropicResponse(InstrumentedResponse):
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(
|
||||
status_code=self.response.status_code,
|
||||
detail=f"Invalid JSON response received from Anthropic: {self.response.text}, got error{e}",
|
||||
detail=(
|
||||
"Invalid JSON response received from Anthropic: "
|
||||
f"{self.response.text}, got error: {e}"
|
||||
),
|
||||
) from e
|
||||
if self.response.status_code != 200:
|
||||
raise HTTPException(
|
||||
@@ -289,12 +300,15 @@ class InstrumentedAnthropicResponse(InstrumentedResponse):
|
||||
return Response(
|
||||
content=content,
|
||||
status_code=status_code,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
headers=dict(updated_headers),
|
||||
)
|
||||
|
||||
async def on_end(self):
|
||||
"""Checks guardrails after the response is received, and asynchronously pushes to Explorer."""
|
||||
"""
|
||||
Checks guardrails after the response is received, and asynchronously
|
||||
pushes to Explorer.
|
||||
"""
|
||||
# ensure the response data is available
|
||||
assert self.response is not None, "response is None"
|
||||
assert self.response_json is not None, "response_json is None"
|
||||
@@ -383,7 +397,10 @@ class InstrumentedAnthropicStreamingResponse(InstrumentedStreamingResponse):
|
||||
self.sse_buffer = "" # Buffer for incomplete events
|
||||
|
||||
async def on_start(self):
|
||||
"""Check guardrails in a pipelined fashion, before processing the first chunk (for input guardrailing)."""
|
||||
"""
|
||||
Check guardrails in a pipelined fashion, before processing the
|
||||
first chunk (for input guardrailing).
|
||||
"""
|
||||
if self.context.guardrails:
|
||||
self.guardrails_execution_result = await get_guardrails_check_result(
|
||||
self.context,
|
||||
@@ -503,7 +520,7 @@ class InstrumentedAnthropicStreamingResponse(InstrumentedStreamingResponse):
|
||||
f"JSON parsing error in event: {e}. Event data: {event_data[:100]}...",
|
||||
flush=True,
|
||||
)
|
||||
except Exception as e:
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
print(f"Error processing event: {e}", flush=True)
|
||||
|
||||
# on last stream chunk, run output guardrails
|
||||
@@ -582,7 +599,7 @@ async def handle_streaming_response(
|
||||
)
|
||||
|
||||
return StreamingResponse(
|
||||
response.instrumented_event_generator(), media_type="text/event-stream"
|
||||
response.instrumented_event_generator(), media_type=CONTENT_TYPE_EVENT_STREAM
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ from gateway.common.config_manager import (
|
||||
)
|
||||
from gateway.common.constants import (
|
||||
CLIENT_TIMEOUT,
|
||||
CONTENT_TYPE_JSON,
|
||||
CONTENT_TYPE_EVENT_STREAM,
|
||||
IGNORED_HEADERS,
|
||||
)
|
||||
from gateway.common.guardrails import GuardrailAction, GuardrailRuleSet
|
||||
@@ -82,7 +84,11 @@ async def gemini_generate_content_gateway(
|
||||
request_json = json.loads(request_body_bytes)
|
||||
|
||||
client = httpx.AsyncClient(timeout=httpx.Timeout(CLIENT_TIMEOUT))
|
||||
gemini_api_url = f"https://generativelanguage.googleapis.com/{api_version}/models/{model}:{endpoint}"
|
||||
gemini_api_url = (
|
||||
f"https://generativelanguage.googleapis.com/"
|
||||
f"{api_version}/models/"
|
||||
f"{model}:{endpoint}"
|
||||
)
|
||||
if alt == "sse":
|
||||
gemini_api_url += "?alt=sse"
|
||||
gemini_request = client.build_request(
|
||||
@@ -303,7 +309,7 @@ async def stream_response(
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
media_type=CONTENT_TYPE_EVENT_STREAM,
|
||||
)
|
||||
|
||||
|
||||
@@ -511,9 +517,9 @@ class InstrumentedGeminiResponse(InstrumentedResponse):
|
||||
Response(
|
||||
content=error_chunk,
|
||||
status_code=400,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Content-Type": CONTENT_TYPE_JSON,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -541,7 +547,7 @@ class InstrumentedGeminiResponse(InstrumentedResponse):
|
||||
return Response(
|
||||
content=response_string,
|
||||
status_code=response_code,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
headers=dict(self.response.headers),
|
||||
)
|
||||
|
||||
@@ -584,7 +590,7 @@ class InstrumentedGeminiResponse(InstrumentedResponse):
|
||||
Response(
|
||||
content=response_string,
|
||||
status_code=response_code,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
headers=dict(self.response.headers),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -16,6 +16,8 @@ from gateway.common.config_manager import (
|
||||
)
|
||||
from gateway.common.constants import (
|
||||
CLIENT_TIMEOUT,
|
||||
CONTENT_TYPE_JSON,
|
||||
CONTENT_TYPE_EVENT_STREAM,
|
||||
IGNORED_HEADERS,
|
||||
)
|
||||
from gateway.common.guardrails import GuardrailAction, GuardrailRuleSet
|
||||
@@ -273,7 +275,8 @@ class InstrumentedOpenAIStreamResponse(InstrumentedStreamingResponse):
|
||||
}
|
||||
)
|
||||
|
||||
# yield an extra error chunk (without preventing the original chunk to go through after)
|
||||
# yield an extra error chunk (without preventing the original
|
||||
# chunk to go through after)
|
||||
return ExtraItem(f"data: {error_chunk}\n\n".encode())
|
||||
|
||||
# push will happen in on_end
|
||||
@@ -324,7 +327,7 @@ async def handle_stream_response(
|
||||
)
|
||||
|
||||
return StreamingResponse(
|
||||
response.instrumented_event_generator(), media_type="text/event-stream"
|
||||
response.instrumented_event_generator(), media_type=CONTENT_TYPE_EVENT_STREAM
|
||||
)
|
||||
|
||||
|
||||
@@ -606,7 +609,7 @@ class InstrumentedOpenAIResponse(InstrumentedResponse):
|
||||
}
|
||||
),
|
||||
status_code=400,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
),
|
||||
end_of_stream=True,
|
||||
)
|
||||
@@ -634,7 +637,7 @@ class InstrumentedOpenAIResponse(InstrumentedResponse):
|
||||
return Response(
|
||||
content=response_string,
|
||||
status_code=response_code,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
headers=dict(self.response.headers),
|
||||
)
|
||||
|
||||
@@ -686,7 +689,7 @@ class InstrumentedOpenAIResponse(InstrumentedResponse):
|
||||
Response(
|
||||
content=response_string,
|
||||
status_code=response_code,
|
||||
media_type="application/json",
|
||||
media_type=CONTENT_TYPE_JSON,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user