mod map;
mod sse;
#[cfg(test)]
mod tests;
use std::collections::HashSet;
use std::time::Duration;
use anyllm_translate::openai::responses::ResponsesRequest;
use anyllm_translate::openai::ChatCompletionRequest;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::sse::{Event, KeepAlive, Sse};
use axum::response::{IntoResponse, Response};
use axum::Json;
use futures::stream::{Stream, StreamExt};
use tokenizer::ReasoningEffort;
use self::map::{may_produce_reasoning, responses_to_chat_request, responses_warnings};
use self::sse::{
build_response, close_message_events, completed_event, content_part_added_event, created_event,
failed_event, function_call_events, function_call_item, message_item, open_message_event,
text_delta_event,
};
use crate::guardrails::run_guarded;
use crate::handler::{
error_response, plan, reasoning_effort, request_id, stream_blocking, tool_names, AppState,
GenError, Piece,
};
const SSE_KEEP_ALIVE: Duration = Duration::from_secs(15);
pub async fn responses(
State(state): State<crate::ServerState>,
tag: Option<axum::Extension<crate::observe::RequestTag>>,
lease_slot: axum::Extension<crate::handler::RequestLeaseSlot>,
Json(request): Json<ResponsesRequest>,
) -> Response {
let model = match crate::handler::resolve_backend(
&state,
&lease_slot.0,
tag.map(|t| t.0),
Some(request.model.as_str()),
request.stream.unwrap_or(false),
)
.await
{
Ok(m) => m,
Err(response) => return response,
};
let chat_request = match responses_to_chat_request(&request) {
Ok(r) => r,
Err(e) => return error_response(StatusCode::BAD_REQUEST, e),
};
if let Err(e) = plan(&model, &chat_request) {
return error_response(StatusCode::BAD_REQUEST, e);
}
let tools = tool_names(&chat_request);
let effort = reasoning_effort(&chat_request, model.default_reasoning())
.unwrap_or_else(|_| model.default_reasoning());
let streaming = request.stream.unwrap_or(false);
let degraded = responses_warnings(&request, streaming && may_produce_reasoning(&model, effort));
let request_model = request.model.clone();
let cancel = crate::cancel::new_cancel();
let mut response = if streaming {
stream_response(model, chat_request, tools, effort, request_model, cancel)
} else {
let mut guard = crate::cancel::CancelGuard::new(cancel.clone());
let result = run_guarded(model, &chat_request, effort, cancel).await;
guard.defuse();
match result {
Ok(generated) => Json(build_response(
request_id("resp_"),
request_model,
generated,
))
.into_response(),
Err(e) => return crate::handler::gen_error_response(e),
}
};
if let Some(note) = degraded.and_then(|v| v.parse().ok()) {
response
.headers_mut()
.insert(crate::DEGRADATION_HEADER, note);
}
response
}
#[allow(clippy::too_many_arguments)]
fn stream_response(
model: AppState,
chat_request: ChatCompletionRequest,
tools: HashSet<String>,
effort: ReasoningEffort,
request_model: String,
cancel: crate::cancel::Cancel,
) -> Response {
if !tools.is_empty() && model.guardrails().active() {
return buffered_stream_response(model, chat_request, effort, request_model, cancel);
}
let planned = match plan(&model, &chat_request) {
Ok(p) => p,
Err(e) => return error_response(StatusCode::BAD_REQUEST, e),
};
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Event>();
let id = request_id("resp_");
let model_name = request_model;
let cancel_for_task = cancel.clone();
let gate = model.generation_queue();
let cancel_for_gate = cancel.clone();
tokio::spawn(async move {
let _ = crate::queue::run_gated(gate, &cancel_for_gate, move || {
let send = |ev: Event| {
if tx.send(ev).is_err() {
cancel_for_task.store(true, std::sync::atomic::Ordering::Relaxed);
}
};
send(created_event(&id, &model_name));
let mut message_open = false;
let mut message_item_id = String::new();
let mut text_acc = String::new();
let mut output_index: u32 = 0;
let mut items: Vec<serde_json::Value> = Vec::new();
let flag = crate::cancel::as_cancel_flag(&cancel_for_task);
let result = stream_blocking(
&model,
&planned.prompt_ids,
&planned.config,
planned.images.as_ref(),
&tools,
effort,
&flag,
&mut |piece| match piece {
Piece::Reasoning(_) => {}
Piece::Text(delta) => {
if delta.is_empty() {
return;
}
if !message_open {
message_item_id = format!("msg_{output_index}");
send(open_message_event(&message_item_id, output_index));
send(content_part_added_event(&message_item_id, output_index));
message_open = true;
}
text_acc.push_str(&delta);
send(text_delta_event(&message_item_id, output_index, &delta));
}
Piece::Tool(call) => {
if message_open {
close_message_events(&send, &message_item_id, output_index, &text_acc);
items.push(message_item(&text_acc, "completed", &message_item_id));
message_open = false;
text_acc.clear();
output_index += 1;
}
function_call_events(&send, &call, output_index);
items.push(function_call_item(&call, "completed"));
output_index += 1;
}
},
);
match result {
Ok((decode, _)) if decode.reason == runtime::StopReason::Cancelled => (),
Ok((decode, _)) => {
if message_open {
close_message_events(&send, &message_item_id, output_index, &text_acc);
items.push(message_item(&text_acc, "completed", &message_item_id));
}
send(completed_event(&id, &model_name, &decode, &items));
}
Err(e) => send(failed_event(&e.to_string())),
}
})
.await;
});
let stream: std::pin::Pin<
Box<dyn Stream<Item = Result<Event, std::convert::Infallible>> + Send>,
> = Box::pin(
crate::cancel::CancelOnDrop::new(
tokio_stream::wrappers::UnboundedReceiverStream::new(rx),
cancel,
)
.map(Ok),
);
Sse::new(stream)
.keep_alive(KeepAlive::new().interval(SSE_KEEP_ALIVE))
.into_response()
}
fn buffered_stream_response(
model: AppState,
chat_request: ChatCompletionRequest,
effort: ReasoningEffort,
request_model: String,
cancel: crate::cancel::Cancel,
) -> Response {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Event>();
let id = request_id("resp_");
let model_name = request_model;
let cancel_for_stream = cancel.clone();
tokio::spawn(async move {
let send = move |ev: Event| {
let _ = tx.send(ev);
};
send(created_event(&id, &model_name));
match run_guarded(model, &chat_request, effort, cancel).await {
Ok(generated) if generated.decode.reason == runtime::StopReason::Cancelled => (),
Ok(generated) => {
let mut output_index: u32 = 0;
let mut items: Vec<serde_json::Value> = Vec::new();
if !generated.text.is_empty() {
let item_id = format!("msg_{output_index}");
send(open_message_event(&item_id, output_index));
send(content_part_added_event(&item_id, output_index));
send(text_delta_event(&item_id, output_index, &generated.text));
close_message_events(&send, &item_id, output_index, &generated.text);
items.push(message_item(&generated.text, "completed", &item_id));
output_index += 1;
}
for call in &generated.calls {
function_call_events(&send, call, output_index);
items.push(function_call_item(call, "completed"));
output_index += 1;
}
send(completed_event(&id, &model_name, &generated.decode, &items));
}
Err(e) => {
let message = match e {
GenError::Runtime(e) => e.to_string(),
GenError::Join(m) => m,
};
send(failed_event(&message));
}
}
});
let stream: std::pin::Pin<
Box<dyn Stream<Item = Result<Event, std::convert::Infallible>> + Send>,
> = Box::pin(
crate::cancel::CancelOnDrop::new(
tokio_stream::wrappers::UnboundedReceiverStream::new(rx),
cancel_for_stream,
)
.map(Ok),
);
Sse::new(stream)
.keep_alive(KeepAlive::new().interval(SSE_KEEP_ALIVE))
.into_response()
}