mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
test(core): move route tests to integration suite
This commit is contained in:
parent
277a71cd30
commit
3406ca1e11
16 changed files with 112 additions and 67 deletions
|
|
@ -32,6 +32,3 @@ pub async fn audio_transcription(request: AudioTranscriptionRequest<'_>) -> Resu
|
|||
.await
|
||||
.into_result()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ pub(super) async fn execute_settled_request(
|
|||
/// second kind has already been billed, and a host that keeps a reference
|
||||
/// implementation must not retry those, so collapse them to one variant that
|
||||
/// can only mean the provider was already called.
|
||||
pub(super) fn as_response_error(err: Error) -> Error {
|
||||
pub fn as_response_error(err: Error) -> Error {
|
||||
match err {
|
||||
already @ (Error::InvalidResponse(_) | Error::Http { .. }) => already,
|
||||
other => Error::InvalidResponse(other.to_string()),
|
||||
|
|
@ -101,7 +101,7 @@ pub(super) fn as_response_error(err: Error) -> Error {
|
|||
}
|
||||
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
pub(super) async fn signed_headers(
|
||||
pub async fn signed_headers(
|
||||
request: &ProviderChatCompletionsRequest,
|
||||
body: &[u8],
|
||||
) -> Result<Vec<(String, String)>, Error> {
|
||||
|
|
@ -158,7 +158,7 @@ pub(super) async fn signed_headers(
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "bedrock-auth"))]
|
||||
pub(super) async fn signed_headers(
|
||||
pub async fn signed_headers(
|
||||
request: &ProviderChatCompletionsRequest,
|
||||
_body: &[u8],
|
||||
) -> Result<Vec<(String, String)>, Error> {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ pub mod types;
|
|||
use serde_json::{Map, Value};
|
||||
|
||||
use handler::execute_chat_completions_provider_call;
|
||||
pub use handler::{as_response_error, signed_headers};
|
||||
use request::{parse_messages, resolve_provider_config, resolve_request};
|
||||
use types::{ChatCompletionsRequest, ChatCompletionsResponse};
|
||||
|
||||
|
|
@ -156,6 +157,3 @@ pub fn chat_completions_decline_reason(
|
|||
.unsupported_reason(&messages, optional_params)
|
||||
.map(|reason| reason.0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ pub(super) fn parse_messages(messages: Value) -> Result<Vec<ChatMessage>, Error>
|
|||
.map_err(|err| Error::InvalidRequest(format!("invalid chat completions messages: {err}")))
|
||||
}
|
||||
|
||||
pub(super) fn resolve_request(
|
||||
pub fn resolve_request(
|
||||
request: ChatCompletionsRequest<'_>,
|
||||
) -> Result<ResolvedChatCompletionsRequest<'_>, Error> {
|
||||
let (model, config) = resolve_provider_config(request.model, request.custom_llm_provider)?;
|
||||
|
|
@ -116,7 +116,7 @@ fn validate_environment(
|
|||
Ok((headers, auth))
|
||||
}
|
||||
|
||||
pub(super) fn build_provider_request(
|
||||
pub fn build_provider_request(
|
||||
request: ResolvedChatCompletionsRequest<'_>,
|
||||
) -> Result<ProviderChatCompletionsRequest, Error> {
|
||||
let (headers, auth) = validate_environment(&request, &request.model, request.config)?;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub struct ChatCompletionsRequest<'a> {
|
|||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
pub(super) struct ResolvedChatCompletionsRequest<'a> {
|
||||
pub struct ResolvedChatCompletionsRequest<'a> {
|
||||
pub(super) model: String,
|
||||
pub(super) config: &'static dyn ChatCompletionsProviderConfig,
|
||||
pub(super) messages: Vec<ChatMessage>,
|
||||
|
|
@ -33,16 +33,16 @@ pub(super) struct ResolvedChatCompletionsRequest<'a> {
|
|||
pub(super) timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
pub(super) struct ProviderChatCompletionsRequest {
|
||||
pub(super) model: String,
|
||||
pub(super) config: &'static dyn ChatCompletionsProviderConfig,
|
||||
pub(super) url: String,
|
||||
pub(super) body: Value,
|
||||
pub(super) upstream_headers: Vec<(String, String)>,
|
||||
pub(super) auth: ChatCompletionsAuth,
|
||||
pub struct ProviderChatCompletionsRequest {
|
||||
pub model: String,
|
||||
pub config: &'static dyn ChatCompletionsProviderConfig,
|
||||
pub url: String,
|
||||
pub body: Value,
|
||||
pub upstream_headers: Vec<(String, String)>,
|
||||
pub auth: ChatCompletionsAuth,
|
||||
#[cfg_attr(not(feature = "bedrock-auth"), allow(dead_code))]
|
||||
pub(super) optional_params: Map<String, Value>,
|
||||
pub(super) timeout: Option<Duration>,
|
||||
pub optional_params: Map<String, Value>,
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
/// The provider-shaped request body a config produces. Named rather than a bare
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use serde_json::{Map, Value};
|
|||
|
||||
use super::transformation::AnthropicMessagesProviderConfig;
|
||||
|
||||
pub(super) use crate::http_utils::{has_bearer_auth, has_header, truncate_error_body};
|
||||
pub use crate::http_utils::{has_bearer_auth, has_header, truncate_error_body};
|
||||
|
||||
const HEADER_CONTEXT: &str = "messages";
|
||||
|
||||
#[tracing::instrument(target = "litellm::function_trace", level = "trace", skip_all)]
|
||||
pub(super) fn messages_provider_config(
|
||||
pub fn messages_provider_config(
|
||||
provider: &str,
|
||||
) -> Option<&'static dyn AnthropicMessagesProviderConfig> {
|
||||
match provider {
|
||||
|
|
@ -21,7 +21,7 @@ pub(super) fn messages_provider_config(
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn string_headers(
|
||||
pub fn string_headers(
|
||||
extra_headers: Option<Map<String, Value>>,
|
||||
) -> Result<Vec<(String, String)>, Error> {
|
||||
shared_string_headers(HEADER_CONTEXT, extra_headers)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
use crate::Error;
|
||||
use crate::constants::ANTHROPIC_MESSAGES_PROVIDER;
|
||||
mod client;
|
||||
mod common_utils;
|
||||
pub mod common_utils;
|
||||
mod handler;
|
||||
pub mod lifecycle;
|
||||
pub mod request;
|
||||
|
|
@ -64,6 +64,3 @@ pub async fn messages_stream(request: MessagesRequest) -> Result<StreamingCall,
|
|||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -214,7 +214,3 @@ impl ChatCompletionsProviderConfig for AnthropicChatCompletionsConfig {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -302,7 +302,3 @@ impl ChatCompletionsProviderConfig for BedrockChatCompletionsConfig {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
pub mod transformation;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
use super::*;
|
||||
use crate::Error;
|
||||
use serde_json::json;
|
||||
use litellm_core::Error;
|
||||
use litellm_core::chat_completions::transformation::{
|
||||
ChatCompletionsAuth, ChatCompletionsProviderConfig, Unsupported,
|
||||
};
|
||||
use litellm_core::chat_completions::types::{
|
||||
ChatCompletionsResponse, ChatMessage, ProviderChatResponseData,
|
||||
};
|
||||
use litellm_core::providers::anthropic::chat_completions::transformation::*;
|
||||
use serde_json::{Map, Value, json};
|
||||
|
||||
fn messages(value: Value) -> Vec<ChatMessage> {
|
||||
serde_json::from_value(value).expect("valid messages")
|
||||
|
|
@ -1,20 +1,31 @@
|
|||
#![recursion_limit = "256"]
|
||||
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use std::io::{Read, Write};
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use std::net::TcpListener;
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use std::sync::{Arc, Mutex};
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use std::thread;
|
||||
|
||||
use serde_json::{Map, json};
|
||||
|
||||
use super::types::AudioTranscriptionRequest;
|
||||
use super::{AudioRoute, AudioRouteRequest, DefaultAudioServices, audio_transcription};
|
||||
use crate::Error;
|
||||
use crate::integrations::custom_guardrail::{
|
||||
use litellm_core::Error;
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use litellm_core::audio_transcription::audio_transcription;
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use litellm_core::audio_transcription::types::AudioTranscriptionRequest;
|
||||
use litellm_core::audio_transcription::{AudioRoute, AudioRouteRequest, DefaultAudioServices};
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
use litellm_core::integrations::custom_guardrail::{
|
||||
CustomGuardrail, GuardrailContext, GuardrailDecision, GuardrailEventHook, GuardrailFuture,
|
||||
GuardrailRequest,
|
||||
};
|
||||
use crate::lifecycle::{ExecutedCall, RouteProjection};
|
||||
use litellm_core::lifecycle::{ExecutedCall, RouteProjection};
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
async fn bedrock_request_is_signed_and_contains_audio() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
|
||||
let address = listener.local_addr().expect("address");
|
||||
|
|
@ -56,10 +67,12 @@ async fn bedrock_request_is_signed_and_contains_audio() {
|
|||
server.join().expect("server");
|
||||
}
|
||||
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
struct ReplacingGuardrail {
|
||||
calls: Mutex<Vec<&'static str>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
impl CustomGuardrail for ReplacingGuardrail {
|
||||
fn guardrail_name(&self) -> &str {
|
||||
"audio-test"
|
||||
|
|
@ -96,6 +109,7 @@ impl CustomGuardrail for ReplacingGuardrail {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "bedrock-auth")]
|
||||
async fn route_owns_guardrail_provider_and_terminal_sequence() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
|
||||
let address = listener.local_addr().expect("address");
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
use super::*;
|
||||
use crate::Error;
|
||||
use serde_json::json;
|
||||
#![cfg(feature = "bedrock-auth")]
|
||||
|
||||
use litellm_core::Error;
|
||||
use litellm_core::chat_completions::transformation::{
|
||||
ChatCompletionsAuth, ChatCompletionsProviderConfig, Unsupported,
|
||||
};
|
||||
use litellm_core::chat_completions::types::{
|
||||
ChatCompletionsResponse, ChatMessage, ProviderChatResponseData,
|
||||
};
|
||||
use litellm_core::providers::bedrock::chat_completions::transformation::*;
|
||||
use serde_json::{Map, Value, json};
|
||||
|
||||
fn messages(value: Value) -> Vec<ChatMessage> {
|
||||
serde_json::from_value(value).expect("valid messages")
|
||||
|
|
@ -37,6 +45,10 @@ fn reason(msgs: Value, opts: Value) -> Option<Unsupported> {
|
|||
|
||||
#[test]
|
||||
fn builds_the_converse_body_python_builds() {
|
||||
assert_eq!(
|
||||
BEDROCK_CHAT_COMPLETIONS_CONFIG.request_body_behavior(),
|
||||
litellm_core::lifecycle::RequestBodyBehavior::SERIALIZED_AT_BUILD
|
||||
);
|
||||
let body = transform(
|
||||
json!([
|
||||
{"role": "system", "content": "be terse"},
|
||||
|
|
@ -553,7 +565,7 @@ fn leaves_a_complete_converse_url_untouched() {
|
|||
|
||||
#[test]
|
||||
fn host_supplied_credentials_outrank_ambient_profile_and_role_state() {
|
||||
use crate::providers::bedrock::aws_base::host_supplied_credentials;
|
||||
use litellm_core::providers::bedrock::aws_base::host_supplied_credentials;
|
||||
|
||||
let supplied = params(json!({
|
||||
"aws_access_key_id": "AKIAHOST",
|
||||
|
|
@ -561,9 +573,15 @@ fn host_supplied_credentials_outrank_ambient_profile_and_role_state() {
|
|||
"aws_session_token": "hosttoken"
|
||||
}));
|
||||
let credentials = host_supplied_credentials(&supplied).expect("host credentials");
|
||||
let different_secret = host_supplied_credentials(¶ms(json!({
|
||||
"aws_access_key_id": "AKIAHOST",
|
||||
"aws_secret_access_key": "different",
|
||||
"aws_session_token": "hosttoken"
|
||||
})))
|
||||
.expect("credentials with a different secret");
|
||||
assert_eq!(credentials.access_key_id(), "AKIAHOST");
|
||||
assert_eq!(credentials.secret_access_key(), "hostsecret");
|
||||
assert_eq!(credentials.session_token(), Some("hosttoken"));
|
||||
assert_ne!(credentials, different_secret);
|
||||
|
||||
// Without a full static pair there is nothing to honor, so the core falls
|
||||
// back to deriving credentials itself.
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
use serde_json::{Map, Value, json};
|
||||
|
||||
use crate::error::Error;
|
||||
use litellm_core::error::Error;
|
||||
|
||||
use super::request::{build_provider_request, resolve_request};
|
||||
use super::transformation::ChatCompletionsAuth;
|
||||
use super::types::{ChatCompletionsRequest, ProviderChatCompletionsRequest};
|
||||
use litellm_core::chat_completions::request::{build_provider_request, resolve_request};
|
||||
use litellm_core::chat_completions::transformation::ChatCompletionsAuth;
|
||||
use litellm_core::chat_completions::types::{
|
||||
ChatCompletionsRequest, ProviderChatCompletionsRequest,
|
||||
};
|
||||
|
||||
fn build_chat_completions_request(
|
||||
request: ChatCompletionsRequest<'_>,
|
||||
|
|
@ -54,6 +56,10 @@ fn resolves_the_provider_from_the_model_prefix() {
|
|||
assert_eq!(built.model, "claude-sonnet-4-5");
|
||||
assert_eq!(built.url, "https://api.anthropic.com/v1/messages");
|
||||
assert_eq!(built.body["model"], json!("claude-sonnet-4-5"));
|
||||
assert_eq!(
|
||||
built.config.request_body_behavior(),
|
||||
litellm_core::lifecycle::RequestBodyBehavior::STRUCTURED_AT_SEND
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -325,7 +331,7 @@ async fn a_forwarded_client_header_does_not_enter_the_bedrock_signature() {
|
|||
json!("abc-123"),
|
||||
)]));
|
||||
let built = build_chat_completions_request(call).expect("builds");
|
||||
let signed = super::handler::signed_headers(&built, br#"{"a":1}"#)
|
||||
let signed = litellm_core::chat_completions::signed_headers(&built, br#"{"a":1}"#)
|
||||
.await
|
||||
.expect("signs");
|
||||
|
||||
|
|
@ -376,7 +382,7 @@ async fn a_forwarded_header_the_signer_computes_declines_to_python() {
|
|||
call.api_key = None;
|
||||
call.extra_headers = Some(Map::from_iter([(forwarded.to_string(), json!("forged"))]));
|
||||
let built = build_chat_completions_request(call).expect("builds");
|
||||
let error = super::handler::signed_headers(&built, br#"{"a":1}"#)
|
||||
let error = litellm_core::chat_completions::signed_headers(&built, br#"{"a":1}"#)
|
||||
.await
|
||||
.expect_err("{forwarded} should decline instead of being signed");
|
||||
assert!(
|
||||
|
|
@ -492,7 +498,9 @@ fn decline_reason(
|
|||
Value::Object(map) => map,
|
||||
other => panic!("params must be an object, got {other}"),
|
||||
};
|
||||
super::chat_completions_decline_reason(model, provider, messages, ¶ms)
|
||||
litellm_core::chat_completions::chat_completions_decline_reason(
|
||||
model, provider, messages, ¶ms,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -595,7 +603,7 @@ mod round_trip {
|
|||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use crate::chat_completions::chat_completions;
|
||||
use litellm_core::chat_completions::chat_completions;
|
||||
|
||||
async fn read_http_request(socket: &mut TcpStream) -> String {
|
||||
let mut request = Vec::new();
|
||||
|
|
@ -800,7 +808,7 @@ mod round_trip {
|
|||
|
||||
#[test]
|
||||
fn response_errors_collapse_to_one_variant_that_can_only_mean_already_sent() {
|
||||
use crate::chat_completions::handler::as_response_error;
|
||||
use litellm_core::chat_completions::as_response_error;
|
||||
|
||||
for original in [
|
||||
Error::MissingField("usage"),
|
||||
|
|
@ -4,13 +4,14 @@ use serde_json::{Map, Value, json};
|
|||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use crate::error::Error;
|
||||
use litellm_core::error::Error;
|
||||
|
||||
use super::common_utils::{
|
||||
use litellm_core::messages::common_utils::{
|
||||
has_bearer_auth, has_header, messages_provider_config, string_headers, truncate_error_body,
|
||||
};
|
||||
use super::messages;
|
||||
use super::types::MessagesRequest;
|
||||
use litellm_core::messages::messages;
|
||||
use litellm_core::messages::request::build_endpoint;
|
||||
use litellm_core::messages::types::{MessagesOptions, MessagesRequest};
|
||||
|
||||
async fn read_http_request(socket: &mut TcpStream) -> String {
|
||||
let mut request = Vec::new();
|
||||
|
|
@ -60,6 +61,23 @@ fn provider_config_resolves_anthropic_and_azure_ai() {
|
|||
assert!(messages_provider_config("openai").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_capture_the_structured_body_during_request_building() {
|
||||
let endpoint = build_endpoint(MessagesOptions {
|
||||
model: "anthropic/claude-sonnet-4-5".into(),
|
||||
api_key: Some("test-key".into()),
|
||||
api_base: None,
|
||||
custom_llm_provider: None,
|
||||
extra_headers: None,
|
||||
timeout: None,
|
||||
})
|
||||
.expect("endpoint");
|
||||
assert_eq!(
|
||||
endpoint.request_body_behavior(),
|
||||
litellm_core::lifecycle::RequestBodyBehavior::STRUCTURED_AT_BUILD
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_error_body_caps_long_payloads() {
|
||||
let body = "x".repeat(400);
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
use rstest::{fixture, rstest};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::transformation::*;
|
||||
use crate::ocr::transformation::OcrProviderConfig;
|
||||
use litellm_core::ocr::transformation::OcrProviderConfig;
|
||||
use litellm_core::providers::reducto::ocr::transformation::*;
|
||||
|
||||
#[fixture]
|
||||
fn parse_response() -> Value {
|
||||
Loading…
Add table
Reference in a new issue