From b747c72b6b5d4b5cb963ab390d3d655b8efd4339 Mon Sep 17 00:00:00 2001 From: dstoc <539597+dstoc@users.noreply.github.com> Date: Sat, 13 Sep 2025 02:57:14 +0000 Subject: [PATCH] chore(llm): upgrade gemini-rust --- Cargo.lock | 33 ++++++++++++++++++++++++++--- crates/llm/Cargo.toml | 2 +- crates/llm/src/gemini_rust.rs | 40 +++++++++++++++++++++++------------ 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98c3d56..d97b88b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1159,19 +1159,23 @@ dependencies = [ [[package]] name = "gemini-rust" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b652bdfbbfe6a6f139df43256fb5ebb8d38f89bfd38d3c134a6278285fd3d0" +checksum = "6b09f22c73a82c5e03dfe4b234ea44139f47af5d47bc282573e4bf34de6603fb" dependencies = [ "async-stream", "async-trait", "base64 0.22.1", + "eventsource-stream", "futures", "futures-util", + "mime", + "mime_guess", "reqwest", "serde", "serde_json", - "thiserror 2.0.12", + "snafu", + "time", "tokio", "url", ] @@ -3462,6 +3466,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "backtrace", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.5.10" @@ -4106,6 +4132,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] diff --git a/crates/llm/Cargo.toml b/crates/llm/Cargo.toml index 2ac6f17..1b16c2e 100644 --- a/crates/llm/Cargo.toml +++ b/crates/llm/Cargo.toml @@ -11,7 +11,7 @@ async-trait = "0.1.88" clap = { version = "4.5.43", features = ["derive"] } futures-util = "0.3.31" gbnf-rs = { version = "0.1.0", path = "../gbnf-rs" } -gemini-rust = "1.3.1" +gemini-rust = "1.4.0" ollama-rs = { git = "https://github.com/dstoc/ollama-rs", branch = "RobJellinghaus/streaming-tools", version = "0.3.2", features = ["macros", "stream"] } openai-harmony = { git = "https://github.com/openai/harmony", tag = "v0.0.4", version = "0.0.4" } reqwest = { version = "0.12.15", default-features = false, features = ["json", "rustls-tls"] } diff --git a/crates/llm/src/gemini_rust.rs b/crates/llm/src/gemini_rust.rs index 14b3f7b..f07b1cc 100644 --- a/crates/llm/src/gemini_rust.rs +++ b/crates/llm/src/gemini_rust.rs @@ -1,12 +1,12 @@ use std::error::Error; use async_trait::async_trait; -use futures_util::StreamExt; +use futures_util::{StreamExt, TryStreamExt}; use gemini_rust::{ Content, FunctionCallingMode, FunctionDeclaration, FunctionParameters, Gemini, Message, Part, Role, }; -use reqwest::Client as HttpClient; +use reqwest::{Client as HttpClient, Url}; use serde_json::Value; use uuid::Uuid; @@ -52,8 +52,8 @@ impl LlmClient for GeminiRustClient { let gemini = Gemini::with_model_and_base_url( self.api_key.clone(), format!("models/{}", request.model_name), - self.base_url.clone(), - ); + Url::parse(self.base_url.as_str()).unwrap(), + )?; let mut builder = gemini.generate_content(); let mut system_instruction: Option = None; @@ -70,6 +70,7 @@ impl LlmClient for GeminiRustClient { parts_vec.push(Part::Text { text, thought: None, + thought_signature: None, }); } AssistantPart::ToolCall(tc) => { @@ -79,6 +80,7 @@ impl LlmClient for GeminiRustClient { }; parts_vec.push(Part::FunctionCall { function_call: gemini_rust::FunctionCall::new(tc.name, args), + thought_signature: None, }); } AssistantPart::Thinking { .. } => {} @@ -143,33 +145,43 @@ impl LlmClient for GeminiRustClient { let mut input_tokens = 0u32; let mut output_tokens = 0u32; let stream = builder.execute_stream().await?; - let mapped = stream.flat_map(move |res| match res { + let mapped = stream.into_stream().flat_map(move |res| match res { Ok(chunk) => { let mut out: Vec>> = Vec::new(); if let Some(usage) = chunk.usage_metadata { - let input_delta = usage.prompt_token_count as u32 - input_tokens; + let input_delta = + usage.prompt_token_count.unwrap_or_default() as u32 - input_tokens; input_tokens += input_delta; - let output_delta = usage.total_token_count as u32 - - usage.prompt_token_count as u32 + let output_delta = usage.total_token_count.unwrap_or_default() as u32 + - usage.prompt_token_count.unwrap_or_default() as u32 - output_tokens; output_tokens += output_delta; - out.push(Ok(ResponseChunk::Usage { - input_tokens: input_delta, - output_tokens: output_delta, - })); + if input_delta > 0 || output_delta > 0 { + out.push(Ok(ResponseChunk::Usage { + input_tokens: input_delta, + output_tokens: output_delta, + })); + } } if let Some(candidate) = chunk.candidates.first() { if let Some(parts) = &candidate.content.parts { for part in parts { match part { - Part::Text { text, thought } => { + Part::Text { + text, + thought, + thought_signature: _, + } => { if thought.unwrap_or(false) { out.push(Ok(ResponseChunk::Thinking(text.clone()))); } else if !text.is_empty() { out.push(Ok(ResponseChunk::Content(text.clone()))); } } - Part::FunctionCall { function_call } => { + Part::FunctionCall { + function_call, + thought_signature: _, + } => { out.push(Ok(ResponseChunk::ToolCall(ToolCall { id: Uuid::new_v4().to_string(), name: function_call.name.clone(),