mod http;
mod metrics;
pub mod mock;
mod pipeline;
mod sampling;
mod scheduler;
mod stop;
use std::fmt;
use std::sync::Arc;
pub(crate) use http::{LlmState, routes};
pub use metrics::{FinishedCounts, LlmMetrics};
pub use pipeline::{TextPipeline, Tokenizer};
pub use sampling::Sampler;
pub use scheduler::{Logits, Scheduled, SchedulerConfig, SchedulerCounters, StepModel};
pub use stop::StopStrings;
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct Message {
pub role: String,
pub content: String,
}
impl Message {
pub fn new(role: &str, content: &str) -> Self {
Self {
role: role.to_string(),
content: content.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Prompt {
Chat(Vec<Message>),
Text(String),
}
#[derive(Debug, Clone, Default, PartialEq)]
#[non_exhaustive]
pub struct SamplingParams {
pub max_tokens: Option<usize>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub top_k: Option<usize>,
pub seed: Option<u64>,
pub presence_penalty: f32,
pub frequency_penalty: f32,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct GenerateRequest {
pub input: Prompt,
pub params: SamplingParams,
pub stop: Vec<String>,
}
impl GenerateRequest {
pub fn chat(messages: Vec<Message>) -> Self {
Self {
input: Prompt::Chat(messages),
params: SamplingParams::default(),
stop: Vec::new(),
}
}
pub fn text(prompt: &str) -> Self {
Self {
input: Prompt::Text(prompt.to_string()),
params: SamplingParams::default(),
stop: Vec::new(),
}
}
pub fn with_params(mut self, params: SamplingParams) -> Self {
self.params = params;
self
}
pub fn with_stop(mut self, stop: Vec<String>) -> Self {
self.stop = stop;
self
}
}
impl SamplingParams {
pub fn greedy() -> Self {
Self {
temperature: Some(0.0),
..Self::default()
}
}
pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
self.max_tokens = Some(max_tokens);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FinishReason {
Stop,
Length,
}
impl FinishReason {
pub fn as_str(self) -> &'static str {
match self {
Self::Stop => "stop",
Self::Length => "length",
}
}
}
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum Event {
Started {
prompt_tokens: usize,
},
Text(String),
Finished {
reason: FinishReason,
completion_tokens: usize,
},
Error(EngineError),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum EngineError {
TooLong {
prompt_tokens: usize,
max_context: usize,
},
Busy(String),
Invalid(String),
Failed(BoxError),
}
impl std::fmt::Display for EngineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TooLong {
prompt_tokens,
max_context,
} => write!(
f,
"the prompt is {prompt_tokens} tokens, the model's context is {max_context}"
),
Self::Busy(m) | Self::Invalid(m) => f.write_str(m),
Self::Failed(e) => std::fmt::Display::fmt(e, f),
}
}
}
impl std::error::Error for EngineError {}
impl EngineError {
pub fn failed(message: impl Into<String>) -> Self {
Self::Failed(message.into().into())
}
}
impl PartialEq for EngineError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::TooLong {
prompt_tokens: a,
max_context: b,
},
Self::TooLong {
prompt_tokens: c,
max_context: d,
},
) => a == c && b == d,
(Self::Busy(a), Self::Busy(b)) | (Self::Invalid(a), Self::Invalid(b)) => a == b,
(Self::Failed(a), Self::Failed(b)) => a.to_string() == b.to_string(),
_ => false,
}
}
}
pub trait LlmEngine: Send + Sync + fmt::Debug + 'static {
fn name(&self) -> &str;
fn max_context(&self) -> usize;
fn generate(
&self,
request: GenerateRequest,
) -> Result<tokio::sync::mpsc::Receiver<Event>, EngineError>;
fn scheduler_stats(&self) -> Option<&SchedulerCounters> {
None
}
}
impl<E: LlmEngine + ?Sized> LlmEngine for Arc<E> {
fn name(&self) -> &str {
(**self).name()
}
fn max_context(&self) -> usize {
(**self).max_context()
}
fn generate(
&self,
request: GenerateRequest,
) -> Result<tokio::sync::mpsc::Receiver<Event>, EngineError> {
(**self).generate(request)
}
fn scheduler_stats(&self) -> Option<&SchedulerCounters> {
(**self).scheduler_stats()
}
}