1use std::fmt::Display;
17
18use serde::{Deserialize, Serialize};
19
20#[cfg(all(
28 target_arch = "wasm32",
29 not(target_os = "wasi"),
30 any(
31 feature = "openai",
32 feature = "anthropic",
33 feature = "ollama",
34 feature = "deepseek",
35 feature = "xai",
36 feature = "phind",
37 feature = "google",
38 feature = "groq",
39 feature = "azure_openai",
40 feature = "openrouter",
41 feature = "minimax"
42 )
43))]
44compile_error!(
45 "autoagents-llm HTTP provider backends are not supported on non-WASI wasm32 targets \
46(such as wasm32-unknown-unknown). Use a native target, or target wasm32-wasip2 with the \
47`wasi-http` feature for the OpenAI Responses backend."
48);
49
50#[cfg(all(
53 target_arch = "wasm32",
54 target_os = "wasi",
55 target_env = "p1",
56 any(
57 feature = "openai",
58 feature = "anthropic",
59 feature = "ollama",
60 feature = "deepseek",
61 feature = "xai",
62 feature = "phind",
63 feature = "google",
64 feature = "groq",
65 feature = "azure_openai",
66 feature = "openrouter",
67 feature = "minimax"
68 )
69))]
70compile_error!(
71 "autoagents-llm HTTP provider backends are not supported on wasm32-wasip1. \
72WASI HTTP requires the Preview2 target (wasm32-wasip2); rebuild with `--target wasm32-wasip2` \
73and the `wasi-http` feature for the OpenAI Responses backend."
74);
75
76#[cfg(all(
79 target_arch = "wasm32",
80 target_os = "wasi",
81 target_env = "p2",
82 feature = "openai",
83 not(feature = "wasi-http")
84))]
85compile_error!(
86 "autoagents-llm OpenAI Responses backend on wasm32-wasip2 requires the `wasi-http` feature. \
87Rebuild with `--features openai,wasi-http`."
88);
89
90#[cfg(all(
92 target_arch = "wasm32",
93 target_os = "wasi",
94 target_env = "p2",
95 any(
96 feature = "anthropic",
97 feature = "ollama",
98 feature = "deepseek",
99 feature = "xai",
100 feature = "phind",
101 feature = "google",
102 feature = "groq",
103 feature = "azure_openai",
104 feature = "openrouter",
105 feature = "minimax"
106 )
107))]
108compile_error!(
109 "autoagents-llm only supports the OpenAI Responses backend on wasm32-wasip2 in this release. \
110Remove the non-openai HTTP provider features, or build for a native target."
111);
112
113pub mod backends;
115
116pub mod builder;
118
119pub mod chat;
121
122pub mod completion;
124
125pub mod embedding;
127
128pub mod error;
130
131pub mod config;
133
134#[cfg(any(
136 not(target_arch = "wasm32"),
137 all(
138 target_arch = "wasm32",
139 target_os = "wasi",
140 target_env = "p2",
141 feature = "wasi-http"
142 )
143))]
144pub mod http;
145
146pub mod evaluator;
148
149#[cfg(not(target_arch = "wasm32"))]
151pub mod secret_store;
152
153pub mod models;
155
156mod protocol;
157pub mod providers;
158mod request_diagnostics;
159
160pub mod pipeline;
162
163#[cfg(all(not(target_arch = "wasm32"), feature = "optim"))]
165pub mod optim;
166
167#[cfg(all(
170 target_arch = "wasm32",
171 target_os = "wasi",
172 target_env = "p2",
173 feature = "wasi-http"
174))]
175mod wasi_http;
176
177pub use async_trait::async_trait;
179pub use chat::SamplingOverrides;
180
181#[derive(Debug, Default, Clone)]
183pub struct NoConfig;
184
185pub trait HasConfig {
190 type Config: Default + Send + Sync + 'static;
192}
193
194pub trait LLMProvider:
197 chat::ChatProvider
198 + completion::CompletionProvider
199 + embedding::EmbeddingProvider
200 + models::ModelsProvider
201 + Send
202 + Sync
203 + 'static
204{
205}
206
207#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
210pub struct ToolCall {
211 pub id: String,
213 #[serde(rename = "type")]
215 pub call_type: String,
216 pub function: FunctionCall,
218}
219
220impl Display for ToolCall {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 write!(
223 f,
224 "ToolCall {{ id: {}, type: {}, function: {:?} }}",
225 self.id, self.call_type, self.function
226 )
227 }
228}
229
230#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
232pub struct FunctionCall {
233 pub name: String,
235 pub arguments: String,
237}
238
239pub fn default_call_type() -> String {
241 "function".to_string()
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::HasConfig;
248 use crate::chat::{ChatMessage, ChatProvider, ChatResponse, StructuredOutputFormat, Tool};
249 use crate::completion::CompletionProvider;
250 use crate::embedding::EmbeddingProvider;
251 use crate::error::LLMError;
252 use async_trait::async_trait;
253 use serde_json::json;
254
255 #[test]
256 fn test_tool_call_creation() {
257 let tool_call = ToolCall {
258 id: "call_123".to_string(),
259 call_type: "function".to_string(),
260 function: FunctionCall {
261 name: "test_function".to_string(),
262 arguments: "{\"param\": \"value\"}".to_string(),
263 },
264 };
265
266 assert_eq!(tool_call.id, "call_123");
267 assert_eq!(tool_call.call_type, "function");
268 assert_eq!(tool_call.function.name, "test_function");
269 assert_eq!(tool_call.function.arguments, "{\"param\": \"value\"}");
270 }
271
272 #[test]
273 fn test_tool_call_serialization() {
274 let tool_call = ToolCall {
275 id: "call_456".to_string(),
276 call_type: "function".to_string(),
277 function: FunctionCall {
278 name: "serialize_test".to_string(),
279 arguments: "{\"test\": true}".to_string(),
280 },
281 };
282
283 let serialized = serde_json::to_string(&tool_call).unwrap();
284 let deserialized: ToolCall = serde_json::from_str(&serialized).unwrap();
285
286 assert_eq!(deserialized.id, "call_456");
287 assert_eq!(deserialized.call_type, "function");
288 assert_eq!(deserialized.function.name, "serialize_test");
289 assert_eq!(deserialized.function.arguments, "{\"test\": true}");
290 }
291
292 #[test]
293 fn test_tool_call_equality() {
294 let tool_call1 = ToolCall {
295 id: "call_1".to_string(),
296 call_type: "function".to_string(),
297 function: FunctionCall {
298 name: "equal_test".to_string(),
299 arguments: "{}".to_string(),
300 },
301 };
302
303 let tool_call2 = ToolCall {
304 id: "call_1".to_string(),
305 call_type: "function".to_string(),
306 function: FunctionCall {
307 name: "equal_test".to_string(),
308 arguments: "{}".to_string(),
309 },
310 };
311
312 let tool_call3 = ToolCall {
313 id: "call_2".to_string(),
314 call_type: "function".to_string(),
315 function: FunctionCall {
316 name: "equal_test".to_string(),
317 arguments: "{}".to_string(),
318 },
319 };
320
321 assert_eq!(tool_call1, tool_call2);
322 assert_ne!(tool_call1, tool_call3);
323 }
324
325 #[test]
326 fn test_tool_call_clone() {
327 let tool_call = ToolCall {
328 id: "clone_test".to_string(),
329 call_type: "function".to_string(),
330 function: FunctionCall {
331 name: "test_clone".to_string(),
332 arguments: "{\"clone\": true}".to_string(),
333 },
334 };
335
336 let cloned = tool_call.clone();
337 assert_eq!(tool_call, cloned);
338 assert_eq!(tool_call.id, cloned.id);
339 assert_eq!(tool_call.function.name, cloned.function.name);
340 }
341
342 #[test]
343 fn test_tool_call_debug() {
344 let tool_call = ToolCall {
345 id: "debug_test".to_string(),
346 call_type: "function".to_string(),
347 function: FunctionCall {
348 name: "debug_function".to_string(),
349 arguments: "{}".to_string(),
350 },
351 };
352
353 let debug_str = format!("{tool_call:?}");
354 assert!(debug_str.contains("ToolCall"));
355 assert!(debug_str.contains("debug_test"));
356 assert!(debug_str.contains("debug_function"));
357 }
358
359 #[test]
360 fn test_function_call_creation() {
361 let function_call = FunctionCall {
362 name: "test_function".to_string(),
363 arguments: "{\"param1\": \"value1\", \"param2\": 42}".to_string(),
364 };
365
366 assert_eq!(function_call.name, "test_function");
367 assert_eq!(
368 function_call.arguments,
369 "{\"param1\": \"value1\", \"param2\": 42}"
370 );
371 }
372
373 #[test]
374 fn test_function_call_serialization() {
375 let function_call = FunctionCall {
376 name: "serialize_function".to_string(),
377 arguments: "{\"data\": [1, 2, 3]}".to_string(),
378 };
379
380 let serialized = serde_json::to_string(&function_call).unwrap();
381 let deserialized: FunctionCall = serde_json::from_str(&serialized).unwrap();
382
383 assert_eq!(deserialized.name, "serialize_function");
384 assert_eq!(deserialized.arguments, "{\"data\": [1, 2, 3]}");
385 }
386
387 #[test]
388 fn test_function_call_equality() {
389 let func1 = FunctionCall {
390 name: "equal_func".to_string(),
391 arguments: "{}".to_string(),
392 };
393
394 let func2 = FunctionCall {
395 name: "equal_func".to_string(),
396 arguments: "{}".to_string(),
397 };
398
399 let func3 = FunctionCall {
400 name: "different_func".to_string(),
401 arguments: "{}".to_string(),
402 };
403
404 assert_eq!(func1, func2);
405 assert_ne!(func1, func3);
406 }
407
408 #[test]
409 fn test_function_call_clone() {
410 let function_call = FunctionCall {
411 name: "clone_func".to_string(),
412 arguments: "{\"clone\": \"test\"}".to_string(),
413 };
414
415 let cloned = function_call.clone();
416 assert_eq!(function_call, cloned);
417 assert_eq!(function_call.name, cloned.name);
418 assert_eq!(function_call.arguments, cloned.arguments);
419 }
420
421 #[test]
422 fn test_function_call_debug() {
423 let function_call = FunctionCall {
424 name: "debug_func".to_string(),
425 arguments: "{}".to_string(),
426 };
427
428 let debug_str = format!("{function_call:?}");
429 assert!(debug_str.contains("FunctionCall"));
430 assert!(debug_str.contains("debug_func"));
431 }
432
433 #[test]
434 fn test_tool_call_with_empty_values() {
435 let tool_call = ToolCall {
436 id: String::default(),
437 call_type: String::default(),
438 function: FunctionCall {
439 name: String::default(),
440 arguments: String::default(),
441 },
442 };
443
444 assert!(tool_call.id.is_empty());
445 assert!(tool_call.call_type.is_empty());
446 assert!(tool_call.function.name.is_empty());
447 assert!(tool_call.function.arguments.is_empty());
448 }
449
450 #[test]
451 fn test_tool_call_with_complex_arguments() {
452 let complex_args = json!({
453 "nested": {
454 "array": [1, 2, 3],
455 "object": {
456 "key": "value"
457 }
458 },
459 "simple": "string"
460 });
461
462 let tool_call = ToolCall {
463 id: "complex_call".to_string(),
464 call_type: "function".to_string(),
465 function: FunctionCall {
466 name: "complex_function".to_string(),
467 arguments: complex_args.to_string(),
468 },
469 };
470
471 let serialized = serde_json::to_string(&tool_call).unwrap();
472 let deserialized: ToolCall = serde_json::from_str(&serialized).unwrap();
473
474 assert_eq!(deserialized.id, "complex_call");
475 assert_eq!(deserialized.function.name, "complex_function");
476 assert!(deserialized.function.arguments.contains("nested"));
478 assert!(deserialized.function.arguments.contains("array"));
479 }
480
481 #[test]
482 fn test_tool_call_with_unicode() {
483 let tool_call = ToolCall {
484 id: "unicode_call".to_string(),
485 call_type: "function".to_string(),
486 function: FunctionCall {
487 name: "unicode_function".to_string(),
488 arguments: "{\"message\": \"Hello δΈη! π\"}".to_string(),
489 },
490 };
491
492 let serialized = serde_json::to_string(&tool_call).unwrap();
493 let deserialized: ToolCall = serde_json::from_str(&serialized).unwrap();
494
495 assert_eq!(deserialized.id, "unicode_call");
496 assert_eq!(deserialized.function.name, "unicode_function");
497 assert!(deserialized.function.arguments.contains("Hello δΈη! π"));
498 }
499
500 #[test]
501 fn test_tool_call_large_arguments() {
502 let large_arg = "x".repeat(10000);
503 let tool_call = ToolCall {
504 id: "large_call".to_string(),
505 call_type: "function".to_string(),
506 function: FunctionCall {
507 name: "large_function".to_string(),
508 arguments: format!("{{\"large_param\": \"{large_arg}\"}}"),
509 },
510 };
511
512 let serialized = serde_json::to_string(&tool_call).unwrap();
513 let deserialized: ToolCall = serde_json::from_str(&serialized).unwrap();
514
515 assert_eq!(deserialized.id, "large_call");
516 assert_eq!(deserialized.function.name, "large_function");
517 assert!(deserialized.function.arguments.len() > 10000);
518 }
519
520 struct MockLLMProvider;
522
523 #[async_trait]
524 impl chat::ChatProvider for MockLLMProvider {
525 async fn chat(
526 &self,
527 _messages: &[ChatMessage],
528 _json_schema: Option<StructuredOutputFormat>,
529 ) -> Result<Box<dyn ChatResponse>, LLMError> {
530 Ok(Box::new(MockChatResponse {
531 text: Some("Mock response".into()),
532 }))
533 }
534
535 async fn chat_with_tools(
536 &self,
537 _messages: &[ChatMessage],
538 _tools: Option<&[Tool]>,
539 _json_schema: Option<StructuredOutputFormat>,
540 ) -> Result<Box<dyn ChatResponse>, LLMError> {
541 Ok(Box::new(MockChatResponse {
542 text: Some("Mock response".into()),
543 }))
544 }
545 }
546
547 #[async_trait]
548 impl completion::CompletionProvider for MockLLMProvider {
549 async fn complete(
550 &self,
551 _req: &completion::CompletionRequest,
552 _json_schema: Option<chat::StructuredOutputFormat>,
553 ) -> Result<completion::CompletionResponse, error::LLMError> {
554 Ok(completion::CompletionResponse {
555 text: "Mock completion".to_string(),
556 })
557 }
558 }
559
560 #[async_trait]
561 impl embedding::EmbeddingProvider for MockLLMProvider {
562 async fn embed(&self, input: Vec<String>) -> Result<Vec<Vec<f32>>, error::LLMError> {
563 let mut embeddings = Vec::new();
564 for (i, _) in input.iter().enumerate() {
565 embeddings.push(vec![i as f32, (i + 1) as f32]);
566 }
567 Ok(embeddings)
568 }
569 }
570
571 #[async_trait]
572 impl models::ModelsProvider for MockLLMProvider {}
573
574 impl LLMProvider for MockLLMProvider {}
575
576 impl HasConfig for MockLLMProvider {
577 type Config = NoConfig;
578 }
579
580 struct MockChatResponse {
581 text: Option<String>,
582 }
583
584 impl chat::ChatResponse for MockChatResponse {
585 fn text(&self) -> Option<String> {
586 self.text.clone()
587 }
588
589 fn tool_calls(&self) -> Option<Vec<ToolCall>> {
590 None
591 }
592 }
593
594 impl std::fmt::Debug for MockChatResponse {
595 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596 write!(f, "MockChatResponse")
597 }
598 }
599
600 impl std::fmt::Display for MockChatResponse {
601 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602 write!(f, "{}", self.text.as_deref().unwrap_or(""))
603 }
604 }
605
606 #[tokio::test]
607 async fn test_llm_provider_trait_chat() {
608 let provider = MockLLMProvider;
609 let messages = vec![chat::ChatMessage::user().content("Test").build()];
610
611 let response = provider.chat(&messages, None).await.unwrap();
612 assert_eq!(response.text(), Some("Mock response".to_string()));
613 }
614
615 #[tokio::test]
616 async fn test_chat_and_sampling_default_impl_ignores_sampling() {
617 let provider = MockLLMProvider;
623 let messages = vec![chat::ChatMessage::user().content("Test").build()];
624
625 let baseline = provider.chat(&messages, None).await.unwrap();
627 let none_override = provider
628 .chat_and_sampling(&messages, None, None)
629 .await
630 .unwrap();
631 assert_eq!(baseline.text(), none_override.text());
632
633 let with_overrides = provider
635 .chat_and_sampling(
636 &messages,
637 None,
638 Some(&chat::SamplingOverrides {
639 temperature: Some(0.0),
640 top_p: Some(0.9),
641 max_tokens: Some(64),
642 }),
643 )
644 .await
645 .unwrap();
646 assert_eq!(with_overrides.text(), Some("Mock response".to_string()));
647 }
648
649 #[tokio::test]
650 async fn test_chat_with_tools_and_sampling_default_impl_ignores_sampling() {
651 let provider = MockLLMProvider;
652 let messages = vec![chat::ChatMessage::user().content("Test").build()];
653
654 let response = provider
655 .chat_with_tools_and_sampling(
656 &messages,
657 None,
658 None,
659 Some(&chat::SamplingOverrides::with_temperature(0.0)),
660 )
661 .await
662 .unwrap();
663 assert_eq!(response.text(), Some("Mock response".to_string()));
664 }
665
666 #[test]
667 fn test_sampling_overrides_helpers() {
668 let empty = chat::SamplingOverrides::empty();
669 assert_eq!(empty, chat::SamplingOverrides::default());
670 assert!(empty.temperature.is_none());
671 assert!(empty.top_p.is_none());
672 assert!(empty.max_tokens.is_none());
673
674 let temp = chat::SamplingOverrides::with_temperature(0.0);
675 assert_eq!(temp.temperature, Some(0.0));
676 assert_eq!(temp.top_p, None);
677
678 let top_p = chat::SamplingOverrides::with_top_p(0.95);
679 assert_eq!(top_p.top_p, Some(0.95));
680 assert_eq!(top_p.temperature, None);
681
682 let max_tok = chat::SamplingOverrides::with_max_tokens(128);
683 assert_eq!(max_tok.max_tokens, Some(128));
684
685 let from_root = SamplingOverrides::with_temperature(0.5);
687 assert_eq!(from_root.temperature, Some(0.5));
688 }
689
690 #[tokio::test]
691 async fn test_llm_provider_trait_completion() {
692 let provider = MockLLMProvider;
693 let request = completion::CompletionRequest::new("Test prompt");
694
695 let response = provider.complete(&request, None).await.unwrap();
696 assert_eq!(response.text, "Mock completion");
697 }
698
699 #[tokio::test]
700 async fn test_llm_provider_trait_embedding() {
701 let provider = MockLLMProvider;
702 let input = vec!["First".to_string(), "Second".to_string()];
703
704 let embeddings = provider.embed(input).await.unwrap();
705 assert_eq!(embeddings.len(), 2);
706 assert_eq!(embeddings[0], vec![0.0, 1.0]);
707 assert_eq!(embeddings[1], vec![1.0, 2.0]);
708 }
709}