. proof-read comments (from devai)

This commit is contained in:
Jeremy Chone
2025-01-06 13:01:08 -08:00
parent a0b3e871bf
commit d00e386600
36 changed files with 86 additions and 85 deletions

View File

@ -109,7 +109,7 @@ impl AdapterKind {
} else if GROQ_MODELS.contains(&model) { } else if GROQ_MODELS.contains(&model) {
return Ok(Self::Groq); return Ok(Self::Groq);
} }
// for now, fallback to Ollama // For now, fallback to Ollama
else { else {
Ok(Self::Ollama) Ok(Self::Ollama)
} }

View File

@ -22,7 +22,7 @@ pub struct AnthropicAdapter;
const MAX_TOKENS_8K: u32 = 8192; const MAX_TOKENS_8K: u32 = 8192;
const MAX_TOKENS_4K: u32 = 4096; const MAX_TOKENS_4K: u32 = 4096;
const ANTRHOPIC_VERSION: &str = "2023-06-01"; const ANTHROPIC_VERSION: &str = "2023-06-01";
const MODELS: &[&str] = &[ const MODELS: &[&str] = &[
"claude-3-5-sonnet-20241022", "claude-3-5-sonnet-20241022",
"claude-3-5-haiku-20241022", "claude-3-5-haiku-20241022",
@ -74,7 +74,7 @@ impl Adapter for AnthropicAdapter {
let headers = vec![ let headers = vec![
// headers // headers
("x-api-key".to_string(), api_key), ("x-api-key".to_string(), api_key),
("anthropic-version".to_string(), ANTRHOPIC_VERSION.to_string()), ("anthropic-version".to_string(), ANTHROPIC_VERSION.to_string()),
]; ];
let model_name = model.model_name.clone(); let model_name = model.model_name.clone();
@ -135,17 +135,17 @@ impl Adapter for AnthropicAdapter {
let usage = body.x_take("usage").map(Self::into_usage).unwrap_or_default(); let usage = body.x_take("usage").map(Self::into_usage).unwrap_or_default();
// -- Capture the content // -- Capture the content
// NOTE: Anthropic support a list of content of multitypes but not the ChatResponse // NOTE: Anthropic supports a list of content of multiple types but not the ChatResponse
// So, the strategy is to: // So, the strategy is to:
// - List all of the content and capture the text and tool_use // - List all of the content and capture the text and tool_use
// - If there is one or more tool_use, this will take precedence and MessageContent support tool_call list // - If there is one or more tool_use, this will take precedence and MessageContent will support tool_call list
// - Otherwise, the text is concatenated // - Otherwise, the text is concatenated
// NOTE: We need to see if the multiple content type text happens and why. If not, we can probably simplify this by just capturing the first one. // NOTE: We need to see if the multiple content type text happens and why. If not, we can probably simplify this by just capturing the first one.
// Eventually, ChatResponse will have `content: Option<Vec<MessageContent>>` for the multi parts (with images and such) // Eventually, ChatResponse will have `content: Option<Vec<MessageContent>>` for the multi parts (with images and such)
let content_items: Vec<Value> = body.x_take("content")?; let content_items: Vec<Value> = body.x_take("content")?;
let mut text_content: Vec<String> = Vec::new(); let mut text_content: Vec<String> = Vec::new();
// Note: here tool_calls is probably the exception, so, not creating the vector if not needed // Note: here tool_calls is probably the exception, so not creating the vector if not needed
let mut tool_calls: Option<Vec<ToolCall>> = None; let mut tool_calls: Option<Vec<ToolCall>> = None;
for mut item in content_items { for mut item in content_items {
@ -228,12 +228,12 @@ impl AnthropicAdapter {
// -- Process the messages // -- Process the messages
for msg in chat_req.messages { for msg in chat_req.messages {
match msg.role { match msg.role {
// for now, system and tool messages go to system // for now, system and tool messages go to the system
ChatRole::System => { ChatRole::System => {
if let MessageContent::Text(content) = msg.content { if let MessageContent::Text(content) = msg.content {
systems.push(content) systems.push(content)
} }
// TODO: Needs to trace/warn that other type are not supported // TODO: Needs to trace/warn that other types are not supported
} }
ChatRole::User => { ChatRole::User => {
let content = match msg.content { let content = match msg.content {
@ -261,7 +261,7 @@ impl AnthropicAdapter {
} }
// Use `match` instead of `if let`. This will allow to future-proof this // Use `match` instead of `if let`. This will allow to future-proof this
// implementation in case some new message content types would appear, // implementation in case some new message content types would appear,
// this way library would not compile if not all methods are implemented // this way the library would not compile if not all methods are implemented
// continue would allow to gracefully skip pushing unserializable message // continue would allow to gracefully skip pushing unserializable message
// TODO: Probably need to warn if it is a ToolCalls type of content // TODO: Probably need to warn if it is a ToolCalls type of content
MessageContent::ToolCalls(_) => continue, MessageContent::ToolCalls(_) => continue,
@ -311,7 +311,7 @@ impl AnthropicAdapter {
}) })
.collect::<Vec<Value>>(); .collect::<Vec<Value>>();
// FIXME: MessageContent::ToolResponse should be MessageContent::ToolResponses (even if openAI does require multi Tool message) // FIXME: MessageContent::ToolResponse should be MessageContent::ToolResponses (even if OpenAI does require multi Tool message)
messages.push(json!({ messages.push(json!({
"role": "user", "role": "user",
"content": tool_responses "content": tool_responses
@ -337,7 +337,7 @@ impl AnthropicAdapter {
.map(|tool| { .map(|tool| {
// TODO: Need to handle the error correctly // TODO: Need to handle the error correctly
// TODO: Needs to have a custom serializer (tool should not have to match to a provider) // TODO: Needs to have a custom serializer (tool should not have to match to a provider)
// NOTE: Right now, low probability, so, we just return null if cannto to value. // NOTE: Right now, low probability, so we just return null if cannot convert to value.
let mut tool_value = json!({ let mut tool_value = json!({
"name": tool.name, "name": tool.name,
"input_schema": tool.schema, "input_schema": tool.schema,

View File

@ -79,7 +79,7 @@ impl futures::Stream for AnthropicStreamer {
} }
// -- END MESSAGE // -- END MESSAGE
"message_stop" => { "message_stop" => {
// Make sure we do not poll the EventSource anymore on the next poll. // Ensure we do not poll the EventSource anymore on the next poll.
// NOTE: This way, the last MessageStop event is still sent, // NOTE: This way, the last MessageStop event is still sent,
// but then, on the next poll, it will be stopped. // but then, on the next poll, it will be stopped.
self.done = true; self.done = true;
@ -142,7 +142,7 @@ impl AnthropicStreamer {
}; };
// -- Capture/Add the eventual input_tokens // -- Capture/Add the eventual input_tokens
// NOTE: Permissive on this one, if error, treat as nonexistent (for now) // NOTE: Permissive on this one; if an error occurs, treat it as nonexistent (for now)
if let Ok(input_tokens) = data.x_get::<i32>(input_path) { if let Ok(input_tokens) = data.x_get::<i32>(input_path) {
let val = self let val = self
.captured_data .captured_data

View File

@ -223,7 +223,7 @@ impl CohereAdapter {
}; };
match msg.role { match msg.role {
// For now, system and tool go to the system // For now, system and tool messages go to the system
ChatRole::System => systems.push(content), ChatRole::System => systems.push(content),
ChatRole::User => chat_history.push(json! ({"role": "USER", "content": content})), ChatRole::User => chat_history.push(json! ({"role": "USER", "content": content})),
ChatRole::Assistant => chat_history.push(json! ({"role": "CHATBOT", "content": content})), ChatRole::Assistant => chat_history.push(json! ({"role": "CHATBOT", "content": content})),

View File

@ -71,7 +71,7 @@ impl futures::Stream for CohereStreamer {
"stream-start" => InterStreamEvent::Start, "stream-start" => InterStreamEvent::Start,
"text-generation" => { "text-generation" => {
if let Some(content) = cohere_message.text { if let Some(content) = cohere_message.text {
// Add to the captured_content if chat options allow it // Add to the captured content if chat options allow it
if self.options.capture_content { if self.options.capture_content {
match self.captured_data.content { match self.captured_data.content {
Some(ref mut c) => c.push_str(&content), Some(ref mut c) => c.push_str(&content),
@ -110,7 +110,7 @@ impl futures::Stream for CohereStreamer {
InterStreamEvent::End(inter_stream_end) InterStreamEvent::End(inter_stream_end)
} }
_ => continue, // Skip the "Other" event _ => continue, // Skip the "other" event
}; };
return Poll::Ready(Some(Ok(inter_event))); return Poll::Ready(Some(Ok(inter_event)));

View File

@ -15,7 +15,7 @@ impl DeepSeekAdapter {
pub const API_KEY_DEFAULT_ENV_NAME: &str = "DEEPSEEK_API_KEY"; pub const API_KEY_DEFAULT_ENV_NAME: &str = "DEEPSEEK_API_KEY";
} }
// The Groq API adapter is modeled after the OpenAI adapter, as the Groq API is compatible with the OpenAI API. // The DeepSeek API adapter is modeled after the OpenAI adapter, as the DeepSeek API is compatible with the OpenAI API.
impl Adapter for DeepSeekAdapter { impl Adapter for DeepSeekAdapter {
fn default_endpoint() -> Endpoint { fn default_endpoint() -> Endpoint {
const BASE_URL: &str = "https://api.deepseek.com/v1/"; const BASE_URL: &str = "https://api.deepseek.com/v1/";

View File

@ -13,7 +13,7 @@ pub struct GeminiStreamer {
options: StreamerOptions, options: StreamerOptions,
// -- Set by the poll_next // -- Set by the poll_next
/// Flag to not poll the EventSource after a MessageStop event /// Flag to not poll the EventSource after a MessageStop event.
done: bool, done: bool,
captured_data: StreamerCapturedData, captured_data: StreamerCapturedData,
} }
@ -41,7 +41,7 @@ impl futures::Stream for GeminiStreamer {
while let Poll::Ready(item) = Pin::new(&mut self.inner).poll_next(cx) { while let Poll::Ready(item) = Pin::new(&mut self.inner).poll_next(cx) {
match item { match item {
Some(Ok(raw_message)) => { Some(Ok(raw_message)) => {
// This is the message sent by the WebStream in PrettyJsonArray mode // This is the message sent by the WebStream in PrettyJsonArray mode.
// - `[` document start // - `[` document start
// - `{...}` block // - `{...}` block
// - `]` document end // - `]` document end
@ -93,10 +93,10 @@ impl futures::Stream for GeminiStreamer {
} }
} }
// NOTE: Apparently in the Gemini API, all events have cumulative usage // NOTE: Apparently in the Gemini API, all events have cumulative usage,
// meaning each message seems to include the tokens for all previous streams. // meaning each message seems to include the tokens for all previous streams.
// Thus, we do not need to add it; we only need to replace captured_data.usage with the latest one. // Thus, we do not need to add it; we only need to replace captured_data.usage with the latest one.
// See https://twitter.com/jeremychone/status/1813734565967802859 for potential additional information // See https://twitter.com/jeremychone/status/1813734565967802859 for potential additional information.
if self.options.capture_usage { if self.options.capture_usage {
self.captured_data.usage = Some(usage); self.captured_data.usage = Some(usage);
} }

View File

@ -26,13 +26,13 @@ impl Adapter for OllamaAdapter {
AuthData::from_single("ollama") AuthData::from_single("ollama")
} }
/// Note 1: For now, this adapter is the only one making a full request to the ollama server /// Note 1: For now, this adapter is the only one making a full request to the Ollama server
/// Note 2: Will the OpenAI API to talk to Ollam server (https://platform.openai.com/docs/api-reference/models/list) /// Note 2: Use the OpenAI API to communicate with the Ollama server (https://platform.openai.com/docs/api-reference/models/list)
/// ///
/// TODO: This will use the default endpoint. /// TODO: This will use the default endpoint.
/// Later, we might add another function with a endpoint, so the the user can give an custom endpoint. /// Later, we might add another function with an endpoint, so the user can provide a custom endpoint.
async fn all_model_names(adapter_kind: AdapterKind) -> Result<Vec<String>> { async fn all_model_names(adapter_kind: AdapterKind) -> Result<Vec<String>> {
// FIXME: This is harcoded to the default endpoint, should take endpoint as Argument // FIXME: This is hardcoded to the default endpoint; it should take the endpoint as an argument.
let endpoint = Self::default_endpoint(); let endpoint = Self::default_endpoint();
let base_url = endpoint.base_url(); let base_url = endpoint.base_url();
let url = format!("{base_url}models"); let url = format!("{base_url}models");

View File

@ -1,5 +1,5 @@
//! OPENAI API DOC: https://platform.openai.com/docs/api-reference/chat //! OPENAI API DOC: https://platform.openai.com/docs/api-reference/chat
//! NOTE: Currently, genai uses the OpenAI compatibility layer, except for listing models. //! NOTE: Currently, GenAI uses the OpenAI compatibility layer, except for listing models.
//! OLLAMA API DOC: https://github.com/ollama/ollama/blob/main/docs/api.md //! OLLAMA API DOC: https://github.com/ollama/ollama/blob/main/docs/api.md
// region: --- Modules // region: --- Modules

View File

@ -327,7 +327,7 @@ impl OpenAIAdapter {
.map(|tool| { .map(|tool| {
// TODO: Need to handle the error correctly // TODO: Need to handle the error correctly
// TODO: Needs to have a custom serializer (tool should not have to match to a provider) // TODO: Needs to have a custom serializer (tool should not have to match to a provider)
// NOTE: Right now, low probability, so, we just return null if cannto to value. // NOTE: Right now, low probability, so, we just return null if cannot convert to value.
json!({ json!({
"type": "function", "type": "function",
"function": { "function": {
@ -387,7 +387,7 @@ fn parse_tool_call(raw_tool_call: Value) -> Result<ToolCall> {
let fn_name = iterim.function.name; let fn_name = iterim.function.name;
// For now support Object only, and parse the eventual string as a json value. // For now, support Object only, and parse the eventual string as a json value.
// Eventually, we might check pricing // Eventually, we might check pricing
let fn_arguments = match iterim.function.arguments { let fn_arguments = match iterim.function.arguments {
Value::Object(obj) => Value::Object(obj), Value::Object(obj) => Value::Object(obj),

View File

@ -82,7 +82,7 @@ impl futures::Stream for OpenAIStreamer {
// If finish_reason exists, it's the end of this choice. // If finish_reason exists, it's the end of this choice.
// Since we support only a single choice, we can proceed, // Since we support only a single choice, we can proceed,
// as there might be other messages, and the last one contains data: `[DONE]` // as there might be other messages, and the last one contains data: `[DONE]`
// NOTE: xAI have no `finish_reason` when not finished, so, need to just account for both null/absent // NOTE: xAI has no `finish_reason` when not finished, so, need to just account for both null/absent
if let Ok(_finish_reason) = first_choice.x_take::<String>("finish_reason") { if let Ok(_finish_reason) = first_choice.x_take::<String>("finish_reason") {
// NOTE: For Groq, the usage is captured when finish_reason indicates stopping, and in the `/x_groq/usage` // NOTE: For Groq, the usage is captured when finish_reason indicates stopping, and in the `/x_groq/usage`
if self.options.capture_usage { if self.options.capture_usage {
@ -101,7 +101,7 @@ impl futures::Stream for OpenAIStreamer {
.unwrap_or_default(); .unwrap_or_default();
self.captured_data.usage = Some(usage) self.captured_data.usage = Some(usage)
} }
_ => (), // do nothing, will be captured the OpenAi way _ => (), // do nothing, will be captured the OpenAI way
} }
} }

View File

@ -1,4 +1,4 @@
//! This support model is for common constructs and utilities for all of the adapter implementations. //! This support module is for common constructs and utilities for all the adapter implementations.
//! It should be private to the `crate::adapter::adapters` module. //! It should be private to the `crate::adapter::adapters` module.
use crate::chat::{ChatOptionsSet, MetaUsage}; use crate::chat::{ChatOptionsSet, MetaUsage};

View File

@ -17,7 +17,7 @@ use crate::resolver::{AuthData, Endpoint};
/// A construct that allows dispatching calls to the Adapters. /// A construct that allows dispatching calls to the Adapters.
/// ///
/// Note 1: This struct does not need to implement the Adapter trait, as some of its methods take the adapter_kind as a parameter. /// Note 1: This struct does not need to implement the Adapter trait, as some of its methods take the adapter kind as a parameter.
/// ///
/// Note 2: This struct might be renamed to avoid confusion with the traditional Rust dispatcher pattern. /// Note 2: This struct might be renamed to avoid confusion with the traditional Rust dispatcher pattern.
pub struct AdapterDispatcher; pub struct AdapterDispatcher;
@ -118,7 +118,7 @@ impl AdapterDispatcher {
AdapterKind::OpenAI => OpenAIAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::OpenAI => OpenAIAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),
AdapterKind::Anthropic => AnthropicAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::Anthropic => AnthropicAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),
AdapterKind::Cohere => CohereAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::Cohere => CohereAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),
AdapterKind::Ollama => OpenAIAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::Ollama => OllamaAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),
AdapterKind::Gemini => GeminiAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::Gemini => GeminiAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),
AdapterKind::Groq => GroqAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::Groq => GroqAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),
AdapterKind::Xai => XaiAdapter::to_chat_stream(model_iden, reqwest_builder, options_set), AdapterKind::Xai => XaiAdapter::to_chat_stream(model_iden, reqwest_builder, options_set),

View File

@ -1,4 +1,4 @@
//! Internal stream event types that serve as an intermediary between the provider event and the GenAI stream event. //! Internal stream event types that serve as intermediaries between the provider event and the GenAI stream event.
//! //!
//! This allows for flexibility if we want to capture events across providers that do not need to //! This allows for flexibility if we want to capture events across providers that do not need to
//! be reflected in the public ChatStream event. //! be reflected in the public ChatStream event.

View File

@ -1,4 +1,4 @@
//! The Adapter layer allows adapting client requests/responses to various AI Providers. //! The Adapter layer allows adapting client requests/responses to various AI providers.
//! Currently, it employs a static dispatch pattern with the `Adapter` trait and `AdapterDispatcher` implementation. //! Currently, it employs a static dispatch pattern with the `Adapter` trait and `AdapterDispatcher` implementation.
//! Adapter implementations are organized by adapter type under the `adapters` submodule. //! Adapter implementations are organized by adapter type under the `adapters` submodule.
//! //!

View File

@ -1,6 +1,6 @@
//! ChatOptions allows customization of a chat request. //! ChatOptions allows customization of a chat request.
//! - It can be provided at the `client::exec_chat(..)` level as an argument, //! - It can be provided at the `client::exec_chat(..)` level as an argument,
//! - or set in the client config `client_config.with_chat_options(..)` to be used as default for all requests //! - or set in the client config `client_config.with_chat_options(..)` to be used as the default for all requests
//! //!
//! Note 1: In the future, we will probably allow setting the client //! Note 1: In the future, we will probably allow setting the client
//! Note 2: Extracting it from the `ChatRequest` object allows for better reusability of each component. //! Note 2: Extracting it from the `ChatRequest` object allows for better reusability of each component.
@ -9,7 +9,7 @@ use crate::chat::chat_req_response_format::ChatResponseFormat;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
/// Chat Options that are taken into account for any `Client::exec...` calls. /// Chat Options that are considered for any `Client::exec...` calls.
/// ///
/// A fallback `ChatOptions` can also be set at the `Client` during the client builder phase /// A fallback `ChatOptions` can also be set at the `Client` during the client builder phase
/// `` /// ``
@ -39,7 +39,7 @@ pub struct ChatOptions {
/// NOTE: More response formats are coming soon. /// NOTE: More response formats are coming soon.
pub response_format: Option<ChatResponseFormat>, pub response_format: Option<ChatResponseFormat>,
/// Specifies sequences used as end marker when generating text /// Specifies sequences used as end markers when generating text
pub stop_sequences: Vec<String>, pub stop_sequences: Vec<String>,
} }

View File

@ -5,7 +5,7 @@ use serde_json::Value;
/// The chat response format for the ChatRequest for structured output. /// The chat response format for the ChatRequest for structured output.
/// This will be taken into consideration only if the provider supports it. /// This will be taken into consideration only if the provider supports it.
/// ///
/// > Note: Currently, the AI Providers will not report an error if not supported. It will just be ignored. /// > Note: Currently, the AI Providers do not report an error if not supported; it will just be ignored.
/// > This may change in the future. /// > This may change in the future.
#[derive(Debug, Clone, From, Serialize, Deserialize)] #[derive(Debug, Clone, From, Serialize, Deserialize)]
pub enum ChatResponseFormat { pub enum ChatResponseFormat {
@ -21,10 +21,10 @@ pub enum ChatResponseFormat {
/// The JSON specification for the structured output format. /// The JSON specification for the structured output format.
#[derive(Debug, Clone, From, Serialize, Deserialize)] #[derive(Debug, Clone, From, Serialize, Deserialize)]
pub struct JsonSpec { pub struct JsonSpec {
/// The name of the spec. Mostly used by OpenAI. /// The name of the specification. Mostly used by OpenAI.
/// IMPORTANT: With OpenAI, this cannot contain any spaces or special characters besides `-` and `_`. /// IMPORTANT: With OpenAI, this cannot contain any spaces or special characters besides `-` and `_`.
pub name: String, pub name: String,
/// The description of the JSON spec. Mostly used by OpenAI adapters (future). /// The description of the JSON specification. Mostly used by OpenAI adapters (future).
/// NOTE: Currently ignored in the OpenAI adapter. /// NOTE: Currently ignored in the OpenAI adapter.
pub description: Option<String>, pub description: Option<String>,

View File

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
/// The Chat request when performing a direct `Client::` /// The Chat request when performing a direct `Client::`
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatRequest { pub struct ChatRequest {
/// The initial system of the request. /// The initial system content of the request.
pub system: Option<String>, pub system: Option<String>,
/// The messages of the request. /// The messages of the request.
@ -28,7 +28,7 @@ impl ChatRequest {
} }
} }
/// From the `.system` property content. /// Create a ChatRequest from the `.system` property content.
pub fn from_system(content: impl Into<String>) -> Self { pub fn from_system(content: impl Into<String>) -> Self {
Self { Self {
system: Some(content.into()), system: Some(content.into()),
@ -46,7 +46,7 @@ impl ChatRequest {
} }
} }
/// Create a new request from messages /// Create a new request from messages.
pub fn from_messages(messages: Vec<ChatMessage>) -> Self { pub fn from_messages(messages: Vec<ChatMessage>) -> Self {
Self { Self {
system: None, system: None,
@ -97,7 +97,7 @@ impl ChatRequest {
.chain(self.messages.iter().filter_map(|message| match message.role { .chain(self.messages.iter().filter_map(|message| match message.role {
ChatRole::System => match message.content { ChatRole::System => match message.content {
MessageContent::Text(ref content) => Some(content.as_str()), MessageContent::Text(ref content) => Some(content.as_str()),
// If system content is not text, then, we do not add it for now. // If system content is not text, then we do not add it for now.
_ => None, _ => None,
}, },
_ => None, _ => None,
@ -116,12 +116,12 @@ impl ChatRequest {
for system in self.iter_systems() { for system in self.iter_systems() {
let systems_content = systems.get_or_insert_with(|| "".to_string()); let systems_content = systems.get_or_insert_with(|| "".to_string());
// add eventual separator // Add eventual separator
if systems_content.ends_with('\n') { if systems_content.ends_with('\n') {
systems_content.push('\n'); systems_content.push('\n');
} else if !systems_content.is_empty() { } else if !systems_content.is_empty() {
systems_content.push_str("\n\n"); systems_content.push_str("\n\n");
} // do not add any empty line if previous content is empty } // Do not add any empty line if previous content is empty
systems_content.push_str(system); systems_content.push_str(system);
} }

View File

@ -58,14 +58,14 @@ impl Stream for ChatStream {
/// The normalized chat stream event for any provider when calling `Client::exec`. /// The normalized chat stream event for any provider when calling `Client::exec`.
#[derive(Debug, From, Serialize, Deserialize)] #[derive(Debug, From, Serialize, Deserialize)]
pub enum ChatStreamEvent { pub enum ChatStreamEvent {
/// Represents the start of the stream. First event. /// Represents the start of the stream. The first event.
Start, Start,
/// Represents each chunk response. Currently only contains text content. /// Represents each chunk response. Currently, it only contains text content.
Chunk(StreamChunk), Chunk(StreamChunk),
/// Represents the end of the stream. /// Represents the end of the stream.
/// Will have the `.captured_usage` and `.captured_content` if specified in the `ChatOptions`. /// It will have the `.captured_usage` and `.captured_content` if specified in the `ChatOptions`.
End(StreamEnd), End(StreamEnd),
} }

View File

@ -15,7 +15,7 @@ pub enum MessageContent {
#[from] #[from]
ToolCalls(Vec<ToolCall>), ToolCalls(Vec<ToolCall>),
/// Tool call Responses /// Tool call responses
#[from] #[from]
ToolResponses(Vec<ToolResponse>), ToolResponses(Vec<ToolResponse>),
} }
@ -43,7 +43,7 @@ impl MessageContent {
/// Returns the MessageContent as &str, only if it is MessageContent::Text /// Returns the MessageContent as &str, only if it is MessageContent::Text
/// Otherwise, it returns None. /// Otherwise, it returns None.
/// ///
/// NOTE: When multi parts content, this will return None and won't concatenate the text parts. /// NOTE: When multi-part content is present, this will return None and won't concatenate the text parts.
pub fn text_as_str(&self) -> Option<&str> { pub fn text_as_str(&self) -> Option<&str> {
match self { match self {
MessageContent::Text(content) => Some(content.as_str()), MessageContent::Text(content) => Some(content.as_str()),
@ -56,7 +56,7 @@ impl MessageContent {
/// Consumes the MessageContent and returns it as &str, /// Consumes the MessageContent and returns it as &str,
/// only if it is MessageContent::Text; otherwise, it returns None. /// only if it is MessageContent::Text; otherwise, it returns None.
/// ///
/// NOTE: When multi parts content, this will return None and won't concatenate the text parts. /// NOTE: When multi-part content is present, this will return None and won't concatenate the text parts.
pub fn text_into_string(self) -> Option<String> { pub fn text_into_string(self) -> Option<String> {
match self { match self {
MessageContent::Text(content) => Some(content), MessageContent::Text(content) => Some(content),
@ -66,7 +66,7 @@ impl MessageContent {
} }
} }
/// Checks if the text content or the tools calls is empty. /// Checks if the text content or the tool calls are empty.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
match self { match self {
MessageContent::Text(content) => content.is_empty(), MessageContent::Text(content) => content.is_empty(),
@ -150,18 +150,18 @@ impl<'a> From<&'a str> for ContentPart {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ImageSource { pub enum ImageSource {
/// For model/services that support URL as input /// For models/services that support URL as input
/// NOTE: Few AI services support this. /// NOTE: Few AI services support this.
Url(String), Url(String),
/// The base64 string of the image /// The base64 string of the image
/// ///
/// Note: Here we use an Arc<str> to avoid cloning large amounts of data when cloning a ChatRequest. /// NOTE: Here we use an Arc<str> to avoid cloning large amounts of data when cloning a ChatRequest.
/// The overhead is minimal compared to cloning relatively large data. /// The overhead is minimal compared to cloning relatively large data.
/// The downside is that it will be an Arc even when used only once, but for this particular data type, the net benefit is positive. /// The downside is that it will be an Arc even when used only once, but for this particular data type, the net benefit is positive.
Base64(Arc<str>), Base64(Arc<str>),
} }
// No `Local` location, this would require handling errors like "file not found" etc. // No `Local` location; this would require handling errors like "file not found" etc.
// Such file can be easily provided by user as Base64, also can implement convenient // Such a file can be easily provided by the user as Base64, and we can implement a convenient
// TryFrom<File> to Base64 version. All LLMs accepts local Images only as Base64 // TryFrom<File> to Base64 version. All LLMs accept local images only as Base64.

View File

@ -1,13 +1,14 @@
//! The genai chat module contains all of the constructs necessary //! The genai chat module contains all of the constructs necessary
//! to make genai requests with the `genai::Client`. //! to make genai requests with the `genai::Client`.
// region: --- Modules // region: --- Modules
mod chat_message; mod chat_message;
mod chat_options; mod chat_options;
mod chat_req_response_format; mod chat_req_response_format;
mod chat_request; mod chat_request;
mod chat_respose; mod chat_response;
mod chat_stream; mod chat_stream;
mod message_content; mod message_content;
mod tool; mod tool;
@ -17,7 +18,7 @@ pub use chat_message::*;
pub use chat_options::*; pub use chat_options::*;
pub use chat_req_response_format::*; pub use chat_req_response_format::*;
pub use chat_request::*; pub use chat_request::*;
pub use chat_respose::*; pub use chat_response::*;
pub use chat_stream::*; pub use chat_stream::*;
pub use message_content::*; pub use message_content::*;
pub use tool::*; pub use tool::*;

View File

@ -112,7 +112,7 @@ async fn print_chat_stream_inner(
// making the main crate error aware of the different error types would be unnecessary. // making the main crate error aware of the different error types would be unnecessary.
// //
// Note 2: This Printer Error is not wrapped in the main crate error because the printer // Note 2: This Printer Error is not wrapped in the main crate error because the printer
// functions are not used by any other crate function (they are more of a debug utility) // functions are not used by any other crate functions (they are more of a debug utility)
use derive_more::From; use derive_more::From;

View File

@ -7,10 +7,10 @@ pub struct Tool {
/// e.g., `get_weather` /// e.g., `get_weather`
pub name: String, pub name: String,
/// The description of the tool which will be used by the LLM to understand the context/usage of this tool /// The description of the tool that will be used by the LLM to understand the context/usage of this tool
pub description: Option<String>, pub description: Option<String>,
/// The json-schema for the parameters /// The JSON schema for the parameters
/// e.g., /// e.g.,
/// ```json /// ```json
/// json!({ /// json!({
@ -27,7 +27,7 @@ pub struct Tool {
/// "unit": { /// "unit": {
/// "type": "string", /// "type": "string",
/// "enum": ["C", "F"], /// "enum": ["C", "F"],
/// "description": "The temperature unit of the country. C for Celsius, and F for Fahrenheit" /// "description": "The temperature unit for the country. C for Celsius, and F for Fahrenheit"
/// } /// }
/// }, /// },
/// "required": ["city", "country", "unit"], /// "required": ["city", "country", "unit"],

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
/// The tool call function name and arguments send back by the LLM. /// The tool call function name and arguments sent back by the LLM.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall { pub struct ToolCall {
pub call_id: String, pub call_id: String,

View File

@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResponse { pub struct ToolResponse {
pub call_id: String, pub call_id: String,
// for now, just string (would probably be serialized json) // For now, just a string (would probably be serialized JSON)
pub content: String, pub content: String,
} }
/// constructor /// Constructor
impl ToolResponse { impl ToolResponse {
pub fn new(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self { pub fn new(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
Self { Self {

View File

@ -29,7 +29,7 @@ impl Client {
Ok(model_iden) Ok(model_iden)
} }
#[deprecated(note = "use `client.resolve_service_target(model_name)")] #[deprecated(note = "use `client.resolve_service_target(model_name)`")]
pub fn resolve_model_iden(&self, model_name: &str) -> Result<ModelIden> { pub fn resolve_model_iden(&self, model_name: &str) -> Result<ModelIden> {
let model = self.default_model(model_name)?; let model = self.default_model(model_name)?;
let target = self.config().resolve_service_target(model)?; let target = self.config().resolve_service_target(model)?;

View File

@ -4,7 +4,7 @@ use crate::ClientBuilder;
use std::sync::Arc; use std::sync::Arc;
/// genai Client for executing AI requests to any providers. /// genai Client for executing AI requests to any providers.
/// Build with: /// Built with:
/// - `ClientBuilder::default()...build()` /// - `ClientBuilder::default()...build()`
/// - or `Client::builder()`, which is equivalent to `ClientBuilder::default()...build()` /// - or `Client::builder()`, which is equivalent to `ClientBuilder::default()...build()`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -17,7 +17,7 @@ pub struct ClientConfig {
impl ClientConfig { impl ClientConfig {
/// Set the AuthResolver for the ClientConfig. /// Set the AuthResolver for the ClientConfig.
/// Note: This will be called before the `service_target_resolver`, and if registered /// Note: This will be called before the `service_target_resolver`, and if registered
/// the `service_target_resolver` will get this new value. /// the `service_target_resolver` will receive this new value.
pub fn with_auth_resolver(mut self, auth_resolver: AuthResolver) -> Self { pub fn with_auth_resolver(mut self, auth_resolver: AuthResolver) -> Self {
self.auth_resolver = Some(auth_resolver); self.auth_resolver = Some(auth_resolver);
self self
@ -25,7 +25,7 @@ impl ClientConfig {
/// Set the ModelMapper for the ClientConfig. /// Set the ModelMapper for the ClientConfig.
/// Note: This will be called before the `service_target_resolver`, and if registered /// Note: This will be called before the `service_target_resolver`, and if registered
/// the `service_target_resolver` will get this new value. /// the `service_target_resolver` will receive this new value.
pub fn with_model_mapper(mut self, model_mapper: ModelMapper) -> Self { pub fn with_model_mapper(mut self, model_mapper: ModelMapper) -> Self {
self.model_mapper = Some(model_mapper); self.model_mapper = Some(model_mapper);
self self
@ -33,8 +33,8 @@ impl ClientConfig {
/// Set the ServiceTargetResolver for this client config. /// Set the ServiceTargetResolver for this client config.
/// ///
/// A ServiceTargetResolver is the last step before execution allowing the users full /// A ServiceTargetResolver is the last step before execution, allowing the users full
/// control of the resolved Endpoint, AuthData, and ModelIden /// control of the resolved Endpoint, AuthData, and ModelIden.
pub fn with_service_target_resolver(mut self, service_target_resolver: ServiceTargetResolver) -> Self { pub fn with_service_target_resolver(mut self, service_target_resolver: ServiceTargetResolver) -> Self {
self.service_target_resolver = Some(service_target_resolver); self.service_target_resolver = Some(service_target_resolver);
self self
@ -91,12 +91,12 @@ impl ClientConfig {
resolver_error, resolver_error,
}) })
}) })
.transpose()? // return error if there is an error on auth resolver .transpose()? // return an error if there is an error with the auth resolver
.flatten() .flatten()
.unwrap_or_else(|| AdapterDispatcher::default_auth(model.adapter_kind)); // flatten the two options .unwrap_or_else(|| AdapterDispatcher::default_auth(model.adapter_kind)); // flatten the two options
// -- Get the default endpoint // -- Get the default endpoint
// For now, just get the default endpoint, the `resolve_target` will allow to override it // For now, just get the default endpoint; the `resolve_target` will allow overriding it.
let endpoint = AdapterDispatcher::default_endpoint(model.adapter_kind); let endpoint = AdapterDispatcher::default_endpoint(model.adapter_kind);
// -- Resolve the service_target // -- Resolve the service_target

View File

@ -5,7 +5,7 @@ use crate::ModelName;
/// Holds the adapter kind and model name in an efficient, clonable way. /// Holds the adapter kind and model name in an efficient, clonable way.
/// ///
/// This struct is used to represent the association between an adapter kind /// This struct represents the association between an adapter kind
/// and a model name, allowing for easy conversion and instantiation. /// and a model name, allowing for easy conversion and instantiation.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ModelIden { pub struct ModelIden {

View File

@ -22,7 +22,7 @@ impl From<ModelName> for String {
} }
// NOTE: Below we avoid the `T: Into<String>` blanket implementation because // NOTE: Below we avoid the `T: Into<String>` blanket implementation because
// it would prevent us from having the `From<ModelName> for String` as `ModelName` // it would prevent us from having the `From<ModelName> for String` implementation since `ModelName`
// also implements `T: Into<String>` from its deref to `&str` // also implements `T: Into<String>` from its deref to `&str`
impl From<String> for ModelName { impl From<String> for ModelName {

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
/// A construct to store the endpoint of a service. /// A construct to store the endpoint of a service.
/// It is designed to be efficiently clonable. /// It is designed to be efficiently clonable.
/// For now, it just supports `base_url` but later might have other URLs per "service name". /// For now, it supports only `base_url`, but it may later have other URLs per "service name".
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Endpoint { pub struct Endpoint {
inner: EndpointInner, inner: EndpointInner,

View File

@ -1,5 +1,5 @@
//! Resolvers are hooks that library users can set to customize aspects of the library's default behavior. //! Resolvers are hooks that library users can set to customize aspects of the library's default behavior.
//! A good example for now is the AuthResolver, which provides the authentication data (e.g., api_key). //! A good example is the AuthResolver, which provides the authentication data (e.g., api_key).
//! //!
//! Eventually, the library will have more resolvers. //! Eventually, the library will have more resolvers.

View File

@ -1,5 +1,5 @@
//! A `ServiceTargetResolver` is responsible for returning the `ServiceTarget`. //! A `ServiceTargetResolver` is responsible for returning the `ServiceTarget`.
//! It allows users to customize/override the service target properties. //! It allows users to customize or override the service target properties.
//! //!
//! It can take the following forms: //! It can take the following forms:
//! - Contains a fixed service target value, //! - Contains a fixed service target value,

View File

@ -4,14 +4,14 @@
mod error; mod error;
mod web_client; mod web_client;
// for when not `text/event-stream` // For when not using `text/event-stream`
mod web_stream; mod web_stream;
pub(crate) use error::Result; pub(crate) use error::Result;
pub(crate) use web_client::*; pub(crate) use web_client::*;
pub(crate) use web_stream::*; pub(crate) use web_stream::*;
// only public for external use // Only public for external use
pub use error::Error; pub use error::Error;
// endregion: --- Modules // endregion: --- Modules

View File

@ -19,7 +19,7 @@ pub struct WebStream {
reqwest_builder: Option<RequestBuilder>, reqwest_builder: Option<RequestBuilder>,
response_future: Option<Pin<Box<dyn Future<Output = Result<Response, Box<dyn Error>>> + Send>>>, response_future: Option<Pin<Box<dyn Future<Output = Result<Response, Box<dyn Error>>> + Send>>>,
bytes_stream: Option<Pin<Box<dyn Stream<Item = Result<Bytes, Box<dyn Error>>> + Send>>>, bytes_stream: Option<Pin<Box<dyn Stream<Item = Result<Bytes, Box<dyn Error>>> + Send>>>,
// If a poll was a partial message, then we kept the previous part // If a poll was a partial message, then we keep the previous part
partial_message: Option<String>, partial_message: Option<String>,
// If a poll retrieved multiple messages, we keep them to be sent in the next poll // If a poll retrieved multiple messages, we keep them to be sent in the next poll
remaining_messages: Option<VecDeque<String>>, remaining_messages: Option<VecDeque<String>>,
@ -206,7 +206,7 @@ fn new_with_pretty_json_array(
messages.push(array_end.to_string()); messages.push(array_end.to_string());
} }
// -- Return the buf response // -- Return the buff response
let first_message = if !messages.is_empty() { let first_message = if !messages.is_empty() {
Some(messages[0].to_string()) Some(messages[0].to_string())
} else { } else {