From 7490571f6667bd8cb653fe0514a93990274bfc1c Mon Sep 17 00:00:00 2001 From: Salman Paracha Date: Wed, 6 Aug 2025 22:05:23 -0700 Subject: [PATCH] addressing feedback: remove rust client integration tests --- crates/Cargo.lock | 292 +-------------- crates/hermesllm/Cargo.toml | 4 - crates/hermesllm/src/apis/openai.rs | 545 ---------------------------- 3 files changed, 8 insertions(+), 833 deletions(-) diff --git a/crates/Cargo.lock b/crates/Cargo.lock index b9ea3f65..1f33b4c2 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -104,43 +104,6 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" -[[package]] -name = "async-openai" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31acf814d6b499e33ec894bb0fd7ddaf2665b44fbdd42b858d736449271fde0c" -dependencies = [ - "async-openai-macros", - "backoff", - "base64 0.22.1", - "bytes", - "derive_builder", - "eventsource-stream", - "futures", - "rand 0.8.5", - "reqwest", - "reqwest-eventsource", - "secrecy", - "serde", - "serde_json", - "thiserror 2.0.12", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", -] - -[[package]] -name = "async-openai-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0289cba6d5143bfe8251d57b4a8cac036adf158525a76533a7082ba65ec76398" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "async-trait" version = "0.1.88" @@ -175,20 +138,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "backoff" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" -dependencies = [ - "futures-core", - "getrandom 0.2.16", - "instant", - "pin-project-lite", - "rand 0.8.5", - "tokio", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -338,12 +287,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.41" @@ -411,16 +354,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -482,7 +415,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "regalloc2", - "rustc-hash 1.1.0", + "rustc-hash", "smallvec", "target-lexicon", ] @@ -676,37 +609,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.101", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.101", -] - [[package]] name = "diff" version = "0.1.13" @@ -970,12 +872,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.31" @@ -1033,10 +929,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1046,11 +940,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", ] [[package]] @@ -1193,12 +1085,10 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" name = "hermesllm" version = "0.1.0" dependencies = [ - "async-openai", "serde", "serde_json", "serde_with", "thiserror 2.0.12", - "tokio", ] [[package]] @@ -1340,7 +1230,7 @@ dependencies = [ "hyper 0.14.32", "log", "rustls 0.21.12", - "rustls-native-certs 0.6.3", + "rustls-native-certs", "tokio", "tokio-rustls 0.24.1", ] @@ -1355,7 +1245,6 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "rustls 0.23.27", - "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -1594,15 +1483,6 @@ dependencies = [ "serde", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -1780,12 +1660,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "mach2" version = "0.4.2" @@ -1831,16 +1705,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1885,7 +1749,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -2335,61 +2199,6 @@ dependencies = [ "cc", ] -[[package]] -name = "quinn" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.1", - "rustls 0.23.27", - "socket2", - "thiserror 2.0.12", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" -dependencies = [ - "bytes", - "getrandom 0.3.3", - "lru-slab", - "rand 0.9.1", - "ring", - "rustc-hash 2.1.1", - "rustls 0.23.27", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.40" @@ -2532,7 +2341,7 @@ checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", "log", - "rustc-hash 1.1.0", + "rustc-hash", "slice-group-by", "smallvec", ] @@ -2605,14 +2414,10 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "quinn", - "rustls 0.23.27", - "rustls-native-certs 0.8.1", "rustls-pki-types", "serde", "serde_json", @@ -2620,7 +2425,6 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.2", "tokio-util", "tower 0.5.2", "tower-http", @@ -2632,22 +2436,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "reqwest-eventsource" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" -dependencies = [ - "eventsource-stream", - "futures-core", - "futures-timer", - "mime", - "nom", - "pin-project-lite", - "reqwest", - "thiserror 1.0.69", -] - [[package]] name = "ring" version = "0.17.14" @@ -2674,12 +2462,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustix" version = "0.38.44" @@ -2725,7 +2507,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "once_cell", - "ring", "rustls-pki-types", "rustls-webpki 0.103.3", "subtle", @@ -2741,19 +2522,7 @@ dependencies = [ "openssl-probe", "rustls-pemfile", "schannel", - "security-framework 2.11.1", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework 3.3.0", + "security-framework", ] [[package]] @@ -2771,7 +2540,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "web-time", "zeroize", ] @@ -2860,16 +2628,6 @@ version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" -[[package]] -name = "secrecy" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" -dependencies = [ - "serde", - "zeroize", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -2877,20 +2635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" -dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -3218,7 +2963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.1", - "core-foundation 0.9.4", + "core-foundation", "system-configuration-sys", ] @@ -3331,7 +3076,7 @@ dependencies = [ "fancy-regex", "lazy_static", "parking_lot", - "rustc-hash 1.1.0", + "rustc-hash", ] [[package]] @@ -3375,21 +3120,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.45.1" @@ -3706,12 +3436,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/crates/hermesllm/Cargo.toml b/crates/hermesllm/Cargo.toml index cbfea10e..c995e85c 100644 --- a/crates/hermesllm/Cargo.toml +++ b/crates/hermesllm/Cargo.toml @@ -8,7 +8,3 @@ serde = {version = "1.0.219", features = ["derive"]} serde_json = "1.0.140" serde_with = "3.12.0" thiserror = "2.0.12" - -[dev-dependencies] -async-openai = "0.29.0" -tokio = { version = "1.0", features = ["full"] } diff --git a/crates/hermesllm/src/apis/openai.rs b/crates/hermesllm/src/apis/openai.rs index ea2d8cd9..1fdf49c8 100644 --- a/crates/hermesllm/src/apis/openai.rs +++ b/crates/hermesllm/src/apis/openai.rs @@ -838,549 +838,4 @@ mod tests { assert!(converted.name.is_none()); assert!(converted.tool_call_id.is_none()); } - - // ============================================================================ - // INTEGRATION TESTS WITH async-openai - // ============================================================================ - - mod integration_tests { - use super::*; - use async_openai::{ - types::{ - ChatCompletionRequestUserMessageArgs, - CreateChatCompletionRequestArgs, - ChatCompletionToolArgs, - ChatCompletionToolType, FunctionObjectArgs, - Role as AsyncOpenAIRole, - }, - }; - - /// Helper to convert our Role to async-openai Role - #[allow(dead_code)] - fn convert_role(role: Role) -> AsyncOpenAIRole { - match role { - Role::System => AsyncOpenAIRole::System, - Role::User => AsyncOpenAIRole::User, - Role::Assistant => AsyncOpenAIRole::Assistant, - Role::Tool => AsyncOpenAIRole::Tool, - } - } - - /// Helper to convert async-openai Role to our Role - #[allow(dead_code)] - fn convert_role_back(role: AsyncOpenAIRole) -> Role { - match role { - AsyncOpenAIRole::System => Role::System, - AsyncOpenAIRole::User => Role::User, - AsyncOpenAIRole::Assistant => Role::Assistant, - AsyncOpenAIRole::Tool => Role::Tool, - AsyncOpenAIRole::Function => Role::Tool, // Map Function to Tool (legacy support) - } - } - - #[tokio::test] - async fn test_required_fields_compatibility() { - // Test 1: Required fields req/response compatibility - - // Create our minimal request - let our_request = ChatCompletionsRequest { - model: "gpt-4".to_string(), - messages: vec![Message { - role: Role::User, - content: MessageContent::Text("Hello, world!".to_string()), - name: None, - tool_calls: None, - tool_call_id: None, - }], - frequency_penalty: None, - function_call: None, - functions: None, - logit_bias: None, - logprobs: None, - max_completion_tokens: None, - max_tokens: None, - modalities: None, - metadata: None, - n: None, - presence_penalty: None, - parallel_tool_calls: None, - prediction: None, - response_format: None, - seed: None, - service_tier: None, - stop: None, - store: None, - stream: None, - stream_options: None, - temperature: None, - tool_choice: None, - tools: None, - top_p: None, - top_logprobs: None, - user: None, - }; - - // Create equivalent async-openai request - let async_openai_request = CreateChatCompletionRequestArgs::default() - .model("gpt-4") - .messages(vec![ - ChatCompletionRequestUserMessageArgs::default() - .content("Hello, world!") - .build() - .unwrap().into(), - ]) - .build() - .unwrap(); - - // Serialize both and compare JSON structure - let our_json = serde_json::to_value(&our_request).unwrap(); - let async_openai_json = serde_json::to_value(&async_openai_request).unwrap(); - - // Both should have the required fields - assert_eq!(our_json["model"], "gpt-4"); - assert_eq!(async_openai_json["model"], "gpt-4"); - - // Messages should be structured the same way - let our_messages = our_json["messages"].as_array().unwrap(); - let async_openai_messages = async_openai_json["messages"].as_array().unwrap(); - - assert_eq!(our_messages.len(), 1); - assert_eq!(async_openai_messages.len(), 1); - - assert_eq!(our_messages[0]["role"], "user"); - assert_eq!(async_openai_messages[0]["role"], "user"); - - assert_eq!(our_messages[0]["content"], "Hello, world!"); - assert_eq!(async_openai_messages[0]["content"], "Hello, world!"); - } - - #[tokio::test] - async fn test_optional_fields_compatibility() { - // Test 2: Optional fields req/response compatibility - - let our_request = ChatCompletionsRequest { - model: "gpt-4".to_string(), - messages: vec![Message { - role: Role::User, - content: MessageContent::Text("Test with options".to_string()), - name: Some("test_user".to_string()), - tool_calls: None, - tool_call_id: None, - }], - temperature: Some(0.7), - max_tokens: Some(150), - stream: Some(false), - top_p: Some(0.95), - frequency_penalty: Some(0.1), - presence_penalty: Some(0.2), - stop: Some(vec!["STOP".to_string()]), - user: Some("user123".to_string()), - // Set remaining fields to None - function_call: None, - functions: None, - logit_bias: None, - logprobs: None, - max_completion_tokens: None, - modalities: None, - metadata: None, - n: None, - parallel_tool_calls: None, - prediction: None, - response_format: None, - seed: None, - service_tier: None, - store: None, - stream_options: None, - tool_choice: None, - tools: None, - top_logprobs: None, - }; - - let async_openai_request = CreateChatCompletionRequestArgs::default() - .model("gpt-4") - .messages(vec![ - ChatCompletionRequestUserMessageArgs::default() - .content("Test with options") - .name("test_user") - .build() - .unwrap().into(), - ]) - .temperature(0.7) - .max_tokens(150u32) - .stream(false) - .top_p(0.95) - .frequency_penalty(0.1) - .presence_penalty(0.2) - .stop(vec!["STOP".to_string()]) - .user("user123") - .build() - .unwrap(); - - let our_json = serde_json::to_value(&our_request).unwrap(); - let async_openai_json = serde_json::to_value(&async_openai_request).unwrap(); - - // Check that optional fields match - assert!((our_json["temperature"].as_f64().unwrap() - 0.7).abs() < 1e-6); - assert!((async_openai_json["temperature"].as_f64().unwrap() - 0.7).abs() < 1e-6); - - assert_eq!(our_json["max_tokens"], 150); - assert_eq!(async_openai_json["max_tokens"], 150); - - assert_eq!(our_json["stream"], false); - assert_eq!(async_openai_json["stream"], false); - - assert_eq!(our_json["user"], "user123"); - assert_eq!(async_openai_json["user"], "user123"); - - // Check message name field - let our_message = &our_json["messages"].as_array().unwrap()[0]; - let async_openai_message = &async_openai_json["messages"].as_array().unwrap()[0]; - - assert_eq!(our_message["name"], "test_user"); - assert_eq!(async_openai_message["name"], "test_user"); - } - - #[tokio::test] - async fn test_streaming_response_compatibility() { - // Test 3: Streaming response compatibility - - // Create a mock streaming response using our types - let our_stream_response = ChatCompletionsStreamResponse { - id: "chatcmpl-123".to_string(), - object: "chat.completion.chunk".to_string(), - created: 1677652288, - model: "gpt-4".to_string(), - choices: vec![StreamChoice { - index: 0, - delta: MessageDelta { - role: Some(Role::Assistant), - content: Some("Hello! ".to_string()), - refusal: None, - function_call: None, - tool_calls: None, - }, - finish_reason: None, - logprobs: None, - }], - usage: None, - system_fingerprint: Some("fp_44709d6fcb".to_string()), - service_tier: None, - }; - - let our_json = serde_json::to_value(&our_stream_response).unwrap(); - - // Verify structure matches expected streaming response format - assert_eq!(our_json["id"], "chatcmpl-123"); - assert_eq!(our_json["object"], "chat.completion.chunk"); - assert_eq!(our_json["created"], 1677652288); - assert_eq!(our_json["model"], "gpt-4"); - - let choices = our_json["choices"].as_array().unwrap(); - assert_eq!(choices.len(), 1); - - let choice = &choices[0]; - assert_eq!(choice["index"], 0); - assert_eq!(choice["delta"]["role"], "assistant"); - assert_eq!(choice["delta"]["content"], "Hello! "); - assert!(!choice["delta"].as_object().unwrap().contains_key("refusal")); - - // Test final streaming chunk with usage - let final_chunk = ChatCompletionsStreamResponse { - id: "chatcmpl-123".to_string(), - object: "chat.completion.chunk".to_string(), - created: 1677652288, - model: "gpt-4".to_string(), - choices: vec![StreamChoice { - index: 0, - delta: MessageDelta { - role: None, - content: None, - refusal: None, - function_call: None, - tool_calls: None, - }, - finish_reason: Some(FinishReason::Stop), - logprobs: None, - }], - usage: Some(Usage { - prompt_tokens: 20, - completion_tokens: 30, - total_tokens: 50, - prompt_tokens_details: None, - completion_tokens_details: None, - }), - system_fingerprint: Some("fp_44709d6fcb".to_string()), - service_tier: None, - }; - - let final_json = serde_json::to_value(&final_chunk).unwrap(); - assert_eq!(final_json["choices"][0]["finish_reason"], "stop"); - assert_eq!(final_json["usage"]["total_tokens"], 50); - } - - #[tokio::test] - async fn test_tool_calls_compatibility() { - // Test 4: Request with tool calls and streaming response with tools - - // Create our tool definition - let weather_tool = Tool { - tool_type: "function".to_string(), - function: Function { - name: "get_weather".to_string(), - description: Some("Get the current weather in a location".to_string()), - parameters: json!({ - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA" - } - }, - "required": ["location"] - }), - strict: None, - }, - }; - - // Create request with tools - let our_request = ChatCompletionsRequest { - model: "gpt-4".to_string(), - messages: vec![Message { - role: Role::User, - content: MessageContent::Text("What's the weather like in San Francisco?".to_string()), - name: None, - tool_calls: None, - tool_call_id: None, - }], - tools: Some(vec![weather_tool]), - tool_choice: Some(ToolChoice::String("auto".to_string())), - stream: Some(true), - stream_options: Some(StreamOptions { - include_usage: Some(true), - }), - // Set all other optional fields to None - frequency_penalty: None, - function_call: None, - functions: None, - logit_bias: None, - logprobs: None, - max_completion_tokens: None, - max_tokens: None, - modalities: None, - metadata: None, - n: None, - presence_penalty: None, - parallel_tool_calls: None, - prediction: None, - response_format: None, - seed: None, - service_tier: None, - stop: None, - store: None, - temperature: None, - top_p: None, - top_logprobs: None, - user: None, - }; - - // Create equivalent async-openai request with tools - let async_openai_tool = ChatCompletionToolArgs::default() - .r#type(ChatCompletionToolType::Function) - .function( - FunctionObjectArgs::default() - .name("get_weather") - .description("Get the current weather in a location") - .parameters(json!({ - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA" - } - }, - "required": ["location"] - })) - .build() - .unwrap(), - ) - .build() - .unwrap(); - - let async_openai_request = CreateChatCompletionRequestArgs::default() - .model("gpt-4") - .messages(vec![ - ChatCompletionRequestUserMessageArgs::default() - .content("What's the weather like in San Francisco?") - .build() - .unwrap().into(), - ]) - .tools(vec![async_openai_tool]) - .tool_choice("auto") - .build() - .unwrap(); - - // Compare JSON structures - let our_json = serde_json::to_value(&our_request).unwrap(); - let async_openai_json = serde_json::to_value(&async_openai_request).unwrap(); - - // Verify tool structure matches - let our_tools = our_json["tools"].as_array().unwrap(); - let async_openai_tools = async_openai_json["tools"].as_array().unwrap(); - - assert_eq!(our_tools.len(), 1); - assert_eq!(async_openai_tools.len(), 1); - - let our_tool = &our_tools[0]; - let async_openai_tool = &async_openai_tools[0]; - - assert_eq!(our_tool["type"], "function"); - assert_eq!(async_openai_tool["type"], "function"); - - assert_eq!(our_tool["function"]["name"], "get_weather"); - assert_eq!(async_openai_tool["function"]["name"], "get_weather"); - - // Test streaming response with tool calls - let tool_call_stream_chunk = ChatCompletionsStreamResponse { - id: "chatcmpl-tool-123".to_string(), - object: "chat.completion.chunk".to_string(), - created: 1677652288, - model: "gpt-4".to_string(), - choices: vec![StreamChoice { - index: 0, - delta: MessageDelta { - role: Some(Role::Assistant), - content: None, - refusal: None, - function_call: None, - tool_calls: Some(vec![ToolCallDelta { - index: 0, - id: Some("call_abc123".to_string()), - call_type: Some("function".to_string()), - function: Some(FunctionCallDelta { - name: Some("get_weather".to_string()), - arguments: Some(r#"{"location":"San Francisco, CA"}"#.to_string()), - }), - }]), - }, - finish_reason: Some(FinishReason::ToolCalls), - logprobs: None, - }], - usage: None, - system_fingerprint: Some("fp_44709d6fcb".to_string()), - service_tier: None, - }; - - let tool_call_json = serde_json::to_value(&tool_call_stream_chunk).unwrap(); - - // Verify tool call structure in streaming response - let choice = &tool_call_json["choices"][0]; - assert_eq!(choice["finish_reason"], "tool_calls"); - - let tool_calls = choice["delta"]["tool_calls"].as_array().unwrap(); - assert_eq!(tool_calls.len(), 1); - - let tool_call = &tool_calls[0]; - assert_eq!(tool_call["index"], 0); - assert_eq!(tool_call["id"], "call_abc123"); - assert_eq!(tool_call["type"], "function"); - assert_eq!(tool_call["function"]["name"], "get_weather"); - assert_eq!(tool_call["function"]["arguments"], r#"{"location":"San Francisco, CA"}"#); - - // Test tool response message - let tool_response_message = Message { - role: Role::Tool, - content: MessageContent::Text(r#"{"temperature": "72°F", "condition": "sunny"}"#.to_string()), - name: None, - tool_calls: None, - tool_call_id: Some("call_abc123".to_string()), - }; - - let tool_response_json = serde_json::to_value(&tool_response_message).unwrap(); - assert_eq!(tool_response_json["role"], "tool"); - assert_eq!(tool_response_json["tool_call_id"], "call_abc123"); - assert_eq!(tool_response_json["content"], r#"{"temperature": "72°F", "condition": "sunny"}"#); - } - - #[test] - fn test_cross_conversion_compatibility() { - // Test conversion of complex response using JSON-first approach - let complex_response_json = json!({ - "id": "chatcmpl-complex", - "object": "chat.completion", - "created": 1677652288u64, - "model": "gpt-4", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "I can help you with that!", - "tool_calls": [ - { - "id": "call_xyz789", - "type": "function", - "function": { - "name": "search_database", - "arguments": r#"{"query":"user question"}"# - } - } - ] - }, - "finish_reason": "tool_calls" - } - ], - "usage": { - "prompt_tokens": 25, - "completion_tokens": 35, - "total_tokens": 60, - "prompt_tokens_details": { - "cached_tokens": 10 - }, - "completion_tokens_details": { - "reasoning_tokens": 5 - } - }, - "system_fingerprint": "fp_complex" - }); - - // Deserialize JSON into ChatCompletionsResponse - let deserialized: ChatCompletionsResponse = serde_json::from_value(complex_response_json.clone()).unwrap(); - - // Verify fields are properly deserialized - assert_eq!(deserialized.id, "chatcmpl-complex"); - assert_eq!(deserialized.object, "chat.completion"); - assert_eq!(deserialized.created, 1677652288); - assert_eq!(deserialized.model, "gpt-4"); - assert_eq!(deserialized.choices.len(), 1); - assert_eq!(deserialized.usage.total_tokens, 60); - assert_eq!(deserialized.usage.prompt_tokens, 25); - assert_eq!(deserialized.usage.completion_tokens, 35); - assert_eq!( - deserialized.usage.prompt_tokens_details.as_ref().unwrap().cached_tokens, - Some(10) - ); - assert_eq!( - deserialized.usage.completion_tokens_details.as_ref().unwrap().reasoning_tokens, - Some(5) - ); - assert_eq!(deserialized.system_fingerprint, Some("fp_complex".to_string())); - - let choice = &deserialized.choices[0]; - assert_eq!(choice.index, 0); - assert_eq!(choice.finish_reason, Some(FinishReason::ToolCalls)); - assert!(choice.message.tool_calls.is_some()); - assert_eq!(choice.message.role, Role::Assistant); - assert_eq!(choice.message.content, Some("I can help you with that!".to_string())); - - let tool_call = &choice.message.tool_calls.as_ref().unwrap()[0]; - assert_eq!(tool_call.id, "call_xyz789"); - assert_eq!(tool_call.call_type, "function"); - assert_eq!(tool_call.function.name, "search_database"); - assert_eq!(tool_call.function.arguments, r#"{"query":"user question"}"#); - - // Serialize back to JSON and verify round-trip compatibility - let serialized_json = serde_json::to_value(&deserialized).unwrap(); - assert_eq!(complex_response_json, serialized_json); - } - } }