mirror of
https://github.com/katanemo/plano.git
synced 2026-07-14 16:22:12 +02:00
add more changes
This commit is contained in:
parent
2d4d0b01ee
commit
f10e0fcece
14 changed files with 486 additions and 139 deletions
|
|
@ -5,12 +5,13 @@ pub mod providers;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::providers::openai::types::OpenAIRequest;
|
||||
use crate::providers::openai::types::ChatCompletionsRequest;
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn openai_builder() {
|
||||
let request = OpenAIRequest::builder()
|
||||
.model("gpt-3.5-turbo")
|
||||
let request = ChatCompletionsRequest::builder("gpt-3.5-turbo", vec![])
|
||||
.temperature(0.7)
|
||||
.top_p(0.9)
|
||||
.n(1)
|
||||
|
|
@ -22,14 +23,14 @@ mod tests {
|
|||
.build()
|
||||
.expect("Failed to build OpenAIRequest");
|
||||
|
||||
assert_eq!(request.base.model, "gpt-3.5-turbo");
|
||||
assert_eq!(request.base.temperature, Some(0.7));
|
||||
assert_eq!(request.base.top_p, Some(0.9));
|
||||
assert_eq!(request.base.n, Some(1));
|
||||
assert_eq!(request.base.max_tokens, Some(100));
|
||||
assert_eq!(request.base.stream, Some(false));
|
||||
assert_eq!(request.base.stop, Some(vec!["\n".to_string()]));
|
||||
assert_eq!(request.base.presence_penalty, Some(0.0));
|
||||
assert_eq!(request.base.frequency_penalty, Some(0.0));
|
||||
assert_eq!(request.model, "gpt-3.5-turbo");
|
||||
assert_eq!(request.temperature, Some(0.7));
|
||||
assert_eq!(request.top_p, Some(0.9));
|
||||
assert_eq!(request.n, Some(1));
|
||||
assert_eq!(request.max_tokens, Some(100));
|
||||
assert_eq!(request.stream, Some(false));
|
||||
assert_eq!(request.stop, Some(vec!["\n".to_string()]));
|
||||
assert_eq!(request.presence_penalty, Some(0.0));
|
||||
assert_eq!(request.frequency_penalty, Some(0.0));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub role: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChatRequestBase {
|
||||
pub model: String,
|
||||
pub messages: Option<Vec<Message>>,
|
||||
pub temperature: Option<f32>,
|
||||
pub top_p: Option<f32>,
|
||||
pub n: Option<u32>,
|
||||
pub max_tokens: Option<u32>,
|
||||
pub stream: Option<bool>,
|
||||
pub stop: Option<Vec<String>>,
|
||||
pub presence_penalty: Option<f32>,
|
||||
pub frequency_penalty: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ChatResponseBase {
|
||||
pub id: String,
|
||||
pub object: String,
|
||||
pub created: u64,
|
||||
pub choices: Vec<Choice>,
|
||||
pub usage: Option<Usage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Choice {
|
||||
pub index: u32,
|
||||
pub message: Message,
|
||||
pub finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Usage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::providers::common_types::{ChatRequestBase, ChatResponseBase};
|
||||
use crate::providers::openai::types::{ChatCompletionsRequest, ChatCompletionsResponse};
|
||||
pub use crate::providers::openai::types::{Choice, Message, Usage};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeepSeekRequest {
|
||||
#[serde(flatten)]
|
||||
pub base: ChatRequestBase,
|
||||
pub base: ChatCompletionsRequest,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeepSeekResponse {
|
||||
#[serde(flatten)]
|
||||
pub base: ChatResponseBase,
|
||||
pub base: ChatCompletionsResponse,
|
||||
}
|
||||
|
||||
// Re-export for convenience
|
||||
pub use crate::providers::common_types::{Message, Choice, Usage};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::providers::common_types::{ChatRequestBase, ChatResponseBase};
|
||||
use crate::providers::openai::types::{ChatCompletionsRequest, ChatCompletionsResponse};
|
||||
pub use crate::providers::openai::types::{Choice, Message, Usage};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GroqRequest {
|
||||
#[serde(flatten)]
|
||||
pub base: ChatRequestBase,
|
||||
pub base: ChatCompletionsRequest,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GroqResponse {
|
||||
#[serde(flatten)]
|
||||
pub base: ChatResponseBase,
|
||||
pub base: ChatCompletionsResponse,
|
||||
}
|
||||
|
||||
pub use crate::providers::common_types::{Message, Choice, Usage};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,3 @@
|
|||
pub mod openai;
|
||||
pub mod groq;
|
||||
pub mod deepseek;
|
||||
pub mod common_types;
|
||||
|
||||
/// Supported LLM providers.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Provider {
|
||||
Groq,
|
||||
OpenAI,
|
||||
DeepSeek,
|
||||
}
|
||||
pub mod groq;
|
||||
pub mod openai;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ pub mod types;
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::providers::openai::types::{OpenAIRequest, OpenAIResponse};
|
||||
use crate::providers::openai::types::{ChatCompletionsRequest, ChatCompletionsResponse};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum OpenAIError {
|
||||
|
|
@ -12,14 +12,14 @@ pub enum OpenAIError {
|
|||
|
||||
type Result<T> = std::result::Result<T, OpenAIError>;
|
||||
|
||||
impl TryFrom<&[u8]> for OpenAIRequest {
|
||||
impl TryFrom<&[u8]> for ChatCompletionsRequest {
|
||||
type Error = OpenAIError;
|
||||
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||
serde_json::from_slice(bytes).map_err(OpenAIError::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for OpenAIResponse {
|
||||
impl TryFrom<&[u8]> for ChatCompletionsResponse {
|
||||
type Error = OpenAIError;
|
||||
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||
serde_json::from_slice(bytes).map_err(OpenAIError::from)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,124 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::providers::common_types::{ChatRequestBase, ChatResponseBase};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OpenAIRequest {
|
||||
#[serde(flatten)]
|
||||
pub base: ChatRequestBase,
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum MultiPartContentType {
|
||||
#[serde(rename = "text")]
|
||||
Text,
|
||||
#[serde(rename = "image_url")]
|
||||
ImageUrl,
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MultiPartContent {
|
||||
pub text: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub content_type: MultiPartContentType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum ContentType {
|
||||
Text(String),
|
||||
MultiPart(Vec<MultiPartContent>),
|
||||
}
|
||||
|
||||
impl Display for ContentType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ContentType::Text(text) => write!(f, "{}", text),
|
||||
ContentType::MultiPart(multi_part) => {
|
||||
let text_parts: Vec<String> = multi_part
|
||||
.iter()
|
||||
.filter_map(|part| {
|
||||
if part.content_type == MultiPartContentType::Text {
|
||||
part.text.clone()
|
||||
} else if part.content_type == MultiPartContentType::ImageUrl {
|
||||
// skip image URLs or their data in text representation
|
||||
None
|
||||
} else {
|
||||
panic!("Unsupported content type: {:?}", part.content_type);
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let combined_text = text_parts.join("\n");
|
||||
write!(f, "{}", combined_text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub role: String,
|
||||
pub content: Option<ContentType>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChatCompletionsRequest {
|
||||
pub model: String,
|
||||
pub messages: Vec<Message>,
|
||||
pub temperature: Option<f32>,
|
||||
pub top_p: Option<f32>,
|
||||
pub n: Option<u32>,
|
||||
pub max_tokens: Option<u32>,
|
||||
pub stream: Option<bool>,
|
||||
pub stop: Option<Vec<String>>,
|
||||
pub presence_penalty: Option<f32>,
|
||||
pub frequency_penalty: Option<f32>,
|
||||
}
|
||||
|
||||
impl Default for ChatCompletionsRequest {
|
||||
fn default() -> Self {
|
||||
ChatCompletionsRequest {
|
||||
model: String::new(),
|
||||
messages: Vec::new(),
|
||||
temperature: None,
|
||||
top_p: None,
|
||||
n: None,
|
||||
max_tokens: None,
|
||||
stream: None,
|
||||
stop: None,
|
||||
presence_penalty: None,
|
||||
frequency_penalty: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ChatCompletionsResponse {
|
||||
pub id: String,
|
||||
pub object: String,
|
||||
pub created: u64,
|
||||
pub choices: Vec<Choice>,
|
||||
pub usage: Option<Usage>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Choice {
|
||||
pub index: u32,
|
||||
pub message: Message,
|
||||
pub finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Usage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpenAIRequestBuilder {
|
||||
model: Option<String>,
|
||||
messages: Option<Vec<crate::providers::common_types::Message>>,
|
||||
model: String,
|
||||
messages: Vec<Message>,
|
||||
temperature: Option<f32>,
|
||||
top_p: Option<f32>,
|
||||
n: Option<u32>,
|
||||
|
|
@ -21,18 +130,19 @@ pub struct OpenAIRequestBuilder {
|
|||
}
|
||||
|
||||
impl OpenAIRequestBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn model(mut self, model: impl Into<String>) -> Self {
|
||||
self.model = Some(model.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn messages(mut self, messages: Vec<crate::providers::common_types::Message>) -> Self {
|
||||
self.messages = Some(messages);
|
||||
self
|
||||
pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
|
||||
Self {
|
||||
model: model.into(),
|
||||
messages,
|
||||
temperature: None,
|
||||
top_p: None,
|
||||
n: None,
|
||||
max_tokens: None,
|
||||
stream: None,
|
||||
stop: None,
|
||||
presence_penalty: None,
|
||||
frequency_penalty: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn temperature(mut self, temperature: f32) -> Self {
|
||||
|
|
@ -75,10 +185,9 @@ impl OpenAIRequestBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<OpenAIRequest, &'static str> {
|
||||
let model = self.model.ok_or("model is required")?;
|
||||
let base = crate::providers::common_types::ChatRequestBase {
|
||||
model,
|
||||
pub fn build(self) -> Result<ChatCompletionsRequest, &'static str> {
|
||||
let request = ChatCompletionsRequest {
|
||||
model: self.model,
|
||||
messages: self.messages,
|
||||
temperature: self.temperature,
|
||||
top_p: self.top_p,
|
||||
|
|
@ -89,20 +198,83 @@ impl OpenAIRequestBuilder {
|
|||
presence_penalty: self.presence_penalty,
|
||||
frequency_penalty: self.frequency_penalty,
|
||||
};
|
||||
Ok(OpenAIRequest { base })
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenAIRequest {
|
||||
pub fn builder() -> OpenAIRequestBuilder {
|
||||
OpenAIRequestBuilder::new()
|
||||
impl ChatCompletionsRequest {
|
||||
pub fn builder(model: impl Into<String>, messages: Vec<Message>) -> OpenAIRequestBuilder {
|
||||
OpenAIRequestBuilder::new(model, messages)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OpenAIResponse {
|
||||
#[serde(flatten)]
|
||||
pub base: ChatResponseBase,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
pub use crate::providers::common_types::{Message, Choice, Usage};
|
||||
#[test]
|
||||
fn test_content_type_display() {
|
||||
let text_content = ContentType::Text("Hello, world!".to_string());
|
||||
assert_eq!(text_content.to_string(), "Hello, world!");
|
||||
|
||||
let multi_part_content = ContentType::MultiPart(vec![
|
||||
MultiPartContent {
|
||||
text: Some("This is a text part.".to_string()),
|
||||
content_type: MultiPartContentType::Text,
|
||||
},
|
||||
MultiPartContent {
|
||||
text: Some("https://example.com/image.png".to_string()),
|
||||
content_type: MultiPartContentType::ImageUrl,
|
||||
},
|
||||
]);
|
||||
assert_eq!(multi_part_content.to_string(), "This is a text part.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_completions_request_text_type_array() {
|
||||
const CHAT_COMPLETIONS_REQUEST: &str = r#"
|
||||
{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "What city do you want to know the weather for?"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "hello world"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
|
||||
let chat_completions_request: ChatCompletionsRequest =
|
||||
serde_json::from_str(CHAT_COMPLETIONS_REQUEST).unwrap();
|
||||
assert_eq!(chat_completions_request.model, "gpt-3.5-turbo");
|
||||
if let Some(ContentType::MultiPart(multi_part_content)) =
|
||||
chat_completions_request.messages[0].content.as_ref()
|
||||
{
|
||||
assert_eq!(multi_part_content.len(), 2);
|
||||
assert_eq!(
|
||||
multi_part_content[0].content_type,
|
||||
MultiPartContentType::Text
|
||||
);
|
||||
assert_eq!(
|
||||
multi_part_content[0].text,
|
||||
Some("What city do you want to know the weather for?".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
multi_part_content[1].content_type,
|
||||
MultiPartContentType::Text
|
||||
);
|
||||
assert_eq!(multi_part_content[1].text, Some("hello world".to_string()));
|
||||
} else {
|
||||
panic!("Expected MultiPartContent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue