2025-06-10 12:53:27 -07:00
|
|
|
//! hermesllm: A library for translating LLM API requests and responses
|
|
|
|
|
//! between Mistral, Grok, Gemini, and OpenAI-compliant formats.
|
|
|
|
|
|
|
|
|
|
pub mod providers;
|
2025-08-07 12:42:09 -07:00
|
|
|
pub mod apis;
|
|
|
|
|
pub mod clients;
|
2025-08-20 12:55:29 -07:00
|
|
|
// Re-export important types and traits
|
|
|
|
|
pub use providers::request::{ProviderRequestType, ProviderRequest, ProviderRequestError};
|
2025-09-10 07:40:30 -07:00
|
|
|
pub use providers::response::{ProviderResponseType, ProviderStreamResponseType, ProviderResponse, ProviderStreamResponse, ProviderResponseError, TokenUsage, SseEvent, SseStreamIter};
|
2025-08-20 12:55:29 -07:00
|
|
|
pub use providers::id::ProviderId;
|
2025-09-10 07:40:30 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
//TODO: Refactor such that commons doesn't depend on Hermes. For now this will clean up strings
|
|
|
|
|
pub const CHAT_COMPLETIONS_PATH: &str = "/v1/chat/completions";
|
|
|
|
|
pub const MESSAGES_PATH: &str = "/v1/messages";
|
|
|
|
|
|
2025-08-07 12:42:09 -07:00
|
|
|
|
2025-08-20 12:55:29 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2025-06-10 12:53:27 -07:00
|
|
|
|
2025-08-20 12:55:29 -07:00
|
|
|
#[test]
|
|
|
|
|
fn test_provider_id_conversion() {
|
|
|
|
|
assert_eq!(ProviderId::from("openai"), ProviderId::OpenAI);
|
|
|
|
|
assert_eq!(ProviderId::from("mistral"), ProviderId::Mistral);
|
|
|
|
|
assert_eq!(ProviderId::from("groq"), ProviderId::Groq);
|
|
|
|
|
assert_eq!(ProviderId::from("arch"), ProviderId::Arch);
|
2025-06-10 12:53:27 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 12:55:29 -07:00
|
|
|
#[test]
|
2025-09-10 07:40:30 -07:00
|
|
|
fn test_provider_streaming_response() {
|
|
|
|
|
// Test streaming response parsing with sample SSE data
|
|
|
|
|
let sse_data = r#"data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]}
|
2025-06-10 12:53:27 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
data: [DONE]
|
|
|
|
|
"#;
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
use crate::clients::endpoints::SupportedAPIs;
|
|
|
|
|
let client_api = SupportedAPIs::OpenAIChatCompletions(crate::apis::OpenAIApi::ChatCompletions);
|
|
|
|
|
let upstream_api = SupportedAPIs::OpenAIChatCompletions(crate::apis::OpenAIApi::ChatCompletions);
|
2025-06-10 12:53:27 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
// Test the new simplified architecture - create SseStreamIter directly
|
|
|
|
|
let sse_iter = SseStreamIter::try_from(sse_data.as_bytes());
|
|
|
|
|
assert!(sse_iter.is_ok());
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
let mut streaming_iter = sse_iter.unwrap();
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
// Test that we can iterate over SseEvents
|
|
|
|
|
let first_event = streaming_iter.next();
|
|
|
|
|
assert!(first_event.is_some());
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
let sse_event = first_event.unwrap();
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
// Test SseEvent properties
|
|
|
|
|
assert!(!sse_event.is_done());
|
|
|
|
|
assert!(sse_event.data.as_ref().unwrap().contains("Hello"));
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
// Test that we can parse the event into a provider stream response
|
|
|
|
|
let transformed_event = SseEvent::try_from((sse_event, &client_api, &upstream_api));
|
|
|
|
|
if let Err(e) = &transformed_event {
|
|
|
|
|
println!("Transform error: {:?}", e);
|
|
|
|
|
}
|
|
|
|
|
assert!(transformed_event.is_ok());
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
let transformed_event = transformed_event.unwrap();
|
|
|
|
|
let provider_response = transformed_event.provider_response();
|
|
|
|
|
assert!(provider_response.is_ok());
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
let stream_response = provider_response.unwrap();
|
|
|
|
|
assert_eq!(stream_response.content_delta(), Some("Hello"));
|
|
|
|
|
assert!(!stream_response.is_final());
|
2025-08-20 12:55:29 -07:00
|
|
|
|
2025-09-10 07:40:30 -07:00
|
|
|
// Test that stream ends properly with [DONE] (SseStreamIter should stop before [DONE])
|
|
|
|
|
let final_event = streaming_iter.next();
|
|
|
|
|
assert!(final_event.is_none()); // Should be None because iterator stops at [DONE]
|
2025-06-10 12:53:27 -07:00
|
|
|
}
|
|
|
|
|
}
|