use anyllm_translate::openai::streaming::{
ChatCompletionChunk, ChunkChoice, ChunkDelta, ChunkFunctionCall, ChunkToolCall,
};
use anyllm_translate::openai::{
ChatCompletionResponse, ChatContent, ChatMessage, ChatRole, ChatUsage, Choice, FinishReason,
FunctionCall, ToolCall,
};
use tokenizer::ParsedToolCall;
pub fn finish_reason(reason: runtime::StopReason) -> FinishReason {
match reason {
runtime::StopReason::MaxTokens => FinishReason::Length,
runtime::StopReason::ToolCalls => FinishReason::ToolCalls,
runtime::StopReason::EndOfTurn
| runtime::StopReason::Eos
| runtime::StopReason::StopString
| runtime::StopReason::Cancelled => FinishReason::Stop,
}
}
pub fn finish_reason_for(reason: runtime::StopReason, has_calls: bool) -> FinishReason {
if has_calls {
return FinishReason::ToolCalls;
}
finish_reason(reason)
}
pub fn ollama_done_reason(reason: runtime::StopReason, has_calls: bool) -> &'static str {
match finish_reason_for(reason, has_calls) {
FinishReason::Length => "length",
_ => "stop",
}
}
pub(crate) fn tool_call(call: ParsedToolCall) -> ToolCall {
ToolCall {
id: call.id,
call_type: "function".to_string(),
function: FunctionCall {
name: call.name,
arguments: call.arguments_json,
},
}
}
pub fn assistant_message(
text: String,
reasoning: String,
calls: Vec<ParsedToolCall>,
) -> ChatMessage {
ChatMessage {
role: ChatRole::Assistant,
content: Some(ChatContent::Text(text)),
name: None,
tool_calls: if calls.is_empty() {
None
} else {
Some(calls.into_iter().map(tool_call).collect())
},
tool_call_id: None,
refusal: None,
reasoning_content: (!reasoning.is_empty()).then_some(reasoning),
thinking_blocks: None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn completion_response(
id: String,
created: u64,
model: String,
text: String,
reasoning: String,
calls: Vec<ParsedToolCall>,
reason: runtime::StopReason,
prompt_tokens: u32,
completion_tokens: u32,
) -> ChatCompletionResponse {
let has_calls = !calls.is_empty();
ChatCompletionResponse {
id,
object: "chat.completion".to_string(),
model,
choices: vec![Choice {
index: 0,
message: assistant_message(text, reasoning, calls),
finish_reason: Some(finish_reason_for(reason, has_calls)),
logprobs: None,
}],
usage: Some(ChatUsage {
prompt_tokens,
completion_tokens,
total_tokens: prompt_tokens + completion_tokens,
..Default::default()
}),
created: Some(created),
system_fingerprint: None,
service_tier: None,
}
}
pub fn completion_chunk(
id: String,
created: u64,
model: String,
delta: ChunkDelta,
finish: Option<FinishReason>,
) -> ChatCompletionChunk {
ChatCompletionChunk {
id,
object: "chat.completion.chunk".to_string(),
model,
choices: vec![ChunkChoice {
index: 0,
delta,
finish_reason: finish,
logprobs: None,
}],
usage: None,
created: Some(created),
system_fingerprint: None,
error: None,
}
}
pub fn usage_chunk(
id: String,
created: u64,
model: String,
prompt_tokens: u32,
completion_tokens: u32,
) -> ChatCompletionChunk {
ChatCompletionChunk {
id,
object: "chat.completion.chunk".to_string(),
model,
choices: vec![],
usage: Some(ChatUsage {
prompt_tokens,
completion_tokens,
total_tokens: prompt_tokens + completion_tokens,
..Default::default()
}),
created: Some(created),
system_fingerprint: None,
error: None,
}
}
pub fn role_delta() -> ChunkDelta {
ChunkDelta {
role: Some(ChatRole::Assistant),
..Default::default()
}
}
pub fn text_delta(text: String) -> ChunkDelta {
ChunkDelta {
content: Some(text),
..Default::default()
}
}
pub fn reasoning_delta(text: String) -> ChunkDelta {
ChunkDelta {
reasoning_content: Some(text),
..Default::default()
}
}
pub fn tool_call_delta(index: u32, call: ParsedToolCall) -> ChunkDelta {
ChunkDelta {
tool_calls: Some(vec![ChunkToolCall {
index,
id: Some(call.id),
call_type: Some("function".to_string()),
function: Some(ChunkFunctionCall {
name: Some(call.name),
arguments: Some(call.arguments_json),
}),
}]),
..Default::default()
}
}
#[cfg(test)]
#[path = "response_tests.rs"]
mod tests;