So I have a language model that can either be OpenAI/Ollama or a customer user implemented one. I want to create a summarize_text function that takes this model dynamically during runtime but hitting with compile time issues primarily around this trait cannot be made into an object..
Inline code:
use async_trait::async_trait;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AIError {
#[error("Rate limit exceeded: retry after {0} seconds")]
RateLimit(u64),
#[error("Model not found: {0}")]
ModelNotFound(String),
#[error("Service unavailable: {0}")]
ServiceUnavailable(String),
#[error("Unknown error: {0}")]
Unknown(String),
}
#[derive(Clone, Debug)]
pub struct GenerateTextRequest<M: LanguageModel> {
model: M,
prompt: String,
}
#[async_trait]
pub trait LanguageModel: Clone + Send + Sync {
async fn generate_text(&self, request: &GenerateTextRequest<Self>) -> Result<String, AIError>
where
Self: Sized;
}
#[derive(Clone, Debug)]
struct OpenAILanguageModel {}
#[async_trait]
impl LanguageModel for OpenAILanguageModel {
async fn generate_text(&self, _request: &GenerateTextRequest<Self>) -> Result<String, AIError> {
Ok("openai".to_string())
}
}
#[derive(Clone, Debug)]
struct OllamaLanguageModel {}
#[async_trait]
impl LanguageModel for OllamaLanguageModel {
async fn generate_text(&self, _request: &GenerateTextRequest<Self>) -> Result<String, AIError> {
Ok("ollama".to_string())
}
}
pub async fn generate_text<M, R>(request: R) -> Result<String, AIError>
where
M: LanguageModel,
R: Into<GenerateTextRequest<M>>,
{
let request = request.into();
// TODO: Implement retry logic here
request.model.generate_text(&request).await
}
pub async fn summarize<M, R>(model: &M, text: &str) -> Result<String, AIError>
where
M: LanguageModel,
R: Into<GenerateTextRequest<M>>,
{
let request = GenerateTextRequest {
model: model.clone(),
prompt: format!("Summarize the following text:\n\n{}", text.to_string()),
};
generate_text(request).await
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// if openai key defined in env use open ai model else use ollama model
let openai_key = std::env::var("OPENAI_KEY").ok();
let model = if openai_key.is_some() {
Box::new(OpenAILanguageModel {}) as Box<dyn LanguageModel>
} else {
Box::new(OllamaLanguageModel {}) as Box<dyn LanguageModel>
};
summarize(&model, "This is a test").await?;
Ok(())
}
What is a good way to enable dynamic runtime selection of LanguageModel for summarize here. Enum of OpenAI and Ollama won't work here since in the future others crates might provide more Models and we can't know the list all up front.
error[E0038]: the trait `LanguageModel` cannot be made into an object
--> src/main.rs:81:45
|
81 | Box::new(OpenAILanguageModel {}) as Box<dyn LanguageModel>
| ^^^^^^^^^^^^^^^^^^^^^^ `LanguageModel` cannot be made into an object
|
note: for a trait to be "dyn-compatible" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:26:26
|
26 | pub trait LanguageModel: Clone + Send + Sync {
| ------------- ^^^^^ ...because it requires `Self: Sized`
| |
| this trait cannot be made into an object...
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `LanguageModel` for this new enum and using it instead:
OpenAILanguageModel
OllamaLanguageModel
= note: `LanguageModel` can be implemented in other crates; if you want to support your users passing their own types here, you can't refer to a specific type
error[E0038]: the trait `LanguageModel` cannot be made into an object