use crate::AppState;
use crate::streaming::{self, ChatStream};
use axum::{
Router,
extract::State,
http::StatusCode,
response::{IntoResponse, Json, Response},
routing::{get, post},
};
use cortiq_core::TaskMask;
use cortiq_engine::SamplerConfig;
use cortiq_engine::dsv41_encoding::{self, EncodeOptions, ReasoningEffort, ThinkingMode};
use cortiq_engine::dsv41_vision::{self, VisionConfig};
use cortiq_engine::pipeline::GenerateResult;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
pub fn routes() -> Router<Arc<AppState>> {
Router::new()
.route("/v1/models", get(list_models))
.route("/v1/chat/completions", post(chat_completions))
.route("/v1/completions", post(completions))
}
#[derive(Serialize)]
struct ModelsResponse {
object: String,
data: Vec<ModelEntry>,
}
#[derive(Serialize)]
struct ModelEntry {
id: String,
object: String,
created: u64,
owned_by: String,
}
async fn list_models(State(state): State<Arc<AppState>>) -> Json<ModelsResponse> {
let arch = state.runtime.model().arch();
Json(ModelsResponse {
object: "list".to_string(),
data: vec![ModelEntry {
id: format!("{}-cortiq", arch.arch_name),
object: "model".to_string(),
created: chrono::Utc::now().timestamp() as u64,
owned_by: "cortiq".to_string(),
}],
})
}
#[derive(Deserialize, Serialize, Clone)]
struct ChatMessage {
role: String,
#[serde(default)]
content: Option<MessageContent>,
#[serde(default, skip_serializing_if = "Option::is_none")]
tool_calls: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
tool_call_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
reasoning_content: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
response_format: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
task: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
wo_eos: Option<bool>,
}
#[derive(Deserialize, Serialize, Clone)]
#[serde(untagged)]
enum MessageContent {
Text(String),
Blocks(Vec<ContentBlock>),
}
#[derive(Deserialize, Serialize, Clone)]
struct ContentBlock {
#[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
text: Option<String>,
#[serde(flatten)]
extra: serde_json::Map<String, serde_json::Value>,
}
impl MessageContent {
fn text(&self) -> String {
match self {
Self::Text(s) => s.clone(),
Self::Blocks(bs) => bs
.iter()
.filter_map(|b| b.text.as_deref())
.collect::<Vec<_>>()
.join("\n"),
}
}
fn to_value(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_else(|_| serde_json::Value::String(self.text()))
}
}
impl From<String> for MessageContent {
fn from(s: String) -> Self {
Self::Text(s)
}
}
#[derive(Deserialize)]
struct CortiqExtension {
task: Option<String>,
#[serde(default)]
class_tokens: Option<Vec<String>>,
}
#[derive(Serialize)]
struct Usage {
prompt_tokens: u32,
completion_tokens: u32,
total_tokens: u32,
}
#[derive(Serialize)]
struct CortiqResponseMeta {
task_used: String,
sparsity: f32,
active_layers: usize,
execution_mode: String,
tokens_per_second: f64,
#[serde(skip_serializing_if = "Option::is_none")]
classification: Option<ClassTokenClassification>,
}
#[derive(Serialize)]
struct ClassTokenScore {
token: String,
token_id: u32,
logit: f32,
probability: f32,
}
#[derive(Serialize)]
struct ClassTokenClassification {
label: String,
confidence: f32,
scores: Vec<ClassTokenScore>,
}
#[derive(Serialize)]
struct ApiError {
error: ApiErrorBody,
}
#[derive(Serialize)]
struct ApiErrorBody {
message: String,
r#type: String,
}
fn error_response(status: StatusCode, message: impl Into<String>) -> Response {
(
status,
Json(ApiError {
error: ApiErrorBody {
message: message.into(),
r#type: "invalid_request_error".to_string(),
},
}),
)
.into_response()
}
async fn run_generation(
state: Arc<AppState>,
prompt_ids: Vec<u32>,
vl_inputs: Option<dsv41_vision::PreparedVlInputs>,
max_tokens: usize,
mask: Option<TaskMask>,
sampler_config: SamplerConfig,
on_token: Option<cortiq_engine::TokenCallback>,
) -> Result<(GenerateResult, f64), Response> {
let started = std::time::Instant::now();
crate::ood::touch_last_request();
let ood_on = crate::ood::ood_dir().is_some();
let ood_state = state.clone();
let mut slot = state.slots.acquire().await;
let remote = state.remote.clone();
let outcome = tokio::task::spawn_blocking(move || {
if ood_on {
let text = ood_state.tokenizer.decode(&prompt_ids);
let p = &mut *slot.pipe;
crate::ood::record_if_ood(ood_state.runtime.model(), p, &prompt_ids, &text);
}
cortiq_engine::gpu::set_current_device(slot.device);
let p = &mut *slot.pipe;
p.set_sampler_config(sampler_config);
match remote {
Some(rm) => {
if vl_inputs.is_some() {
return Err(
"V4.1 multimodal generation is not supported with a network-split pipeline"
.to_string(),
);
}
if mask.is_some() {
return Err(
"this server runs a network split (--peer): task masks are not \
supported yet — use task 'general'"
.to_string(),
);
}
let mut rm = rm.lock().expect("remote segment mutex");
cortiq_net::generate_split(p, &mut rm, &prompt_ids, max_tokens, None, on_token)
.map(
|(r, st)| {
if st.remote_steps > 0 {
tracing::info!(
"net: prefill {:.0} ms ({} of {} pos) · {} trips · {:.2} ms avg · {:.0}% of decode",
st.prefill_s * 1e3,
st.prefilled,
r.prompt_tokens,
st.remote_steps,
st.net_s * 1e3 / st.remote_steps as f64,
100.0 * st.net_s / st.decode_s.max(1e-9),
);
}
r
},
)
}
None => match vl_inputs.as_ref() {
Some(inputs) => p.generate_from_vl(inputs, max_tokens, mask.as_ref(), on_token),
None => p.generate_from_ids(&prompt_ids, max_tokens, mask.as_ref(), on_token),
},
}
})
.await;
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
match outcome {
Ok(Ok(result)) => Ok((result, elapsed_ms)),
Ok(Err(e)) => Err(error_response(StatusCode::BAD_REQUEST, e)),
Err(join_err) => {
tracing::error!("generation task panicked: {join_err}");
Err(error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"generation failed",
))
}
}
}
async fn run_classification(
state: Arc<AppState>,
prompt_ids: Vec<u32>,
mask: Option<TaskMask>,
labels: Vec<(String, u32)>,
) -> Result<(ClassTokenClassification, f64), Response> {
if state.remote.is_some() {
return Err(error_response(
StatusCode::BAD_REQUEST,
"class-token scoring is not available with --peer",
));
}
let started = std::time::Instant::now();
let mut slot = state.slots.acquire().await;
let outcome = tokio::task::spawn_blocking(move || {
cortiq_engine::gpu::set_current_device(slot.device);
let logits = slot.pipe.prefill_next_logits(&prompt_ids, mask.as_ref());
let selected: Vec<f32> = labels.iter().map(|(_, id)| logits[*id as usize]).collect();
let max = selected.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let denom: f32 = selected.iter().map(|value| (*value - max).exp()).sum();
let mut scores: Vec<ClassTokenScore> = labels
.into_iter()
.zip(selected)
.map(|((token, token_id), logit)| ClassTokenScore {
token,
token_id,
logit,
probability: (logit - max).exp() / denom,
})
.collect();
scores.sort_by(|left, right| right.probability.total_cmp(&left.probability));
ClassTokenClassification {
label: scores[0].token.clone(),
confidence: scores[0].probability,
scores,
}
})
.await;
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
match outcome {
Ok(classification) => Ok((classification, elapsed_ms)),
Err(join_err) => {
tracing::error!("classification task panicked: {join_err}");
Err(error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"classification failed",
))
}
}
}
fn request_sampler(
temperature: Option<f32>,
top_p: Option<f32>,
seed: Option<u64>,
) -> Result<SamplerConfig, Response> {
if temperature.is_some_and(|v| !v.is_finite() || v < 0.0) {
return Err(error_response(
StatusCode::BAD_REQUEST,
"temperature must be finite and >= 0",
));
}
if top_p.is_some_and(|v| !v.is_finite() || !(0.0..=1.0).contains(&v)) {
return Err(error_response(
StatusCode::BAD_REQUEST,
"top_p must be finite and between 0 and 1",
));
}
let mut config = SamplerConfig::default();
if let Some(v) = temperature {
config.temperature = v;
}
if let Some(v) = top_p {
config.top_p = v;
}
config.seed = seed;
Ok(config)
}
#[derive(Deserialize)]
struct ChatCompletionsRequest {
model: String,
messages: Vec<ChatMessage>,
temperature: Option<f32>,
top_p: Option<f32>,
seed: Option<u64>,
#[serde(default = "default_max_tokens")]
max_tokens: u32,
#[serde(default)]
stream: bool,
#[serde(default)]
enable_thinking: Option<bool>,
#[serde(default)]
reasoning_effort: Option<serde_json::Value>,
#[serde(default)]
chat_template_kwargs: Option<serde_json::Value>,
#[serde(default)]
cortiq: Option<CortiqExtension>,
#[serde(default)]
tools: Option<Vec<serde_json::Value>>,
#[serde(default)]
tool_choice: Option<serde_json::Value>,
}
impl ChatCompletionsRequest {
fn effective_tools(&self) -> Option<&[serde_json::Value]> {
if matches!(
self.tool_choice.as_ref().and_then(|v| v.as_str()),
Some("none")
) {
return None;
}
match self.tools.as_deref() {
Some([]) | None => None,
Some(ts) => Some(ts),
}
}
fn thinking(&self) -> Option<bool> {
self.enable_thinking.or_else(|| {
self.chat_template_kwargs
.as_ref()
.and_then(|k| k.get("enable_thinking"))
.and_then(|v| v.as_bool())
})
}
}
#[derive(Debug)]
pub struct PromptIngress {
pub token_ids: Vec<u32>,
pub token_types: Vec<i8>,
pub images: Vec<dsv41_vision::ImageInput>,
pub dsv41: bool,
}
fn message_to_json(message: &ChatMessage) -> serde_json::Value {
let content = message
.content
.as_ref()
.map(MessageContent::to_value)
.unwrap_or_else(|| serde_json::Value::String(String::new()));
let mut object = serde_json::json!({
"role": message.role,
"content": content,
});
if let Some(tool_calls) = &message.tool_calls {
let mut tool_calls = tool_calls.clone();
if let Some(calls) = tool_calls.as_array_mut() {
for call in calls {
if let Some(arguments) = call
.get_mut("function")
.and_then(|function| function.get_mut("arguments"))
{
if let Some(string) = arguments.as_str() {
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(string) {
if parsed.is_object() {
*arguments = parsed;
}
}
}
}
}
}
object["tool_calls"] = tool_calls;
}
if let Some(tool_call_id) = &message.tool_call_id {
object["tool_call_id"] = serde_json::Value::String(tool_call_id.clone());
}
if let Some(name) = &message.name {
object["name"] = serde_json::Value::String(name.clone());
}
if let Some(reasoning) = &message.reasoning_content {
object["reasoning_content"] = serde_json::Value::String(reasoning.clone());
}
if let Some(response_format) = &message.response_format {
object["response_format"] = response_format.clone();
}
if let Some(task) = &message.task {
object["task"] = serde_json::Value::String(task.clone());
}
if let Some(wo_eos) = message.wo_eos {
object["wo_eos"] = serde_json::Value::Bool(wo_eos);
}
object
}
fn request_messages_json(
messages: &[ChatMessage],
parse_dsv41_images: bool,
) -> Result<Vec<serde_json::Value>, String> {
messages
.iter()
.map(|message| {
let mut value = message_to_json(message);
if !parse_dsv41_images {
value["content"] = serde_json::Value::String(
message
.content
.as_ref()
.map(|content| content.text())
.unwrap_or_default(),
);
}
if parse_dsv41_images {
if let Some(text) = value.get("content").and_then(|v| v.as_str()) {
let blocks = dsv41_encoding::parse_tagged_text(text)
.map_err(|error| error.to_string())?;
if blocks.is_array() {
value["content"] = blocks;
}
}
}
Ok(value)
})
.collect()
}
pub fn encode_dsv41_messages(
messages: &[serde_json::Value],
tools: Option<&[serde_json::Value]>,
enable_thinking: Option<bool>,
reasoning_effort: Option<&serde_json::Value>,
tokenizer: &cortiq_engine::tokenizer::Tokenizer,
config: &VisionConfig,
) -> Result<PromptIngress, String> {
let mut messages = messages.to_vec();
if let Some(tools) = tools.filter(|items| !items.is_empty()) {
let tools_value = serde_json::Value::Array(tools.to_vec());
if messages
.first()
.and_then(|message| message.get("role"))
.and_then(|role| role.as_str())
== Some("system")
{
messages[0]["tools"] = tools_value;
} else {
messages.insert(
0,
serde_json::json!({"role":"system", "content":"", "tools":tools_value}),
);
}
}
let thinking_mode = if enable_thinking == Some(true)
|| (enable_thinking.is_none() && reasoning_effort.is_some())
{
ThinkingMode::Thinking
} else {
ThinkingMode::Chat
};
let effort = reasoning_effort
.map(ReasoningEffort::from_json)
.transpose()
.map_err(|error| error.to_string())?;
let encoded = dsv41_encoding::encode_messages(
&messages,
&EncodeOptions {
thinking_mode,
reasoning_effort: effort,
..EncodeOptions::default()
},
)
.map_err(|error| error.to_string())?;
let prepared =
dsv41_vision::prepare_vl_inputs(&encoded.prompt, &encoded.images, tokenizer, config)
.map_err(|error| error.to_string())?;
Ok(PromptIngress {
token_ids: prepared.token_ids,
token_types: prepared.token_types,
images: prepared.images,
dsv41: true,
})
}
#[derive(Serialize)]
struct ChatCompletionsResponse {
id: String,
object: String,
created: u64,
model: String,
choices: Vec<ChatChoice>,
usage: Usage,
#[serde(skip_serializing_if = "Option::is_none")]
cortiq: Option<CortiqResponseMeta>,
}
#[derive(Serialize)]
struct ChatChoice {
index: u32,
message: ChatMessage,
finish_reason: String,
}
async fn chat_completions(
State(state): State<Arc<AppState>>,
Json(req): Json<ChatCompletionsRequest>,
) -> Response {
if req.messages.is_empty() {
return error_response(StatusCode::BAD_REQUEST, "messages must not be empty");
}
let (task_used, request_mask) =
if let Some(task) = req.cortiq.as_ref().and_then(|c| c.task.as_deref()) {
let Some(mask) = state.runtime.masks().get(task).cloned() else {
return error_response(
StatusCode::NOT_FOUND,
format!("Task mask '{task}' not found"),
);
};
(task.to_string(), Some(mask))
} else {
state.runtime.active_selection().await
};
let mut sampler_config = match request_sampler(req.temperature, req.top_p, req.seed) {
Ok(config) => config,
Err(response) => return response,
};
if req.thinking() == Some(false) {
let think_tokens = state.tokenizer.encode("<think>");
sampler_config.suppress_tokens.extend(think_tokens);
}
let (prompt_ids, prompt_ingress) = if let Some(source) =
state.runtime.model().arch().deepseek_v41.as_ref()
{
let config = match VisionConfig::from_source(source) {
Ok(config) => config,
Err(error) => return error_response(StatusCode::BAD_REQUEST, error),
};
let messages = match request_messages_json(&req.messages, true) {
Ok(messages) => messages,
Err(error) => return error_response(StatusCode::BAD_REQUEST, error),
};
let ingress = match encode_dsv41_messages(
&messages,
req.effective_tools(),
req.thinking(),
req.reasoning_effort.as_ref(),
&state.tokenizer,
&config,
) {
Ok(ingress) => ingress,
Err(error) => return error_response(StatusCode::BAD_REQUEST, error),
};
(ingress.token_ids.clone(), Some(ingress))
} else {
let prompt_ids = {
let mut msgs: Vec<serde_json::Value> = req
.messages
.iter()
.map(|m| {
let mut o = serde_json::json!({
"role": m.role,
"content": m.content.as_ref().map(|c| c.text()).unwrap_or_default(),
});
if let Some(tc) = &m.tool_calls {
let mut tc = tc.clone();
if let Some(arr) = tc.as_array_mut() {
for call in arr {
if let Some(args) = call
.get_mut("function")
.and_then(|f| f.get_mut("arguments"))
{
if let Some(s) = args.as_str() {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(s)
{
if v.is_object() {
*args = v;
}
}
}
}
}
}
o["tool_calls"] = tc;
}
if let Some(id) = &m.tool_call_id {
o["tool_call_id"] = serde_json::json!(id);
}
if let Some(n) = &m.name {
o["name"] = serde_json::json!(n);
}
o
})
.collect();
eprintln!("[serve] thinking={:?}", req.thinking());
if req.thinking() == Some(false) {
let has_system = msgs.iter().any(|m| m["role"] == "system");
let directive = "Answer directly and concisely. Do NOT reason, think step-by-step, or explain your process. Output ONLY the final answer.";
if has_system {
if let Some(m) = msgs.iter_mut().find(|m| m["role"] == "system") {
let cur = m["content"].as_str().unwrap_or_default();
m["content"] = serde_json::json!(format!("{directive}\n\n{cur}"));
}
} else {
msgs.insert(
0,
serde_json::json!({"role": "system", "content": directive}),
);
}
}
eprintln!("[serve] msgs[0]={:?}", msgs.first());
state
.tokenizer
.apply_chat_template_json(&msgs, req.effective_tools(), req.thinking())
};
(prompt_ids, None)
};
let vl_inputs = prompt_ingress.map(|ingress| dsv41_vision::PreparedVlInputs {
token_ids: ingress.token_ids,
token_types: ingress.token_types,
images: ingress.images,
});
let dsv41 = vl_inputs.is_some();
let request_id = format!("cmf-{}", uuid::Uuid::new_v4());
let created = chrono::Utc::now().timestamp() as u64;
let max_tokens = req.max_tokens as usize;
let dsv41_thinking = dsv41
&& req.thinking() != Some(false)
&& (req.thinking() == Some(true) || req.reasoning_effort.is_some());
if let Some(class_tokens) = req
.cortiq
.as_ref()
.and_then(|extension| extension.class_tokens.clone())
{
if vl_inputs
.as_ref()
.is_some_and(|inputs| !inputs.images.is_empty())
{
return error_response(
StatusCode::BAD_REQUEST,
"cortiq.class_tokens does not support image prompts",
);
}
if req.stream {
return error_response(
StatusCode::BAD_REQUEST,
"cortiq.class_tokens does not support stream=true",
);
}
if !(2..=32).contains(&class_tokens.len()) {
return error_response(
StatusCode::BAD_REQUEST,
"cortiq.class_tokens must contain 2..32 labels",
);
}
let mut labels = Vec::with_capacity(class_tokens.len());
for token in class_tokens {
let ids = state.tokenizer.encode(&token);
if ids.len() != 1 {
return error_response(
StatusCode::BAD_REQUEST,
format!("class label {token:?} must encode to exactly one token, got {ids:?}"),
);
}
labels.push((token, ids[0]));
}
let prompt_tokens = prompt_ids.len() as u32;
let (classification, elapsed_ms) =
match run_classification(state.clone(), prompt_ids, request_mask, labels).await {
Ok(result) => result,
Err(response) => return response,
};
let status = state.runtime.status().await;
let task_mask = state.runtime.masks().get(&task_used);
return Json(ChatCompletionsResponse {
id: request_id,
object: "chat.completion".to_string(),
created,
model: req.model,
choices: vec![ChatChoice {
index: 0,
message: ChatMessage {
role: "assistant".to_string(),
content: Some(classification.label.clone().into()),
tool_calls: None,
tool_call_id: None,
name: None,
reasoning_content: None,
response_format: None,
task: None,
wo_eos: None,
},
finish_reason: "stop".to_string(),
}],
usage: Usage {
prompt_tokens,
completion_tokens: 0,
total_tokens: prompt_tokens,
},
cortiq: Some(CortiqResponseMeta {
task_used,
sparsity: task_mask.map(|mask| mask.sparsity).unwrap_or(0.0),
active_layers: task_mask
.map(|mask| mask.active_layer_count())
.unwrap_or(state.runtime.model().arch().num_layers),
execution_mode: format!("{:?}", status.execution_mode),
tokens_per_second: prompt_tokens as f64 / (elapsed_ms / 1000.0).max(1e-9),
classification: Some(classification),
}),
})
.into_response();
}
if req.stream {
let tool_names: Vec<String> = req
.effective_tools()
.map(|ts| {
ts.iter()
.filter_map(|t| t["function"]["name"].as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let (tx, stream) = ChatStream::new(64);
let model = req.model.clone();
let id = request_id.clone();
let state2 = state.clone();
tokio::spawn(async move {
let _ = tx
.send(streaming::StreamChunk {
id: id.clone(),
object: "chat.completion.chunk".to_string(),
created,
model: model.clone(),
choices: vec![streaming::StreamChoice {
index: 0,
delta: streaming::StreamDelta {
role: Some("assistant".to_string()),
content: None,
tool_calls: None,
},
finish_reason: None,
}],
usage: None,
})
.await;
let tx_tokens = tx.clone();
let id2 = id.clone();
let model2 = model.clone();
let filter_shared = std::sync::Arc::new(std::sync::Mutex::new(String::new()));
let filter_cb = filter_shared.clone();
let mut filter_passthrough = req.thinking() != Some(false);
let tools_active = req.effective_tools().is_some();
let mut tool_tail = String::new();
let mut tool_holding = false;
const MARK: &str = "<tool_call>";
let callback: cortiq_engine::TokenCallback = Box::new(move |token: &str| {
if filter_passthrough {
if tools_active {
if tool_holding {
return !tx_tokens.is_closed();
}
tool_tail.push_str(token);
if let Some(pos) = tool_tail.find(MARK) {
tool_holding = true;
let before = tool_tail[..pos].to_string();
if !before.is_empty() {
let chunk = streaming::token_chunk(&id2, &model2, &before, created);
return tx_tokens.blocking_send(chunk).is_ok();
}
return !tx_tokens.is_closed();
}
if tool_tail.len() > MARK.len() {
let cut = tool_tail.len() - (MARK.len() - 1);
let safe_cut = (0..=cut)
.rev()
.find(|&c| tool_tail.is_char_boundary(c))
.unwrap_or(0);
if safe_cut > 0 {
let out: String = tool_tail.drain(..safe_cut).collect();
let chunk = streaming::token_chunk(&id2, &model2, &out, created);
return tx_tokens.blocking_send(chunk).is_ok();
}
}
return !tx_tokens.is_closed();
}
let chunk = streaming::token_chunk(&id2, &model2, token, created);
return tx_tokens.blocking_send(chunk).is_ok();
}
let mut filter_buf = filter_cb.lock().expect("think filter buf");
filter_buf.push_str(token);
if let Some(pos) = filter_buf.find("</think>") {
let tail = filter_buf[pos + "</think>".len()..].to_string();
filter_buf.clear();
filter_passthrough = true;
let tail_trimmed = tail.trim_start_matches('\n');
if !tail_trimmed.is_empty() {
let chunk = streaming::token_chunk(&id2, &model2, tail_trimmed, created);
return tx_tokens.blocking_send(chunk).is_ok();
}
return true;
}
if filter_buf.len() > 100 && !filter_buf.contains("<think>") {
let b = std::mem::take(&mut *filter_buf);
filter_passthrough = true;
let chunk = streaming::token_chunk(&id2, &model2, &b, created);
return tx_tokens.blocking_send(chunk).is_ok();
}
true
});
let outcome = run_generation(
state2.clone(),
prompt_ids,
vl_inputs,
max_tokens,
request_mask,
sampler_config,
Some(callback),
)
.await;
match outcome {
Ok((result, elapsed_ms)) => {
let leftover = std::mem::take(&mut *filter_shared.lock().expect("filter buf"));
if !leftover.is_empty() {
let out = if !leftover.contains("<think>") {
leftover
} else if let Some(pos) = leftover.find("</think>") {
leftover[pos + "</think>".len()..]
.trim_start_matches('\n')
.to_string()
} else {
String::new()
};
if !out.is_empty() {
let _ = tx
.send(streaming::token_chunk(&id, &model, &out, created))
.await;
}
}
state2
.runtime
.record_generation(result.tokens_generated, elapsed_ms, elapsed_ms)
.await;
let (plain2, mut calls, _) = if dsv41 {
extract_dsv41_result(&result, dsv41_thinking, &state2.tokenizer)
} else {
let (plain, calls) = extract_tool_calls(&result.text);
(plain, calls, None)
};
if calls.is_empty() {
if let Some(c) = bare_call_fallback(&plain2, &tool_names) {
calls = vec![c];
}
}
let finish = if calls.is_empty() {
result.finish_reason.clone()
} else {
let _ = tx
.send(streaming::tool_calls_chunk(
&id,
&model,
serde_json::Value::Array(
calls
.into_iter()
.enumerate()
.map(|(i, mut c)| {
c["index"] = serde_json::json!(i);
c
})
.collect(),
),
created,
))
.await;
"tool_calls".to_string()
};
let _ = tx
.send(streaming::usage_chunk(
&id,
&model,
created,
result.prompt_tokens as u32,
result.tokens_generated as u32,
))
.await;
let _ = tx
.send(streaming::finish_chunk(&id, &model, &finish, created))
.await;
}
Err(_) => {
let _ = tx
.send(streaming::finish_chunk(&id, &model, "error", created))
.await;
}
}
});
stream.into_sse().into_response()
} else {
let (result, elapsed_ms) = match run_generation(
state.clone(),
prompt_ids,
vl_inputs,
max_tokens,
request_mask,
sampler_config,
None,
)
.await
{
Ok(r) => r,
Err(resp) => return resp,
};
state
.runtime
.record_generation(result.tokens_generated, elapsed_ms, elapsed_ms)
.await;
let status = state.runtime.status().await;
let task_mask = state.runtime.masks().get(&task_used);
let cortiq_meta = req.cortiq.as_ref().map(|_| CortiqResponseMeta {
task_used,
sparsity: task_mask.map(|m| m.sparsity).unwrap_or(0.0),
active_layers: task_mask
.map(|m| m.active_layer_count())
.unwrap_or(state.runtime.model().arch().num_layers),
execution_mode: format!("{:?}", status.execution_mode),
tokens_per_second: result.tokens_generated as f64 / (elapsed_ms / 1000.0).max(1e-9),
classification: None,
});
let (mut plain, mut calls, reasoning_content) = if dsv41 {
extract_dsv41_result(&result, dsv41_thinking, &state.tokenizer)
} else if req.thinking() == Some(false) {
let content = strip_think_block(&result.text);
let (plain, calls) = extract_tool_calls(&content);
(plain, calls, None)
} else {
let (plain, calls) = extract_tool_calls(&result.text);
(plain, calls, None)
};
if calls.is_empty() {
if let Some(names) = req.effective_tools().map(|ts| {
ts.iter()
.filter_map(|t| t["function"]["name"].as_str().map(String::from))
.collect::<Vec<_>>()
}) {
if let Some(c) = bare_call_fallback(&plain, &names) {
calls = vec![c];
plain = String::new();
}
}
}
let made_calls = !calls.is_empty();
Json(ChatCompletionsResponse {
id: request_id,
object: "chat.completion".to_string(),
created,
model: req.model,
choices: vec![ChatChoice {
index: 0,
message: ChatMessage {
role: "assistant".to_string(),
content: if made_calls && plain.is_empty() {
None
} else {
Some(plain.into())
},
tool_calls: made_calls.then_some(serde_json::Value::Array(calls)),
tool_call_id: None,
name: None,
reasoning_content,
response_format: None,
task: None,
wo_eos: None,
},
finish_reason: if made_calls {
"tool_calls".to_string()
} else {
result.finish_reason.clone()
},
}],
usage: Usage {
prompt_tokens: result.prompt_tokens as u32,
completion_tokens: result.tokens_generated as u32,
total_tokens: (result.prompt_tokens + result.tokens_generated) as u32,
},
cortiq: cortiq_meta,
})
.into_response()
}
}
fn bare_call_fallback(text: &str, allowed: &[String]) -> Option<serde_json::Value> {
let t = text.trim();
if !t.starts_with('{') || !t.ends_with('}') {
return None;
}
let v: serde_json::Value = serde_json::from_str(t).ok()?;
let name = v.get("name")?.as_str()?;
if !allowed.iter().any(|a| a == name) {
return None;
}
let args = v.get("arguments").cloned().unwrap_or(serde_json::json!({}));
if !args.is_object() {
return None;
}
Some(serde_json::json!({
"id": format!("call_{}", uuid::Uuid::new_v4().simple()),
"type": "function",
"function": {
"name": name,
"arguments": serde_json::to_string(&args).unwrap_or_else(|_| "{}".into()),
}
}))
}
fn parse_xml_function(body: &str) -> Option<serde_json::Value> {
let t = body.trim();
let name_start = t.find("<function=")? + "<function=".len();
let name_end = t[name_start..].find(['>', '\n'])? + name_start;
let name = t[name_start..name_end].trim().to_string();
if name.is_empty() {
return None;
}
let mut args = serde_json::Map::new();
let mut rest = &t[name_end..];
while let Some(ps) = rest.find("<parameter=") {
let key_start = ps + "<parameter=".len();
let key_end = rest[key_start..].find('>')? + key_start;
let key = rest[key_start..key_end].trim().to_string();
let val_start = key_end + 1;
let val_end = rest[val_start..].find("</parameter>")? + val_start;
let val = rest[val_start..val_end]
.strip_prefix('\n')
.unwrap_or(&rest[val_start..val_end])
.strip_suffix('\n')
.unwrap_or(&rest[val_start..val_end])
.to_string();
args.insert(key, serde_json::Value::String(val));
rest = &rest[val_end + "</parameter>".len()..];
}
Some(serde_json::json!({"name": name, "arguments": args}))
}
fn extract_tool_calls(text: &str) -> (String, Vec<serde_json::Value>) {
const OPEN: &str = "<tool_call>";
const CLOSE: &str = "</tool_call>";
let mut rest = text;
let mut plain = String::new();
let mut calls = Vec::new();
while let Some(i) = rest.find(OPEN) {
let Some(j) = rest[i + OPEN.len()..].find(CLOSE) else {
break; };
let body = rest[i + OPEN.len()..i + OPEN.len() + j].trim();
let after = &rest[i + OPEN.len() + j + CLOSE.len()..];
let parsed = serde_json::from_str::<serde_json::Value>(body)
.ok()
.or_else(|| parse_xml_function(body));
match parsed {
Some(v) if v.get("name").map(|n| n.is_string()) == Some(true) => {
plain.push_str(&rest[..i]);
let args = v.get("arguments").cloned().unwrap_or(serde_json::json!({}));
calls.push(serde_json::json!({
"id": format!("call_{}", uuid::Uuid::new_v4().simple()),
"type": "function",
"function": {
"name": v["name"],
"arguments": serde_json::to_string(&args).unwrap_or_else(|_| "{}".into()),
}
}));
}
_ => {
plain.push_str(&rest[..i + OPEN.len() + j + CLOSE.len()]);
}
}
rest = after;
}
plain.push_str(rest);
(plain.trim().to_string(), calls)
}
fn extract_dsv41_completion(
text: &str,
thinking: bool,
) -> (String, Vec<serde_json::Value>, Option<String>) {
let mode = if thinking {
ThinkingMode::Thinking
} else {
ThinkingMode::Chat
};
let mut wire = text.to_string();
if !wire.contains(dsv41_encoding::EOS_TOKEN) {
wire.push_str(dsv41_encoding::EOS_TOKEN);
}
let Ok(message) = dsv41_encoding::parse_message_from_completion_text(&wire, mode) else {
return (text.to_string(), Vec::new(), None);
};
let content = message
.get("content")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_string();
let reasoning = message
.get("reasoning_content")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.is_empty())
.map(str::to_string);
let calls = message
.get("tool_calls")
.and_then(serde_json::Value::as_array)
.map(|calls| {
calls
.iter()
.enumerate()
.map(|(index, call)| {
let mut call = call.clone();
if call.get("id").is_none() {
call["id"] = serde_json::json!(format!("call_dsv41_{index}"));
}
call
})
.collect()
})
.unwrap_or_default();
(content, calls, reasoning)
}
fn extract_dsv41_result(
result: &GenerateResult,
thinking: bool,
tokenizer: &cortiq_engine::tokenizer::Tokenizer,
) -> (String, Vec<serde_json::Value>, Option<String>) {
if !result.token_ids.is_empty() {
let wire = tokenizer.decode_for_protocol(&result.token_ids);
return extract_dsv41_completion(&wire, thinking);
}
extract_dsv41_completion(&result.text, thinking)
}
fn strip_think_block(s: &str) -> String {
let mut rest = s;
if let Some(pos) = rest.find("</think>") {
rest = &rest[pos + "</think>".len()..];
} else if rest.starts_with("<think>") {
return String::new();
}
rest.trim_start_matches('\n').to_string()
}
#[derive(Deserialize)]
struct CompletionsRequest {
model: String,
prompt: String,
temperature: Option<f32>,
#[serde(default = "default_max_tokens")]
max_tokens: u32,
}
#[derive(Serialize)]
struct CompletionsResponse {
id: String,
object: String,
created: u64,
model: String,
choices: Vec<CompletionChoice>,
usage: Usage,
}
#[derive(Serialize)]
struct CompletionChoice {
text: String,
index: u32,
finish_reason: String,
}
async fn completions(
State(state): State<Arc<AppState>>,
Json(req): Json<CompletionsRequest>,
) -> Response {
let prompt_ids = state.tokenizer.encode(&req.prompt);
let sampler_config = match request_sampler(req.temperature, None, None) {
Ok(config) => config,
Err(response) => return response,
};
let (_, request_mask) = state.runtime.active_selection().await;
let (result, elapsed_ms) = match run_generation(
state.clone(),
prompt_ids,
None,
req.max_tokens as usize,
request_mask,
sampler_config,
None,
)
.await
{
Ok(r) => r,
Err(resp) => return resp,
};
state
.runtime
.record_generation(result.tokens_generated, elapsed_ms, elapsed_ms)
.await;
Json(CompletionsResponse {
id: format!("cmf-{}", uuid::Uuid::new_v4()),
object: "text_completion".to_string(),
created: chrono::Utc::now().timestamp() as u64,
model: req.model,
choices: vec![CompletionChoice {
text: result.text,
index: 0,
finish_reason: result.finish_reason,
}],
usage: Usage {
prompt_tokens: result.prompt_tokens as u32,
completion_tokens: result.tokens_generated as u32,
total_tokens: (result.prompt_tokens + result.tokens_generated) as u32,
},
})
.into_response()
}
fn default_max_tokens() -> u32 {
256
}
#[cfg(test)]
mod tests {
#[test]
fn tool_calls_extract_single() {
let (text, calls) = extract_tool_calls(
"<tool_call>\n{\"name\": \"get_weather\", \"arguments\": {\"city\": \"Paris\"}}\n</tool_call>",
);
assert_eq!(text, "");
assert_eq!(calls.len(), 1);
assert_eq!(calls[0]["function"]["name"], "get_weather");
let args: serde_json::Value =
serde_json::from_str(calls[0]["function"]["arguments"].as_str().unwrap()).unwrap();
assert_eq!(args["city"], "Paris");
assert!(calls[0]["id"].as_str().unwrap().starts_with("call_"));
}
#[test]
fn tool_calls_extract_text_and_multiple() {
let (text, calls) = extract_tool_calls(
"Let me check both.\n<tool_call>\n{\"name\": \"a\", \"arguments\": {}}\n</tool_call>\n<tool_call>\n{\"name\": \"b\", \"arguments\": {\"x\": 1}}\n</tool_call>",
);
assert_eq!(text, "Let me check both.");
assert_eq!(calls.len(), 2);
assert_eq!(calls[1]["function"]["name"], "b");
}
#[test]
fn tool_calls_malformed_body_stays_text() {
let (text, calls) = extract_tool_calls("<tool_call>\nnot json at all\n</tool_call> done");
assert!(calls.is_empty());
assert!(
text.contains("not json at all"),
"broken call must stay readable text"
);
}
#[test]
fn tool_calls_unterminated_stays_text() {
let (text, calls) = extract_tool_calls("<tool_call>\n{\"name\": \"a\"");
assert!(calls.is_empty());
assert!(
text.contains("<tool_call>"),
"truncated output must not vanish"
);
}
use super::*;
#[test]
fn sampler_options_start_from_defaults_and_validate_ranges() {
let changed = request_sampler(Some(0.2), Some(0.5), Some(7)).unwrap();
assert_eq!(changed.temperature, 0.2);
assert_eq!(changed.top_p, 0.5);
assert_eq!(changed.seed, Some(7));
let fresh = request_sampler(None, None, None).unwrap();
let defaults = SamplerConfig::default();
assert_eq!(fresh.temperature, defaults.temperature);
assert_eq!(fresh.top_p, defaults.top_p);
assert_eq!(fresh.seed, None);
assert!(request_sampler(Some(-1.0), None, None).is_err());
assert!(request_sampler(None, Some(1.1), None).is_err());
}
#[test]
fn content_accepts_both_a_string_and_a_block_array() {
let flat: ChatMessage =
serde_json::from_str(r#"{"role":"user","content":"hello"}"#).unwrap();
assert_eq!(flat.content.as_ref().unwrap().text(), "hello");
let blocks: ChatMessage = serde_json::from_str(
r#"{"role":"user","content":[
{"type":"text","text":"file context"},
{"type":"text","text":"the question"}]}"#,
)
.unwrap();
assert_eq!(
blocks.content.as_ref().unwrap().text(),
"file context\nthe question"
);
let mixed: ChatMessage = serde_json::from_str(
r#"{"role":"user","content":[
{"type":"image_url","image_url":{"url":"data:x"}},
{"type":"text","text":"describe"}]}"#,
)
.unwrap();
assert_eq!(mixed.content.as_ref().unwrap().text(), "describe");
let req: ChatCompletionsRequest = serde_json::from_str(
r#"{"model":"m","messages":[
{"role":"system","content":[{"type":"text","text":"sys"}]},
{"role":"user","content":"hi"}]}"#,
)
.unwrap();
assert_eq!(req.messages[0].content.as_ref().unwrap().text(), "sys");
assert_eq!(req.messages[1].content.as_ref().unwrap().text(), "hi");
}
}