mirror of
https://github.com/mii443/rust-genai.git
synced 2025-09-04 00:19:12 +00:00
. clippy clean
. remove example 'images' alias to example c07-image . MessageContent::text_into_string/str return None when Parts (to avoid leak)
This commit is contained in:
@ -9,10 +9,6 @@ keywords = ["generative-ai","openai","chatgpt","gemini","ollama"]
|
|||||||
homepage = "https://github.com/jeremychone/rust-genai"
|
homepage = "https://github.com/jeremychone/rust-genai"
|
||||||
repository = "https://github.com/jeremychone/rust-genai"
|
repository = "https://github.com/jeremychone/rust-genai"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "images"
|
|
||||||
path = "examples/c07-image.rs"
|
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unsafe_code = "forbid"
|
unsafe_code = "forbid"
|
||||||
# unused = { level = "allow", priority = -1 } # For exploratory dev.
|
# unused = { level = "allow", priority = -1 } # For exploratory dev.
|
||||||
|
@ -5,6 +5,7 @@ use genai::chat::{ChatMessage, ChatRequest, ContentPart, ImageSource};
|
|||||||
use genai::Client;
|
use genai::Client;
|
||||||
|
|
||||||
const MODEL: &str = "gpt-4o-mini";
|
const MODEL: &str = "gpt-4o-mini";
|
||||||
|
const IMAGE_URL: &str = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@ -14,22 +15,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let mut chat_req = ChatRequest::default().with_system("Answer in one sentence");
|
let mut chat_req = ChatRequest::default().with_system("Answer in one sentence");
|
||||||
// This is similar to sending initial system chat messages (which will be cumulative with system chat messages)
|
// This is similar to sending initial system chat messages (which will be cumulative with system chat messages)
|
||||||
chat_req = chat_req.append_message(ChatMessage::user(
|
chat_req = chat_req.append_message(ChatMessage::user(vec![
|
||||||
vec![
|
ContentPart::Text(question.to_string()),
|
||||||
ContentPart::Text(question.to_string()),
|
ContentPart::Image {
|
||||||
ContentPart::Image {
|
content: IMAGE_URL.to_string(),
|
||||||
content: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg".to_string(),
|
content_type: "image/jpg".to_string(),
|
||||||
content_type: "image/png".to_string(),
|
source: ImageSource::Url,
|
||||||
source: ImageSource::Url,
|
},
|
||||||
}
|
]));
|
||||||
]
|
|
||||||
));
|
|
||||||
|
|
||||||
println!("\n--- Question:\n{question}");
|
println!("\n--- Question:\n{question}");
|
||||||
let chat_res = client.exec_chat_stream(MODEL, chat_req.clone(), None).await?;
|
let chat_res = client.exec_chat_stream(MODEL, chat_req.clone(), None).await?;
|
||||||
|
|
||||||
println!("\n--- Answer: (streaming)");
|
println!("\n--- Answer: (streaming)");
|
||||||
let assistant_answer = print_chat_stream(chat_res, None).await?;
|
let _assistant_answer = print_chat_stream(chat_res, None).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,9 @@ impl MessageContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new MessageContent from provided content parts
|
/// Create a new MessageContent from provided content parts
|
||||||
pub fn from_parts(parts: impl Into<Vec<ContentPart>>) -> Self { MessageContent::Parts(parts.into()) }
|
pub fn from_parts(parts: impl Into<Vec<ContentPart>>) -> Self {
|
||||||
|
MessageContent::Parts(parts.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new MessageContent with the ToolCalls variant
|
/// Create a new MessageContent with the ToolCalls variant
|
||||||
pub fn from_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
|
pub fn from_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
|
||||||
@ -39,17 +41,12 @@ impl MessageContent {
|
|||||||
impl MessageContent {
|
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: As of now, it always returns Some(..) because MessageContent has only the Text variant.
|
///
|
||||||
/// However, this is in preparation for future expansions.
|
/// NOTE: When multi parts content, 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()),
|
||||||
MessageContent::Parts(parts) => {
|
MessageContent::Parts(_) => None,
|
||||||
Some(parts.iter().filter_map(|part| match part {
|
|
||||||
ContentPart::Text(content) => Some(content.clone()),
|
|
||||||
_ => None,
|
|
||||||
}).collect::<Vec<String>>().join("\n").leak()) // TODO revisit this, should we leak &str?
|
|
||||||
},
|
|
||||||
MessageContent::ToolCalls(_) => None,
|
MessageContent::ToolCalls(_) => None,
|
||||||
MessageContent::ToolResponses(_) => None,
|
MessageContent::ToolResponses(_) => None,
|
||||||
}
|
}
|
||||||
@ -58,17 +55,11 @@ 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: As of now, it always returns Some(..) because MessageContent has only the Text variant.
|
/// NOTE: When multi parts content, this will return None and won't concatenate the text parts.
|
||||||
/// However, this is in preparation for future expansions.
|
|
||||||
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),
|
||||||
MessageContent::Parts(parts) => {
|
MessageContent::Parts(_) => None,
|
||||||
Some(parts.into_iter().filter_map(|part| match part {
|
|
||||||
ContentPart::Text(content) => Some(content),
|
|
||||||
_ => None,
|
|
||||||
}).collect::<Vec<String>>().join("\n"))
|
|
||||||
},
|
|
||||||
MessageContent::ToolCalls(_) => None,
|
MessageContent::ToolCalls(_) => None,
|
||||||
MessageContent::ToolResponses(_) => None,
|
MessageContent::ToolResponses(_) => None,
|
||||||
}
|
}
|
||||||
@ -112,7 +103,9 @@ impl From<ToolResponse> for MessageContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<ContentPart>> for MessageContent {
|
impl From<Vec<ContentPart>> for MessageContent {
|
||||||
fn from(parts: Vec<ContentPart>) -> Self { MessageContent::Parts(parts) }
|
fn from(parts: Vec<ContentPart>) -> Self {
|
||||||
|
MessageContent::Parts(parts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion: --- Froms
|
// endregion: --- Froms
|
||||||
@ -137,13 +130,10 @@ impl<'a> From<&'a str> for ContentPart {
|
|||||||
|
|
||||||
// endregion: --- Froms
|
// endregion: --- Froms
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, From)]
|
#[derive(Debug, Clone, Serialize, Deserialize, From)]
|
||||||
pub enum ImageSource {
|
pub enum ImageSource {
|
||||||
Url,
|
Url,
|
||||||
Base64
|
Base64, // 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
|
||||||
// No `Local` location, this would require handling errors like "file not found" etc.
|
// TryFrom<File> to Base64 version. All LLMs accepts local Images only as Base64
|
||||||
// Such file can be easily provided by user as Base64, also can implement convenient
|
|
||||||
// TryFrom<File> to Base64 version. All LLMs accepts local Images only as Base64
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user