macro_rules! generate_language_model_tests {
(
provider: $provider_type:ident,
api_key_var: $env_key:expr,
model_struct: $model_struct:ident,
default_model: $default_model:expr,
tool_model: $tool_model:expr,
structured_output_model: $structured_output_model:expr,
reasoning_model: $reasoning_model:expr,
embedding_model: $embedding_model:expr,
skip_reasoning: $skip_reasoning:tt,
skip_tool: $skip_tool:tt,
skip_structured_output: $skip_structured_output:tt,
skip_streaming: $skip_streaming:tt,
skip_embedding: $skip_embedding:tt
) => {
use agentsdk::core::tools::ToolExecute;
use agentsdk::core::{
DynamicModel, LanguageModelRequest, LanguageModelStreamChunkType, Message,
language_model::{LanguageModel, LanguageModelResponseContentType, StopReason},
tools::Tool,
};
use agentsdk::tool;
use dotenv::dotenv;
use std::sync::{Arc, Mutex};
#[allow(unused_imports)]
use {futures::StreamExt, schemars::JsonSchema, serde::Deserialize, serde_json::Value};
macro_rules! skip_if_no_api_key {
() => {
dotenv().ok();
if std::env::var($env_key).is_err() {
println!("Skipping test: {} not set", $env_key);
return;
}
};
}
generate_provider_has_default_interface!($provider_type, $model_struct);
generate_basic_tests!($default_model);
generate_language_model_stop_reason_tests!($default_model);
generate_language_model_hook_tests!($tool_model);
generate_language_model_step_id_tests!($default_model);
generate_language_model_streaming_tests!($reasoning_model, $skip_streaming);
generate_language_model_tool_tests!($tool_model, $skip_tool);
generate_language_model_schema_tests!($structured_output_model, $skip_structured_output);
generate_language_model_reasoning_tests!($reasoning_model, $skip_reasoning);
generate_embedding_tests!($embedding_model, $skip_embedding);
};
}
macro_rules! generate_provider_has_default_interface {
($provider_type:ident, $model_struct:ident) => {
#[tokio::test]
async fn test_provider_has_default_interface() {
let provider = $provider_type::<$model_struct>::builder()
.provider_name("test-provider".to_string())
.api_key("test-api-key")
.base_url("http://localhost:8080".to_string())
.path("/custom/path")
.build();
assert!(provider.is_ok());
let provider = provider.unwrap();
assert_eq!(provider.settings.provider_name, "test-provider");
assert_eq!(provider.settings.api_key, "test-api-key");
assert_eq!(provider.settings.base_url, "http://localhost:8080/");
assert_eq!(provider.settings.path, Some("/custom/path".to_string()));
let provider_with_body = $provider_type::<$model_struct>::builder()
.provider_name("test-provider-body".to_string())
.api_key("test-api-key")
.base_url("http://localhost:8080")
.body(serde_json::json!({
"store": false
}))
.build()
.unwrap();
assert_eq!(
provider_with_body.settings.body,
Some(
serde_json::json!({
"store": false
})
.as_object()
.expect("body should be an object")
.clone()
)
);
let provider_with_headers = $provider_type::<$model_struct>::builder()
.provider_name("test-provider-headers".to_string())
.api_key("test-api-key")
.base_url("http://localhost:8080")
.headers(std::collections::HashMap::from([(
"x-trace-id".to_string(),
"trace-123".to_string(),
)]))
.build()
.unwrap();
assert_eq!(
provider_with_headers.settings.headers,
Some(std::collections::HashMap::from([(
"x-trace-id".to_string(),
"trace-123".to_string(),
)]))
);
let provider2 = $provider_type::<$model_struct>::builder()
.provider_name("test-provider2".to_string())
.api_key("test-api-key2")
.base_url("ocalhost:80802".to_string())
.build();
assert!(provider2.is_err());
assert_eq!(
provider2.unwrap_err().to_string(),
"Invalid input: Base URL must start with http:// or https://"
);
let provider3 = $provider_type::<$model_struct>::builder()
.provider_name("test-provider3".to_string())
.api_key("")
.base_url("http://localhost:8080/".to_string())
.build();
assert!(provider3.is_err());
assert_eq!(
provider3.unwrap_err().to_string(),
"A required field is missing: api_key"
);
let provider_no_path = $provider_type::<$model_struct>::builder()
.provider_name("test-provider-no-path")
.api_key("test-api-key")
.base_url("http://localhost:8080")
.build()
.unwrap();
assert_eq!(provider_no_path.settings.path, None);
let _provider_dynamic = $provider_type::model_name("test-model".to_string());
let provider_dynamic_builder = $provider_type::<DynamicModel>::builder()
.model_name("test-model".to_string())
.api_key("test-api-key")
.base_url("http://localhost:8080")
.build()
.unwrap();
assert_eq!(provider_dynamic_builder.name(), "test-model");
}
};
}
macro_rules! generate_basic_tests {
($default_model:expr) => {
#[tokio::test]
async fn test_generate_text_basic() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($default_model)
.prompt("Respond with exactly the word 'hello' in all lowercase.Do not include any punctuation, prefixes, or suffixes.")
.build()
.generate_text()
.await;
assert!(result.is_ok());
let text = result
.as_ref()
.expect("")
.text()
.unwrap()
.trim()
.to_string();
assert!(text.contains("hello"));
}
#[tokio::test]
async fn test_generate_text_with_system_prompt() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($default_model)
.system("Only say hello whatever the user says. all lowercase no punctuation, prefixes, or suffixes.")
.prompt("Hello how are you doing?")
.build()
.generate_text()
.await;
assert!(result.is_ok());
let text = result
.as_ref()
.expect("Failed to get result")
.text()
.unwrap()
.trim()
.to_string();
assert!(text.contains("hello"));
}
#[tokio::test]
async fn test_generate_text_with_messages() {
skip_if_no_api_key!();
let messages = Message::builder()
.system("You are a helpful assistant.")
.user("Whatsup?, Surafel is here")
.assistant("How could I help you?")
.user("Could you tell my name?")
.build();
let mut language_model = LanguageModelRequest::builder()
.model($default_model)
.messages(messages)
.build();
let result = language_model.generate_text().await;
assert!(result.is_ok());
let text = result
.as_ref()
.expect("Failed to get result")
.text()
.unwrap()
.trim()
.to_string();
assert!(text.contains("Surafel"));
}
#[tokio::test]
async fn test_generate_text_with_messages_and_system_prompt() {
skip_if_no_api_key!();
let messages = Message::builder()
.system("Only say hello whatever the user says. \n all lowercase no punctuation, prefixes, or suffixes.")
.user("Whatsup?, Surafel is here")
.assistant("How could I help you?")
.user("Could you tell my name?")
.build();
let result = LanguageModelRequest::builder()
.model($default_model)
.system("Only say hello whatever the user says. all lowercase no punctuation, prefixes, or suffixes.")
.messages(messages)
.build()
.generate_text()
.await;
assert!(result.is_ok());
let text = result
.as_ref()
.expect("Failed to get result")
.text()
.unwrap()
.trim()
.to_string();
assert!(text.contains("hello"));
}
};
}
macro_rules! generate_language_model_stop_reason_tests {
($default_model:expr) => {
#[tokio::test]
async fn test_stop_reason_normal_finish() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($default_model)
.prompt("Respond with exactly the word 'hello' in all lowercase. Do not include any punctuation.")
.build()
.generate_text()
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(matches!(response.stop_reason(), Some(StopReason::Finish)));
}
#[tokio::test]
async fn test_stop_reason_hook_stop() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($default_model)
.prompt("just response with 'HI' nothing less nothing more")
.stop_when(|_| true) .build()
.generate_text()
.await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(matches!(response.stop_reason(), Some(StopReason::Hook)));
}
#[tokio::test]
async fn test_stop_reason_api_error() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($default_model)
.prompt("Hello")
.build()
.generate_text()
.await;
if let Ok(response) = result {
assert!(matches!(response.stop_reason(), Some(StopReason::Finish)));
} else {
assert!(result.is_err());
}
}
#[tokio::test]
async fn test_stop_reason_stream_finish() {
skip_if_no_api_key!();
let mut response = LanguageModelRequest::builder()
.model($default_model)
.prompt("Respond with 'world'")
.build()
.stream_text()
.await
.unwrap();
while let Some(_) = response.stream.next().await {}
assert!(matches!(response.stop_reason().await, Some(StopReason::Finish)));
}
};
}
macro_rules! generate_language_model_streaming_tests {
($reasoning_model:expr, true) => {
};
($reasoning_model:expr, false) => {
#[tokio::test]
async fn test_generate_stream_basic() {
skip_if_no_api_key!();
let response = LanguageModelRequest::builder()
.model($reasoning_model)
.prompt("Respond with exactly the word 'hello' in all lowercase.Do not include any punctuation, prefixes, or suffixes.")
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
let mut buf = String::new();
while let Some(chunk) = stream.next().await {
if let LanguageModelStreamChunkType::TextDelta(text) = chunk {
buf.push_str(&text);
}
}
assert!(buf.contains("hello"));
}
#[tokio::test]
async fn test_streaming_with_reasoning_effort() {
skip_if_no_api_key!();
let response = LanguageModelRequest::builder()
.model($reasoning_model)
.prompt("Count from 1 to 5 step by step")
.reasoning_effort(agentsdk::core::language_model::ReasoningEffort::Medium)
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
let mut chunks_received = 0;
while let Some(chunk) = stream.next().await {
if let LanguageModelStreamChunkType::TextDelta(_) = chunk {
chunks_received += 1;
}
}
assert!(chunks_received > 0);
}
};
}
macro_rules! generate_language_model_tool_tests {
($tool_model:expr, true) => {
};
($tool_model:expr, false) => {
#[tokio::test]
async fn test_generate_text_with_tools() {
skip_if_no_api_key!();
#[tool]
fn get_username() -> Tool {
Ok("ishak".to_string())
}
let response = LanguageModelRequest::builder()
.model($tool_model)
.system("Call a tool to get the username.")
.prompt("What is the username?")
.with_tool(get_username())
.build()
.generate_text()
.await
.unwrap();
assert!(response.text().unwrap().contains("ishak"));
}
#[tokio::test]
async fn test_generate_text_with_tools_input() {
skip_if_no_api_key!();
#[tool]
fn get_username(user_id: String) -> Tool {
match user_id.as_str() {
"123" => Ok("sura".to_string()),
_ => Ok("invalid".to_string()),
}
}
let response = LanguageModelRequest::builder()
.model($tool_model)
.system("you are a helpful assistant.")
.prompt("What is the username with user id 123?")
.with_tool(get_username())
.build()
.generate_text()
.await
.unwrap();
assert!(response.text().unwrap().contains("sura"));
}
#[tokio::test]
async fn test_generate_stream_with_tools() {
skip_if_no_api_key!();
#[tool]
fn get_username() -> Tool {
Ok("ishak".to_string())
}
let response = LanguageModelRequest::builder()
.model($tool_model)
.system("Call a tool to get the username.")
.prompt("What is the username?")
.with_tool(get_username())
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
let mut buf = String::new();
while let Some(chunk) = stream.next().await {
if let LanguageModelStreamChunkType::TextDelta(text) = chunk {
buf.push_str(&text);
}
}
assert!(buf.contains("ishak"));
}
#[tokio::test]
async fn test_generate_stream_with_tools_input() {
skip_if_no_api_key!();
#[tool]
fn get_username(user_id: String) -> Tool {
match user_id.as_str() {
"123" => Ok("sura".to_string()),
_ => Ok("invalid".to_string()),
}
}
let response = LanguageModelRequest::builder()
.model($tool_model)
.system("You are a helpful assistant.")
.prompt("What is the username for user id '123'?")
.with_tool(get_username())
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
let mut buf = String::new();
while let Some(chunk) = stream.next().await {
if let LanguageModelStreamChunkType::TextDelta(text) = chunk {
buf.push_str(&text);
}
}
assert!(buf.contains("sura"));
}
#[tokio::test]
async fn test_generate_stream_with_structs() {
skip_if_no_api_key!();
#[allow(unused_variables)]
let func = ToolExecute::from_sync(|_ctx, inp: Value| {
let location = inp.get("location").unwrap();
Ok(format!("Cloudy"))
});
#[derive(schemars::JsonSchema, Debug)]
#[allow(dead_code)]
struct ToolInput {
location: String,
}
let schema = schemars::schema_for!(ToolInput);
let get_weather_tool = Tool::builder()
.name("get-weather")
.description("Get the weather information given a location")
.input_schema(schema.clone())
.execute(func)
.build()
.unwrap();
let result = LanguageModelRequest::builder()
.model($tool_model)
.system("You are a helpful assistant with access to tools.")
.prompt("What is the weather in New York?")
.with_tool(get_weather_tool) .build()
.generate_text()
.await;
assert!(result.is_ok());
}
};
}
macro_rules! generate_language_model_schema_tests {
($structured_output_model:expr, true) => {
};
($structured_output_model:expr, false) => {
#[tokio::test]
async fn test_generate_text_with_output_schema() {
skip_if_no_api_key!();
#[derive(Debug, JsonSchema, Deserialize)]
#[allow(dead_code)]
struct User {
name: String,
age: u32,
email: String,
phone: String,
}
let result = LanguageModelRequest::builder()
.model($structured_output_model)
.prompt("generate user with dummy data, and and name of 'John Doe'")
.schema::<User>()
.build()
.generate_text()
.await
.unwrap();
let user: User = result.into_schema().unwrap();
assert_eq!(user.name, "John Doe");
}
#[tokio::test]
async fn test_stream_text_with_output_schema() {
skip_if_no_api_key!();
#[derive(Debug, JsonSchema, Deserialize)]
#[allow(dead_code)]
struct User {
name: String,
age: u32,
email: String,
phone: String,
}
let response = LanguageModelRequest::builder()
.model($structured_output_model)
.prompt("generate user with dummy data, and add name of 'John Doe'")
.schema::<User>()
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
let mut buf = String::new();
while let Some(chunk) = stream.next().await {
if let LanguageModelStreamChunkType::TextDelta(text) = chunk {
buf.push_str(&text);
}
}
let user: User = serde_json::from_str(&buf).unwrap();
assert_eq!(user.name, "John Doe");
}
};
}
macro_rules! generate_language_model_hook_tests {
($tool_model:expr) => {
#[tokio::test]
async fn test_on_step_start_executes_before_each_step() {
skip_if_no_api_key!();
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
#[tool]
fn get_neighborhood() -> Tool {
Ok("ankocha".to_string())
}
let _ = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. Return the neighborhood. Nothing more and nothing less")
.prompt("What is the neighborhood?")
.with_tool(get_neighborhood())
.on_step_start(move |_| {
let mut c = counter_clone.lock().unwrap();
*c += 1;
})
.build()
.generate_text()
.await
.unwrap();
let count = *counter.lock().unwrap();
assert!(count >= 2); }
#[tokio::test]
async fn test_on_step_finish_executes_after_each_step() {
skip_if_no_api_key!();
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
#[tool]
fn get_neighborhood() -> Tool {
Ok("ankocha".to_string())
}
let _ = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. Return the neighborhood. Nothing more and nothing less")
.prompt("What is the neighborhood?")
.with_tool(get_neighborhood())
.on_step_finish(move |_| {
let mut c = counter_clone.lock().unwrap();
*c += 1;
})
.build()
.generate_text()
.await
.unwrap();
let count = *counter.lock().unwrap();
assert!(count >= 2);
}
#[tokio::test]
async fn test_hooks_run_in_correct_order() {
skip_if_no_api_key!();
let log = Arc::new(Mutex::new(Vec::new()));
let log_prepare = Arc::clone(&log);
let log_finish = Arc::clone(&log);
#[tool]
fn get_neighbourhood() -> Tool {
Ok("ankocha".to_string())
}
let result = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. Return the neighborhood. Nothing more and nothing less")
.prompt("What is the neighborhood?")
.with_tool(get_neighbourhood())
.on_step_start(move |_| {
log_prepare.lock().unwrap().push("prepare");
})
.on_step_finish(move |_| {
log_finish.lock().unwrap().push("finish");
})
.build()
.generate_text()
.await
.unwrap();
assert!(result.text().is_some());
let log = log.lock().unwrap();
let mut i = 0;
while i + 1 < log.len() {
assert_eq!(log[i], "prepare");
assert_eq!(log[i + 1], "finish");
i += 2;
}
}
#[tokio::test]
async fn test_stop_when_halts_during_tool_call() {
skip_if_no_api_key!();
#[tool]
fn get_neighborhood() -> Tool {
Ok("done".to_string())
}
let result = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. Return the neighborhood. Nothing more and nothing less")
.prompt("What is the neighborhood?")
.with_tool(get_neighborhood())
.stop_when(|_| true) .build()
.generate_text()
.await
.unwrap();
let step_ids = result.step_ids();
assert!(step_ids.len() < 5); assert!(matches!(result.stop_reason().unwrap(), StopReason::Hook));
}
#[tokio::test]
async fn test_stop_when_halts_during_streaming() {
skip_if_no_api_key!();
#[tool]
fn get_neighbourhood() -> Tool {
Ok("ankocha".to_string())
}
let response = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. Return the neighborhood. Nothing more and nothing less")
.prompt("What is the neighborhood?")
.with_tool(get_neighbourhood())
.stop_when(|_| true)
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
let mut found_incomplete = false;
while let Some(chunk) = stream.next().await {
if let LanguageModelStreamChunkType::Incomplete(_) = chunk {
found_incomplete = true;
break;
}
}
assert!(found_incomplete);
}
#[tokio::test]
async fn test_stop_when_false_allows_continuation() {
skip_if_no_api_key!();
#[tool]
fn get_neighbourhood() -> Tool {
Ok("anckocha".to_string())
}
let result = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. Return the neighborhood. Nothing more and nothing less")
.prompt("What is the neighborhood?")
.with_tool(get_neighbourhood())
.stop_when(|_| false) .build()
.generate_text()
.await
.unwrap();
let step_ids = result.step_ids();
assert!(step_ids.len() >= 5); assert!(result.text().is_some());
}
#[tokio::test]
async fn test_hooks_persist_across_multiple_steps() {
skip_if_no_api_key!();
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
#[tool]
fn get_neighbourhood() -> Tool {
Ok("You can find the neighborhood if you call the get_neighbourhood_2 tool".to_string())
}
#[tool]
fn get_neighbourhood_2() -> Tool {
Ok("ankocha".to_string())
}
let _ = LanguageModelRequest::builder()
.model($tool_model)
.system(
"Call the tool get_neighbourhood. Return the neighborhood.
Nothing more and nothing less. If you can't find the neighborhood,
call the tool get_neighbourhood_2. Return the neighborhood.
Nothing more and nothing less",
)
.prompt("What is the neighborhood?")
.with_tool(get_neighbourhood())
.with_tool(get_neighbourhood_2())
.on_step_finish(move |_| {
let mut c = counter_clone.lock().unwrap();
*c += 1;
})
.build()
.generate_text()
.await
.unwrap();
let count = *counter.lock().unwrap();
assert!(count >= 3); }
#[tokio::test]
async fn test_hooks_cloned_via_arc() {
skip_if_no_api_key!();
let called = Arc::new(Mutex::new(false));
let called_clone = Arc::clone(&called);
let _ = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.on_step_finish(move |_| {
*called_clone.lock().unwrap() = true;
})
.build()
.generate_text()
.await
.unwrap();
assert!(*called.lock().unwrap());
}
#[tokio::test]
async fn test_no_panic_when_hooks_none() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.build()
.generate_text()
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_on_step_start_mutates_options() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.on_step_start(|opts| {
opts.system = Some("Updated system message!!!".to_string());
})
.build()
.generate_text()
.await
.unwrap();
assert!(result.text().is_some());
assert_eq!(result.options.system.unwrap(), "Updated system message!!!");
}
#[tokio::test]
async fn test_hook_isolation() {
skip_if_no_api_key!();
let result_no_hooks = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.build()
.generate_text()
.await
.unwrap();
let result_with_hooks = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.on_step_finish(|_| {})
.build()
.generate_text()
.await
.unwrap();
assert!(result_no_hooks.text().is_some());
assert!(result_with_hooks.text().is_some());
}
#[tokio::test]
async fn test_on_step_finish_for_text_reasoning_and_tool_call() {
skip_if_no_api_key!();
let called_for_text = Arc::new(Mutex::new(false));
let called_for_tool = Arc::new(Mutex::new(false));
let text_clone = Arc::clone(&called_for_text);
let tool_clone = Arc::clone(&called_for_tool);
#[tool]
fn get_username() -> Tool {
Ok("ishak".to_string())
}
let result = LanguageModelRequest::builder()
.model($tool_model)
.system("Call the tool. to find the username. and return only the username nothing more and nothing less")
.prompt("What is the username")
.with_tool(get_username())
.on_step_finish(move |opts| {
if let Some(Message::Assistant(assistant_msg)) = opts.messages().last() {
match &assistant_msg.content {
LanguageModelResponseContentType::ToolCall(_) => {
*tool_clone.lock().unwrap() = true;
}
LanguageModelResponseContentType::Text(_) => {
*text_clone.lock().unwrap() = true;
}
LanguageModelResponseContentType::Reasoning { .. } => {
*text_clone.lock().unwrap() = true;
}
_ => {}
}
}
})
.build()
.generate_text()
.await
.unwrap();
assert!(!*called_for_tool.lock().unwrap());
assert!(*called_for_text.lock().unwrap());
assert_eq!(result.text().unwrap(), "ishak");
}
#[tokio::test]
async fn test_streaming_on_step_start_before_start() {
skip_if_no_api_key!();
let called = Arc::new(Mutex::new(false));
let called_clone = Arc::clone(&called);
let mut response = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.on_step_start(move |_| {
*called_clone.lock().unwrap() = true;
})
.build()
.stream_text()
.await
.unwrap();
while let Some(_) = response.stream.next().await {}
assert!(*called.lock().unwrap()); }
#[tokio::test]
async fn test_streaming_on_step_finish_at_end() {
skip_if_no_api_key!();
let called = Arc::new(Mutex::new(false));
let called_clone = Arc::clone(&called);
let response = LanguageModelRequest::builder()
.model($tool_model)
.prompt("Say hello")
.on_step_finish(move |_| {
*called_clone.lock().unwrap() = true;
})
.build()
.stream_text()
.await
.unwrap();
let mut stream = response.stream;
while stream.next().await.is_some() {}
assert!(*called.lock().unwrap()); }
};
}
macro_rules! generate_language_model_step_id_tests {
($default_model:expr) => {
#[tokio::test]
async fn test_step_id_basic_assignment() {
skip_if_no_api_key!();
let result = LanguageModelRequest::builder()
.model($default_model)
.system("Do the exact instructions you are told")
.prompt("Respond with exactly 'test' in lowercase.")
.build()
.generate_text()
.await
.unwrap();
let step_ids = result.step_ids();
assert_eq!(step_ids.len(), 3);
assert_eq!(step_ids[0], 0); assert_eq!(step_ids[1], 0); assert_eq!(step_ids[2], 1); }
#[tokio::test]
async fn test_step_id_tool_call_flow() {
skip_if_no_api_key!();
#[tool]
fn get_test_value() -> Tool {
Ok("test_value".to_string())
}
let result = LanguageModelRequest::builder()
.model($default_model)
.system("Call the tool to get the test value.")
.prompt("What is the test value?")
.with_tool(get_test_value())
.build()
.generate_text()
.await
.unwrap();
let step_ids = dbg!(&result).step_ids();
assert!(dbg!(&step_ids).len() >= 5);
assert_eq!(step_ids[0], 0);
assert_eq!(step_ids[1], 0);
assert_eq!(step_ids[2], 1); assert_eq!(step_ids[3], 1); assert_eq!(step_ids[4], 2); assert!(result.text().unwrap().contains("test_value"));
}
#[tokio::test]
async fn test_step_id_streaming() {
skip_if_no_api_key!();
let response = LanguageModelRequest::builder()
.model($default_model)
.system("Do the exact instructions you are told")
.prompt("Respond with 'stream test'")
.build()
.stream_text()
.await
.unwrap();
let step_ids = response.step_ids().await;
assert_eq!(step_ids.len(), 2);
assert_eq!(step_ids[0], 0);
assert_eq!(step_ids[1], 0);
}
};
}
macro_rules! generate_language_model_reasoning_tests {
($reasoning_model:expr, true) => {
};
($reasoning_model:expr, false) => {
#[tokio::test]
async fn test_reasoning_effort_with_non_reasoning_model() {
skip_if_no_api_key!();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| async {
LanguageModelRequest::builder()
.model($reasoning_model)
.prompt("What is 2 + 2? Answer with just the number.")
.reasoning_effort(agentsdk::core::language_model::ReasoningEffort::Low)
.build()
.generate_text()
.await
}))
.map(|future| tokio::runtime::Handle::current().block_on(future));
assert!(result.is_err());
}
};
}
macro_rules! generate_embedding_tests {
($embedding_model:expr, true) => {
};
($embedding_model:expr, false) => {
use agentsdk::core::embedding_model::EmbeddingModelRequest;
#[tokio::test]
async fn test_single_embedding_request() {
skip_if_no_api_key!();
let result = EmbeddingModelRequest::builder()
.model($embedding_model)
.input(vec!["Hello, world!".to_string()])
.dimensions(100)
.build()
.embed()
.await
.expect("Embedding request failed");
assert!(!result.is_empty(), "Expected at least one embedding");
assert!(
!result[0].is_empty(),
"Expected embedding vector to be non-empty"
);
assert_eq!(result[0].len(), 100);
for value in &result[0] {
assert!(
value.is_finite(),
"Embedding values should be finite floats"
);
}
}
#[tokio::test]
async fn test_embedding_with_multiple_inputs() {
skip_if_no_api_key!();
let inputs = vec![
"Hello, world!".to_string(),
"This is a test".to_string(),
"Multiple embeddings".to_string(),
];
let result = EmbeddingModelRequest::builder()
.model($embedding_model)
.input(inputs.clone())
.build()
.embed()
.await
.expect("Embedding request failed");
assert_eq!(
result.len(),
inputs.len(),
"Expected {} embeddings, got {}",
inputs.len(),
result.len()
);
for (i, embedding) in result.iter().enumerate() {
assert!(!embedding.is_empty(), "Embedding {} should not be empty", i);
for (j, value) in embedding.iter().enumerate() {
assert!(
value.is_finite(),
"Embedding {} value {} should be a finite float",
i,
j
);
}
}
}
};
}