Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gateway/src/routes/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use axum::{
routing::{delete, get, patch, post},
};
use metrics_exporter_prometheus::PrometheusHandle;
use tensorzero_core::endpoints::anthropic_compatible::build_anthropic_compatible_routes;
use tensorzero_core::endpoints::openai_compatible::build_openai_compatible_routes;
use tensorzero_core::observability::OtelEnabledRoutes;
use tensorzero_core::{endpoints, utils::gateway::AppStateData};
Expand All @@ -34,6 +35,7 @@ pub fn build_otel_enabled_routes() -> (OtelEnabledRoutes, Router<AppStateData>)
("/feedback", post(endpoints::feedback::feedback_handler)),
];
routes.extend(build_openai_compatible_routes().routes);
routes.extend(build_anthropic_compatible_routes().routes);
let mut router = Router::new();
let mut route_names = Vec::with_capacity(routes.len());
for (path, handler) in routes {
Expand Down
100 changes: 100 additions & 0 deletions tensorzero-core/src/endpoints/anthropic_compatible/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Messages endpoint handler for Anthropic-compatible API.
//!
//! This module implements the HTTP handler for the `/anthropic/v1/messages` endpoint,
//! which provides Anthropic Messages API compatibility. It handles request validation,
//! parameter parsing, inference execution, and response formatting for both streaming
//! and non-streaming requests.

use axum::Json;
use axum::body::Body;
use axum::extract::State;
use axum::response::sse::Sse;
use axum::response::{IntoResponse, Response};
use axum::{Extension, debug_handler};

use crate::endpoints::anthropic_compatible::types::messages::AnthropicMessageResponse;
use crate::endpoints::anthropic_compatible::types::messages::AnthropicMessagesParams;
use crate::endpoints::anthropic_compatible::types::streaming::prepare_serialized_anthropic_events;
use crate::endpoints::inference::{InferenceOutput, Params, inference};
use crate::error::{Error, ErrorDetails};
use crate::utils::gateway::{AppState, AppStateData, StructuredJson};
use tensorzero_auth::middleware::RequestApiKeyExtension;

/// A handler for the Anthropic-compatible messages endpoint
#[debug_handler(state = AppStateData)]
pub async fn messages_handler(
State(AppStateData {
config,
http_client,
clickhouse_connection_info,
postgres_connection_info,
deferred_tasks,
rate_limiting_manager,
..
}): AppState,
api_key_ext: Option<Extension<RequestApiKeyExtension>>,
StructuredJson(anthropic_params): StructuredJson<AnthropicMessagesParams>,
) -> Result<Response<Body>, Error> {
// Validate that max_tokens is set (it's required in Anthropic's API)
if anthropic_params.max_tokens == 0 {
return Err(Error::new(
ErrorDetails::InvalidAnthropicCompatibleRequest {
message: "`max_tokens` is required and must be greater than 0".to_string(),
},
));
}

let include_raw_usage = anthropic_params.tensorzero_include_raw_usage;
let include_raw_response = anthropic_params.tensorzero_include_raw_response;

let params = Params::try_from_anthropic(anthropic_params)?;

// The prefix for the response's `model` field depends on the inference target
let response_model_prefix = match (&params.function_name, &params.model_name) {
(Some(function_name), None) => Ok::<String, Error>(format!(
"tensorzero::function_name::{function_name}::variant_name::",
)),
(None, Some(_model_name)) => Ok("tensorzero::model_name::".to_string()),
(Some(_), Some(_)) => Err(ErrorDetails::InvalidInferenceTarget {
message: "Only one of `function_name` or `model_name` can be provided".to_string(),
}
.into()),
(None, None) => Err(ErrorDetails::InvalidInferenceTarget {
message: "Either `function_name` or `model_name` must be provided".to_string(),
}
.into()),
}?;

let response = Box::pin(inference(
config,
&http_client,
clickhouse_connection_info,
postgres_connection_info,
deferred_tasks,
rate_limiting_manager,
params,
api_key_ext,
))
.await?
.output;

match response {
InferenceOutput::NonStreaming(response) => {
let anthropic_response =
AnthropicMessageResponse::from((response, response_model_prefix));
Ok(Json(anthropic_response).into_response())
}
InferenceOutput::Streaming(stream) => {
let anthropic_stream = prepare_serialized_anthropic_events(
stream,
response_model_prefix,
true, // include_usage
include_raw_usage,
include_raw_response,
);
Ok(Sse::new(anthropic_stream)
.keep_alive(axum::response::sse::KeepAlive::new())
.into_response())
}
}
}
47 changes: 47 additions & 0 deletions tensorzero-core/src/endpoints/anthropic_compatible/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Anthropic-compatible API endpoints.
//!
//! This module provides compatibility with Anthropic's Messages API format.
//! It handles routing, request/response conversion, and provides the main entry
//! points for Anthropic-compatible requests.

pub mod messages;
pub mod models;
pub mod types;

use messages::messages_handler;
use models::models_handler;

use axum::Router;
use axum::routing::{get, post};

use crate::endpoints::RouteHandlers;
use crate::utils::gateway::AppStateData;

/// Constructs (but does not register) all of our Anthropic-compatible endpoints.
/// The `RouterExt::register_anthropic_compatible_routes` is a convenience method
/// to register all of the routes on a router.
///
/// Alternatively, the returned `RouteHandlers` can be inspected (e.g. to allow middleware to see the route paths)
/// and then manually registered on a router.
pub fn build_anthropic_compatible_routes() -> RouteHandlers {
RouteHandlers {
routes: vec![
("/anthropic/v1/messages", post(messages_handler)),
("/anthropic/v1/models", get(models_handler)),
],
}
}

pub trait RouterExt {
/// Applies our Anthropic-compatible endpoints to the router.
fn register_anthropic_compatible_routes(self) -> Self;
}

impl RouterExt for Router<AppStateData> {
fn register_anthropic_compatible_routes(mut self) -> Self {
for (path, handler) in build_anthropic_compatible_routes().routes {
self = self.route(path, handler);
}
self
}
}
Loading