mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
parent
d6c877b73a
commit
3f8c4598da
5 changed files with 22 additions and 315 deletions
|
|
@ -8,11 +8,9 @@ import os
|
|||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from litellm.integrations.additional_logging_utils import AdditionalLoggingUtils
|
||||
from litellm.integrations.arize import _utils
|
||||
from litellm.integrations.opentelemetry import OpenTelemetry
|
||||
from litellm.types.integrations.arize import ArizeConfig
|
||||
from litellm.types.integrations.base_health_check import IntegrationHealthCheckStatus
|
||||
from litellm.types.services import ServiceLoggerPayload
|
||||
from litellm.types.utils import StandardCallbackDynamicParams
|
||||
|
||||
|
|
@ -28,7 +26,7 @@ else:
|
|||
Span = Any
|
||||
|
||||
|
||||
class ArizeLogger(OpenTelemetry, AdditionalLoggingUtils):
|
||||
class ArizeLogger(OpenTelemetry):
|
||||
def set_attributes(self, span: Span, kwargs, response_obj: Optional[Any]):
|
||||
ArizeLogger.set_arize_attributes(span, kwargs, response_obj)
|
||||
return
|
||||
|
|
@ -143,87 +141,3 @@ class ArizeLogger(OpenTelemetry, AdditionalLoggingUtils):
|
|||
)
|
||||
|
||||
return dynamic_headers
|
||||
|
||||
async def async_health_check(self, standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = None) -> IntegrationHealthCheckStatus:
|
||||
"""
|
||||
Check if Arize service is healthy by testing OTEL trace export
|
||||
|
||||
Args:
|
||||
standard_callback_dynamic_params: Dynamic parameters containing arize_api_key and arize_space_key/arize_space_id
|
||||
|
||||
Returns:
|
||||
IntegrationHealthCheckStatus with status and optional error message
|
||||
"""
|
||||
try:
|
||||
api_key = None
|
||||
space_key = None
|
||||
|
||||
if standard_callback_dynamic_params:
|
||||
api_key = standard_callback_dynamic_params.get("arize_api_key")
|
||||
space_key = (
|
||||
standard_callback_dynamic_params.get("arize_space_key") or
|
||||
standard_callback_dynamic_params.get("arize_space_id") # fallback for backwards compatibility
|
||||
)
|
||||
|
||||
if not api_key:
|
||||
api_key = os.environ.get("ARIZE_API_KEY")
|
||||
if not space_key:
|
||||
space_key = os.environ.get("ARIZE_SPACE_KEY")
|
||||
|
||||
if not api_key or not space_key:
|
||||
return IntegrationHealthCheckStatus(
|
||||
status="unhealthy",
|
||||
error_message="Arize credentials not configured. Please set arize_api_key and arize_space_key parameters or ARIZE_API_KEY and ARIZE_SPACE_KEY environment variables."
|
||||
)
|
||||
|
||||
# Get Arize configuration
|
||||
arize_config = ArizeLogger.get_arize_config()
|
||||
|
||||
# Validate configuration
|
||||
if not arize_config.endpoint:
|
||||
return IntegrationHealthCheckStatus(
|
||||
status="unhealthy",
|
||||
error_message="Arize endpoint not configured. Using default endpoint https://otlp.arize.com/v1"
|
||||
)
|
||||
|
||||
try:
|
||||
test_headers = {
|
||||
"arize-space-id": space_key.strip(),
|
||||
"api_key": api_key.strip(),
|
||||
}
|
||||
|
||||
endpoint = arize_config.endpoint or "https://otlp.arize.com/v1"
|
||||
|
||||
# For a basic health check, we just validate that the configuration is properly formed
|
||||
# A full test would require actually sending a trace, which might be overkill for health checks
|
||||
|
||||
return IntegrationHealthCheckStatus(
|
||||
status="healthy",
|
||||
error_message=None
|
||||
)
|
||||
|
||||
except Exception as config_error:
|
||||
return IntegrationHealthCheckStatus(
|
||||
status="unhealthy",
|
||||
error_message=f"Arize configuration error: {str(config_error)}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return IntegrationHealthCheckStatus(
|
||||
status="unhealthy",
|
||||
error_message=f"Arize health check failed: {str(e)}"
|
||||
)
|
||||
|
||||
async def get_request_response_payload(
|
||||
self,
|
||||
request_id: str,
|
||||
start_time_utc: Optional[datetime] = None,
|
||||
end_time_utc: Optional[datetime] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Get the request and response payload for a given request_id from Arize.
|
||||
|
||||
Note: Arize is primarily for observability/tracing, not request/response storage.
|
||||
This method returns None as Arize doesn't typically store raw payloads.
|
||||
"""
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -2368,17 +2368,6 @@ class AllCallbacks(LiteLLMPydanticObjectBase):
|
|||
ui_callback_name="Lago Billing",
|
||||
)
|
||||
|
||||
arize: CallbackOnUI = CallbackOnUI(
|
||||
litellm_callback_name="arize",
|
||||
litellm_callback_params=[
|
||||
"ARIZE_API_KEY",
|
||||
"ARIZE_SPACE_KEY",
|
||||
"ARIZE_ENDPOINT",
|
||||
"ARIZE_HTTP_ENDPOINT",
|
||||
],
|
||||
ui_callback_name="Arize",
|
||||
)
|
||||
|
||||
|
||||
class SpendLogsMetadata(TypedDict):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ async def health_services_endpoint( # noqa: PLR0915
|
|||
"braintrust",
|
||||
"datadog",
|
||||
"generic_api",
|
||||
"arize",
|
||||
],
|
||||
str,
|
||||
] = fastapi.Query(description="Specify the service being hit."),
|
||||
|
|
@ -114,7 +113,6 @@ async def health_services_endpoint( # noqa: PLR0915
|
|||
"langsmith",
|
||||
"datadog",
|
||||
"generic_api",
|
||||
"arize",
|
||||
]:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
|
@ -167,19 +165,6 @@ async def health_services_endpoint( # noqa: PLR0915
|
|||
"status": "success",
|
||||
"message": "Mock LLM request made - check langfuse.",
|
||||
}
|
||||
elif service == "arize":
|
||||
from litellm.integrations.arize.arize import ArizeLogger
|
||||
|
||||
arize_logger = ArizeLogger()
|
||||
response = await arize_logger.async_health_check()
|
||||
return {
|
||||
"status": response["status"],
|
||||
"message": (
|
||||
response["error_message"]
|
||||
if response["status"] == "unhealthy"
|
||||
else "Arize is healthy and ready to receive traces"
|
||||
),
|
||||
}
|
||||
|
||||
if service == "webhook":
|
||||
user_info = CallInfo(
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const callbackInfo: Record<string, CallbackInfo> = {
|
|||
supports_key_team_logging: true,
|
||||
dynamic_params: {
|
||||
"arize_api_key": "password",
|
||||
"arize_space_key": "text",
|
||||
"arize_space_id": "text",
|
||||
},
|
||||
description: "Arize Logging Integration"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -48,10 +48,8 @@ import {
|
|||
callback_map,
|
||||
callbackInfo,
|
||||
Callbacks,
|
||||
reverse_callback_map,
|
||||
} from "./callback_info_helpers";
|
||||
import { parseErrorMessage } from "./shared/errorUtils";
|
||||
import Image from "next/image";
|
||||
interface SettingsPageProps {
|
||||
accessToken: string | null;
|
||||
userRole: string | null;
|
||||
|
|
@ -109,8 +107,6 @@ const Settings: React.FC<SettingsPageProps> = ({
|
|||
);
|
||||
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
|
||||
const [callbackToDelete, setCallbackToDelete] = useState<string | null>(null);
|
||||
const [testingConnection, setTestingConnection] = useState<boolean>(false);
|
||||
const [connectionStatus, setConnectionStatus] = useState<'success' | 'error' | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (showEditCallback && selectedEditCallback) {
|
||||
|
|
@ -255,133 +251,6 @@ const Settings: React.FC<SettingsPageProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleCallbackSelectChange = (value: string) => {
|
||||
// Reset connection status when callback changes
|
||||
setConnectionStatus(null);
|
||||
|
||||
// Find callback by internal value - check if allCallbacks is array first
|
||||
let selectedCallbackObject = null;
|
||||
if (Array.isArray(allCallbacks) && allCallbacks.length > 0) {
|
||||
selectedCallbackObject = allCallbacks.find(
|
||||
cb => cb.litellm_callback_name === value
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedCallbackObject) {
|
||||
handleSelectedCallbackChange(selectedCallbackObject);
|
||||
} else {
|
||||
// Fallback: use dynamic params from callbackInfo
|
||||
const displayName = reverse_callback_map[value];
|
||||
if (displayName && callbackInfo[displayName]?.dynamic_params) {
|
||||
const dynamicParams = Object.keys(callbackInfo[displayName].dynamic_params);
|
||||
setSelectedCallback(value);
|
||||
setSelectedCallbackParams(dynamicParams);
|
||||
} else {
|
||||
// Final fallback: try to find in allCallbacks by index (for backward compatibility)
|
||||
let legacyCallback = null;
|
||||
if (Array.isArray(allCallbacks)) {
|
||||
// The old system used array indices, check if value is a number
|
||||
const numericValue = parseInt(value);
|
||||
if (!isNaN(numericValue) && allCallbacks[numericValue]) {
|
||||
legacyCallback = allCallbacks[numericValue];
|
||||
}
|
||||
}
|
||||
|
||||
if (legacyCallback) {
|
||||
handleSelectedCallbackChange(legacyCallback);
|
||||
} else {
|
||||
setSelectedCallback(value);
|
||||
setSelectedCallbackParams([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getFieldType = (paramName: string, callbackName: string): "text" | "password" => {
|
||||
const displayName = reverse_callback_map[callbackName];
|
||||
if (displayName && callbackInfo[displayName]?.dynamic_params) {
|
||||
const paramType = callbackInfo[displayName].dynamic_params[paramName];
|
||||
return paramType === "password" ? "password" : "text";
|
||||
}
|
||||
// Default heuristics for legacy callbacks
|
||||
return paramName.toLowerCase().includes("key") ||
|
||||
paramName.toLowerCase().includes("secret") ||
|
||||
paramName.toLowerCase().includes("token") ? "password" : "text";
|
||||
};
|
||||
|
||||
const getFieldLabel = (paramName: string): string => {
|
||||
return paramName
|
||||
.replace(/_/g, " ")
|
||||
.replace(/\b\w/g, l => l.toUpperCase());
|
||||
};
|
||||
|
||||
const getFieldPlaceholder = (paramName: string, callbackName: string): string => {
|
||||
const displayName = reverse_callback_map[callbackName];
|
||||
|
||||
if (displayName === "Arize") {
|
||||
if (paramName === "ARIZE_API_KEY" || paramName === "arize_api_key") return "Enter your Arize API Key...";
|
||||
if (paramName === "ARIZE_SPACE_KEY" || paramName === "arize_space_id") return "Enter your Arize Space Key...";
|
||||
if (paramName === "ARIZE_ENDPOINT" || paramName === "ARIZE_HTTP_ENDPOINT") return "Optional: Custom endpoint URL";
|
||||
}
|
||||
|
||||
return `Enter ${getFieldLabel(paramName)}...`;
|
||||
};
|
||||
|
||||
const isRequiredField = (paramName: string, callbackName: string): boolean => {
|
||||
const displayName = reverse_callback_map[callbackName];
|
||||
|
||||
if (displayName === "Arize") {
|
||||
return (paramName === "ARIZE_API_KEY" || paramName === "arize_api_key") ||
|
||||
(paramName === "ARIZE_SPACE_KEY" || paramName === "arize_space_id");
|
||||
}
|
||||
|
||||
// Default: all fields are required except endpoints
|
||||
return !paramName.toLowerCase().includes("endpoint") &&
|
||||
!paramName.toLowerCase().includes("base") &&
|
||||
!paramName.toLowerCase().includes("host");
|
||||
};
|
||||
|
||||
const testCallbackConnection = async () => {
|
||||
if (!selectedCallback || !accessToken) return;
|
||||
|
||||
setTestingConnection(true);
|
||||
setConnectionStatus(null);
|
||||
|
||||
try {
|
||||
const formValues = addForm.getFieldsValue();
|
||||
|
||||
// Build query params for connection test
|
||||
const params = new URLSearchParams();
|
||||
selectedCallbackParams.forEach(param => {
|
||||
const value = formValues[param];
|
||||
if (value) {
|
||||
// Convert backend param names to dynamic param names
|
||||
let dynamicParamName = param.toLowerCase();
|
||||
if (selectedCallback === "arize") {
|
||||
if (param === "ARIZE_API_KEY" || param === "arize_api_key") dynamicParamName = "arize_api_key";
|
||||
if (param === "ARIZE_SPACE_KEY" || param === "arize_space_id") dynamicParamName = "arize_space_id";
|
||||
}
|
||||
params.append(dynamicParamName, value);
|
||||
}
|
||||
});
|
||||
|
||||
const response = await serviceHealthCheck(accessToken, selectedCallback);
|
||||
|
||||
if (response) {
|
||||
setConnectionStatus('success');
|
||||
NotificationsManager.success('Connection test successful!');
|
||||
} else {
|
||||
setConnectionStatus('error');
|
||||
NotificationsManager.error('Connection test failed');
|
||||
}
|
||||
} catch (error) {
|
||||
setConnectionStatus('error');
|
||||
NotificationsManager.fromBackend(error);
|
||||
} finally {
|
||||
setTestingConnection(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveAlerts = async () => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
|
|
@ -777,7 +646,12 @@ const Settings: React.FC<SettingsPageProps> = ({
|
|||
rules={[{ required: true, message: "Please select a callback" }]}
|
||||
>
|
||||
<Select
|
||||
onChange={handleCallbackSelectChange}
|
||||
onChange={(value) => {
|
||||
const selectedCallback = allCallbacks[value];
|
||||
if (selectedCallback) {
|
||||
handleSelectedCallbackChange(selectedCallback);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{Object.entries(Callbacks).map(
|
||||
([callbackEnum, callbackDisplayName]) => (
|
||||
|
|
@ -813,76 +687,21 @@ const Settings: React.FC<SettingsPageProps> = ({
|
|||
</FormItem>
|
||||
|
||||
{selectedCallbackParams &&
|
||||
selectedCallbackParams.map((param) => {
|
||||
const fieldType = getFieldType(param, selectedCallback || "");
|
||||
const isRequired = isRequiredField(param, selectedCallback || "");
|
||||
const placeholder = getFieldPlaceholder(param, selectedCallback || "");
|
||||
const label = getFieldLabel(param);
|
||||
|
||||
return (
|
||||
<FormItem
|
||||
label={
|
||||
<span className="flex items-center space-x-2">
|
||||
<span>{label}</span>
|
||||
{!isRequired && (
|
||||
<span className="text-xs text-gray-500">(Optional)</span>
|
||||
)}
|
||||
{selectedCallback === "arize" && (param === "ARIZE_API_KEY" || param === "arize_api_key") && (
|
||||
<a
|
||||
href="https://docs.arize.com/arize/api-reference/authentication"
|
||||
target="_blank"
|
||||
className="text-blue-500 text-xs hover:underline"
|
||||
>
|
||||
Get API Key
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
name={param}
|
||||
key={param}
|
||||
rules={[
|
||||
{
|
||||
required: isRequired,
|
||||
message: `Please enter the value for ${label}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{fieldType === "password" ? (
|
||||
<Input.Password placeholder={placeholder} />
|
||||
) : (
|
||||
<Input placeholder={placeholder} />
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
})}
|
||||
|
||||
{selectedCallback && selectedCallbackParams.length > 0 && (
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
{connectionStatus === 'success' && (
|
||||
<span className="flex items-center text-green-600 text-sm">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
||||
Connection successful
|
||||
</span>
|
||||
)}
|
||||
{connectionStatus === 'error' && (
|
||||
<span className="flex items-center text-red-600 text-sm">
|
||||
<span className="w-2 h-2 bg-red-500 rounded-full mr-2"></span>
|
||||
Connection failed
|
||||
</span>
|
||||
)}
|
||||
<Button2
|
||||
type="default"
|
||||
loading={testingConnection}
|
||||
onClick={testCallbackConnection}
|
||||
disabled={!selectedCallbackParams.some(param => {
|
||||
const value = addForm.getFieldValue(param);
|
||||
return isRequiredField(param, selectedCallback || "") ? value : true;
|
||||
})}
|
||||
selectedCallbackParams.map((param) => (
|
||||
<FormItem
|
||||
label={param}
|
||||
name={param}
|
||||
key={param}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please enter the value for " + param,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{testingConnection ? 'Testing...' : 'Test Connection'}
|
||||
</Button2>
|
||||
</div>
|
||||
)}
|
||||
<Input.Password />
|
||||
</FormItem>
|
||||
))}
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button2 htmlType="submit">Save</Button2>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue