1pub mod assistant;
79pub mod event;
81pub mod failure;
83pub mod message;
85pub mod model;
87pub mod provider;
89pub mod reasoning;
91pub mod researcher;
93pub mod retry;
95pub mod tool;
97
98use crate::llm::{model::Parameters, tool::Tools};
99use alloc::{
100 boxed::Box,
101 string::{String, ToString},
102 sync::Arc,
103 vec,
104 vec::Vec,
105};
106use core::{any::TypeId, future::Future};
107pub use event::{Event, ToolCall, Usage};
108pub use failure::{Classify, Failure, FailureKind};
109use futures_core::Stream;
110use futures_lite::{StreamExt, pin};
111pub use message::{Attachment, Message, Role};
112pub use provider::LanguageModelProvider;
113pub use reasoning::ReasoningState;
114pub use researcher::{
115 ResearchCitation, ResearchEvent, ResearchFinding, ResearchOptions, ResearchReport,
116 ResearchRequest, ResearchSource, ResearchStage, Researcher, ResearcherProfile,
117};
118pub use retry::RetryPolicy;
119use schemars::{JsonSchema, schema_for};
120use serde::de::DeserializeOwned;
121pub use tool::{
122 AgentIdentity, CallIdentity, CommandOutcome, CommandResult, CommandStream, Interruption,
123 InterruptionCause, IntoToolResult, StoredFile, SubagentIdentity, Tool, ToolCallId, ToolContext,
124 ToolResult, render_interruptions,
125};
126
127use crate::llm::model::Profile;
128
129#[derive(Debug)]
138pub enum GenerateError<E> {
139 Provider(E),
141
142 Parse {
146 source: serde_json::Error,
148 response: String,
150 },
151}
152
153impl<E: core::fmt::Display> core::fmt::Display for GenerateError<E> {
154 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
155 match self {
156 Self::Provider(err) => write!(f, "language model request failed: {err}"),
157 Self::Parse { source, response } => {
158 write!(
159 f,
160 "structured output did not match the requested schema: {source}; response: {response}"
161 )
162 }
163 }
164 }
165}
166
167impl<E: core::error::Error + 'static> core::error::Error for GenerateError<E> {
168 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
169 match self {
170 Self::Provider(err) => Some(err),
171 Self::Parse { source, .. } => Some(source),
172 }
173 }
174}
175
176#[derive(Debug, Clone)]
181pub struct LLMRequest {
182 messages: Vec<Message>,
183 parameters: Parameters,
184 tool_definitions: Vec<tool::ToolDefinition>,
185}
186
187impl LLMRequest {
188 pub fn new(messages: impl Into<Vec<Message>>) -> Self {
190 Self {
191 messages: messages.into(),
192 parameters: Parameters::default(),
193 tool_definitions: Vec::new(),
194 }
195 }
196
197 #[must_use]
202 pub fn with_tool_definitions(mut self, definitions: Vec<tool::ToolDefinition>) -> Self {
203 self.tool_definitions = definitions;
204 self
205 }
206
207 #[must_use]
209 pub fn with_tool<T: Tool>(mut self, tool: &T) -> Self {
210 self.tool_definitions.push(tool::ToolDefinition::new(tool));
211 self
212 }
213
214 #[must_use]
216 pub fn with_parameters(mut self, parameters: Parameters) -> Self {
217 self.parameters = parameters;
218 self
219 }
220
221 #[must_use]
223 pub fn messages(&self) -> &[Message] {
224 &self.messages
225 }
226
227 pub const fn messages_mut(&mut self) -> &mut Vec<Message> {
229 &mut self.messages
230 }
231
232 #[must_use]
234 pub const fn parameters(&self) -> &Parameters {
235 &self.parameters
236 }
237
238 #[must_use]
240 pub fn tool_definitions(&self) -> &[tool::ToolDefinition] {
241 &self.tool_definitions
242 }
243
244 #[must_use]
246 pub fn into_parts(self) -> (Vec<Message>, Parameters, Vec<tool::ToolDefinition>) {
247 (self.messages, self.parameters, self.tool_definitions)
248 }
249}
250
251#[derive(Debug)]
256pub struct LLMRequestWithTools<'tools> {
257 inner: LLMRequest,
258 tools: &'tools mut Tools,
259}
260
261impl LLMRequest {
262 pub fn with_tools(self, tools: &mut Tools) -> LLMRequestWithTools<'_> {
267 let definitions = tools.definitions();
268 LLMRequestWithTools {
269 inner: self.with_tool_definitions(definitions),
270 tools,
271 }
272 }
273}
274
275impl<'tools> LLMRequestWithTools<'tools> {
276 #[must_use]
278 pub const fn request(&self) -> &LLMRequest {
279 &self.inner
280 }
281
282 pub const fn request_mut(&mut self) -> &mut LLMRequest {
284 &mut self.inner
285 }
286
287 #[must_use]
289 pub const fn tools(&mut self) -> &mut Tools {
290 self.tools
291 }
292
293 #[must_use]
295 pub fn into_parts(self) -> (LLMRequest, &'tools mut Tools) {
296 (self.inner, self.tools)
297 }
298
299 pub async fn call_tool(
305 &mut self,
306 name: &str,
307 args_json: &str,
308 cx: ToolContext,
309 ) -> crate::Result<ToolResult> {
310 self.tools.call(name, args_json, cx).await
311 }
312}
313
314#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
321pub enum TokenCounting {
322 #[default]
324 None,
325 Local,
327 Endpoint,
329}
330
331pub trait LanguageModel: Sized + Send + Sync {
339 type Error: core::error::Error + Classify + Send + Sync + 'static;
341
342 fn token_counting(&self) -> TokenCounting {
344 TokenCounting::None
345 }
346
347 fn count_tokens(
351 &self,
352 request: &LLMRequest,
353 ) -> impl Future<Output = Result<Option<u32>, GenerateError<Self::Error>>> + Send {
354 let _ = request;
355 async { Ok(None) }
356 }
357
358 fn count_text(&self, text: &str) -> Option<u32> {
362 let _ = text;
363 None
364 }
365
366 fn respond(&self, request: LLMRequest)
374 -> impl Stream<Item = Result<Event, Self::Error>> + Send;
375
376 fn respond_with_tools(
381 &self,
382 request: LLMRequestWithTools<'_>,
383 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
384 let (inner, _tools) = request.into_parts();
385 self.respond(inner)
386 }
387
388 fn generate<T: JsonSchema + DeserializeOwned + 'static>(
395 &self,
396 request: LLMRequest,
397 ) -> impl Future<Output = Result<T, GenerateError<Self::Error>>> + Send {
398 async { structured_generate(self, request).await }
399 }
400
401 fn complete(&self, prefix: &str) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
403 self.respond(oneshot("Please complete the following text:", prefix))
404 }
405
406 fn summarize(&self, text: &str) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
412 summarize(self, text)
413 }
414
415 fn categorize<T: JsonSchema + DeserializeOwned + 'static>(
421 &self,
422 text: &str,
423 ) -> impl Future<Output = Result<T, GenerateError<Self::Error>>> + Send {
424 async { categorize_text(self, text).await }
425 }
426
427 fn profile(&self) -> impl Future<Output = Result<Profile, Self::Error>> + Send;
437
438 fn retry_policy(&self) -> RetryPolicy {
446 RetryPolicy::default()
447 }
448}
449
450macro_rules! impl_language_model {
451 ($($name:ident),*) => {
452 $(
453 impl<T: LanguageModel> LanguageModel for $name<T> {
454 type Error = T::Error;
455
456 fn respond(
457 &self,
458 request: LLMRequest,
459 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
460 T::respond(self, request)
461 }
462
463 fn respond_with_tools(
464 &self,
465 request: LLMRequestWithTools<'_>,
466 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
467 T::respond_with_tools(self, request)
468 }
469
470 fn generate<U: JsonSchema + DeserializeOwned + 'static>(
471 &self,
472 request: LLMRequest,
473 ) -> impl Future<Output = Result<U, GenerateError<Self::Error>>> + Send {
474 T::generate(self, request)
475 }
476
477 fn complete(
478 &self,
479 prefix: &str,
480 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
481 T::complete(self, prefix)
482 }
483
484 fn summarize(
485 &self,
486 text: &str,
487 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
488 T::summarize(self, text)
489 }
490
491 fn categorize<U: JsonSchema + DeserializeOwned + 'static>(
492 &self,
493 text: &str,
494 ) -> impl Future<Output = Result<U, GenerateError<Self::Error>>> + Send {
495 T::categorize(self, text)
496 }
497
498 fn profile(&self) -> impl Future<Output = Result<Profile, Self::Error>> + Send {
499 T::profile(self)
500 }
501
502 fn retry_policy(&self) -> RetryPolicy {
503 T::retry_policy(self)
504 }
505
506 fn token_counting(&self) -> TokenCounting {
507 T::token_counting(self)
508 }
509
510 fn count_tokens(
511 &self,
512 request: &LLMRequest,
513 ) -> impl Future<Output = Result<Option<u32>, GenerateError<Self::Error>>>
514 + Send {
515 T::count_tokens(self, request)
516 }
517
518 fn count_text(&self, text: &str) -> Option<u32> {
519 T::count_text(self, text)
520 }
521 }
522 )*
523 };
524}
525
526impl<T: LanguageModel> LanguageModel for &T {
527 type Error = T::Error;
528
529 fn respond(
530 &self,
531 request: LLMRequest,
532 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
533 T::respond(self, request)
534 }
535
536 fn respond_with_tools(
537 &self,
538 request: LLMRequestWithTools<'_>,
539 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
540 T::respond_with_tools(self, request)
541 }
542
543 fn generate<U: JsonSchema + DeserializeOwned + 'static>(
544 &self,
545 request: LLMRequest,
546 ) -> impl Future<Output = Result<U, GenerateError<Self::Error>>> + Send {
547 T::generate(self, request)
548 }
549
550 fn complete(&self, prefix: &str) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
551 T::complete(self, prefix)
552 }
553
554 fn summarize(&self, text: &str) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
555 T::summarize(self, text)
556 }
557
558 fn categorize<U: JsonSchema + DeserializeOwned + 'static>(
559 &self,
560 text: &str,
561 ) -> impl Future<Output = Result<U, GenerateError<Self::Error>>> + Send {
562 T::categorize(self, text)
563 }
564
565 fn profile(&self) -> impl Future<Output = Result<Profile, Self::Error>> + Send {
566 T::profile(self)
567 }
568
569 fn retry_policy(&self) -> RetryPolicy {
570 T::retry_policy(self)
571 }
572
573 fn token_counting(&self) -> TokenCounting {
574 T::token_counting(self)
575 }
576
577 fn count_tokens(
578 &self,
579 request: &LLMRequest,
580 ) -> impl Future<Output = Result<Option<u32>, GenerateError<Self::Error>>> + Send {
581 T::count_tokens(self, request)
582 }
583
584 fn count_text(&self, text: &str) -> Option<u32> {
585 T::count_text(self, text)
586 }
587}
588
589mod prompts;
590
591impl_language_model!(Arc, Box);
592
593pub async fn collect_text<S, E>(stream: S) -> Result<String, E>
599where
600 S: Stream<Item = Result<Event, E>>,
601{
602 pin!(stream);
603 let mut result = String::new();
604 while let Some(event) = stream.next().await {
605 if let Event::Text(text) = event? {
606 result.push_str(&text);
607 }
608 }
609 Ok(result)
610}
611
612async fn structured_generate<T: JsonSchema + DeserializeOwned + 'static, M: LanguageModel>(
613 model: &M,
614 mut request: LLMRequest,
615) -> Result<T, GenerateError<M::Error>> {
616 let schema = schema_for!(T);
617
618 let json = if schema.as_value().is_string() {
620 let stream = model.respond(request);
621 let response = collect_text(stream)
622 .await
623 .map_err(GenerateError::Provider)?;
624 serde_json::to_string(&response).map_err(|source| GenerateError::Parse {
626 source,
627 response: response.clone(),
628 })?
629 } else {
630 let schema =
633 serde_json::to_string_pretty(&schema).map_err(|source| GenerateError::Parse {
634 source,
635 response: String::new(),
636 })?;
637 let prompt = prompts::generate(&schema);
638 request.messages.push(Message::system(prompt));
639 request.parameters.structured_outputs = true;
640
641 let stream = model.respond(request);
642 collect_text(stream)
643 .await
644 .map_err(GenerateError::Provider)?
645 };
646
647 parse_json_with_recovery(&json).map_err(|source| GenerateError::Parse {
648 source,
649 response: truncate_for_error(&json),
650 })
651}
652
653fn truncate_for_error(response: &str) -> String {
655 const LIMIT: usize = 500;
656 response.chars().take(LIMIT).collect()
657}
658
659pub fn oneshot(system: impl Into<String>, user: impl Into<String>) -> LLMRequest {
661 let messages = vec![Message::system(system.into()), Message::user(user.into())];
662 LLMRequest::new(messages)
663}
664
665fn summarize<M: LanguageModel>(
666 model: &M,
667 text: &str,
668) -> impl Stream<Item = Result<Event, M::Error>> + Send {
669 let messages = oneshot("Summarize text:", text);
670 model.respond(messages)
671}
672
673async fn categorize_text<T: JsonSchema + DeserializeOwned + 'static, M: LanguageModel>(
674 model: &M,
675 text: &str,
676) -> Result<T, GenerateError<M::Error>> {
677 let request = oneshot("Categorize text by provided schema", text);
678 model.generate(request).await
679}
680
681fn parse_json_with_recovery<T: DeserializeOwned + 'static>(
682 json: &str,
683) -> Result<T, serde_json::Error> {
684 use serde::de::Error as _;
685
686 let trimmed = json.trim();
687 let mut last_error: Option<serde_json::Error> = None;
688 let mut last_candidate: Option<String> = None;
689
690 for candidate in build_json_candidates(trimmed) {
691 match serde_json::from_str::<T>(&candidate) {
692 Ok(value) => return Ok(value),
693 Err(err) => {
694 last_error = Some(err);
695 last_candidate = Some(candidate);
696 }
697 }
698 }
699
700 if is_string_type::<T>()
703 && let Some(candidate) = last_candidate
704 && let Ok(value) = serde_json::from_str::<serde_json::Value>(&candidate)
705 {
706 let text = match value {
707 serde_json::Value::String(s) => s,
708 other => other.to_string(),
709 };
710 let encoded = serde_json::to_string(&text)?;
711 if let Ok(value) = serde_json::from_str::<T>(&encoded) {
712 return Ok(value);
713 }
714 }
715
716 Err(last_error.unwrap_or_else(|| {
717 serde_json::Error::custom("structured output was empty or missing a JSON block")
718 }))
719}
720
721fn strip_code_fences(raw: &str) -> Option<String> {
722 let trimmed = raw.trim();
723 let fence_start = trimmed.find("```")?;
724 let after_fence = &trimmed[fence_start + 3..];
725 let mut lines = after_fence.lines();
726 let _maybe_lang = lines.next();
727 let body = lines.collect::<Vec<_>>().join("\n");
728 let content = body.rfind("```").map_or(body.as_str(), |end| &body[..end]);
729
730 let cleaned = content.trim();
731 if cleaned.is_empty() {
732 None
733 } else {
734 Some(cleaned.to_string())
735 }
736}
737
738fn extract_json_block(raw: &str) -> Option<String> {
739 if let (Some(start), Some(end)) = (raw.find('{'), raw.rfind('}'))
740 && end >= start
741 {
742 let candidate = &raw[start..=end];
743 if !candidate.trim().is_empty() {
744 return Some(candidate.trim().to_string());
745 }
746 }
747 if let (Some(start), Some(end)) = (raw.find('['), raw.rfind(']'))
748 && end >= start
749 {
750 let candidate = &raw[start..=end];
751 if !candidate.trim().is_empty() {
752 return Some(candidate.trim().to_string());
753 }
754 }
755 None
756}
757
758fn build_json_candidates(raw: &str) -> Vec<String> {
759 let mut candidates = Vec::new();
760
761 if !raw.is_empty() {
762 candidates.push(raw.to_string());
763 }
764
765 if let Some(fenced) = strip_code_fences(raw) {
766 candidates.push(fenced);
767 }
768
769 if let Some(block) = extract_json_block(raw) {
770 candidates.push(block);
771 }
772
773 if let Some(dequoted) = dequote_json_string(raw) {
774 candidates.push(dequoted);
775 }
776
777 if let Some(stripped) = strip_leading_label(raw, "json") {
778 candidates.push(stripped);
779 }
780
781 let mut deduped = Vec::new();
782 for candidate in candidates {
783 if deduped.iter().all(|seen| seen != &candidate) {
784 deduped.push(candidate);
785 }
786 }
787 deduped
788}
789
790fn dequote_json_string(raw: &str) -> Option<String> {
791 let trimmed = raw.trim();
792 if !(trimmed.starts_with('"') && trimmed.ends_with('"')) {
793 return None;
794 }
795 let inner: String = serde_json::from_str(trimmed).ok()?;
796 if inner.trim().is_empty() {
797 None
798 } else {
799 Some(inner)
800 }
801}
802
803fn strip_leading_label(raw: &str, label: &str) -> Option<String> {
804 let trimmed = raw.trim_start();
805 if !trimmed.to_ascii_lowercase().starts_with(label) {
806 return None;
807 }
808 let stripped = trimmed[label.len()..]
809 .trim_start_matches(|c: char| c.is_whitespace() || c == ':' || c == '-')
810 .trim();
811 if stripped.is_empty() {
812 None
813 } else {
814 Some(stripped.to_string())
815 }
816}
817
818fn is_string_type<T: 'static>() -> bool {
819 TypeId::of::<T>() == TypeId::of::<String>()
820}
821
822#[cfg(test)]
823mod tests {
824 use super::parse_json_with_recovery;
825 use alloc::string::String;
826 use serde::Deserialize;
827
828 #[derive(Debug, Deserialize, PartialEq, Eq)]
829 struct Foo {
830 a: u8,
831 }
832
833 #[test]
834 fn parses_plain_json() {
835 let foo: Foo = parse_json_with_recovery(r#"{"a":1}"#).unwrap();
836 assert_eq!(foo, Foo { a: 1 });
837 }
838
839 #[test]
840 fn parses_code_fence_json() {
841 let foo: Foo = parse_json_with_recovery("```json\n{\"a\":2}\n```").unwrap();
842 assert_eq!(foo, Foo { a: 2 });
843 }
844
845 #[test]
846 fn parses_embedded_block() {
847 let foo: Foo = parse_json_with_recovery("noise {\"a\":3} trailing").unwrap();
848 assert_eq!(foo, Foo { a: 3 });
849 }
850
851 #[test]
852 fn parses_quoted_json_string() {
853 let foo: Foo = parse_json_with_recovery(r#""{\"a\":4}""#).unwrap();
854 assert_eq!(foo, Foo { a: 4 });
855 }
856
857 #[test]
858 fn parses_labeled_json() {
859 let foo: Foo = parse_json_with_recovery("json {\"a\":5}").unwrap();
860 assert_eq!(foo, Foo { a: 5 });
861 }
862
863 #[test]
864 fn coerces_object_to_string() {
865 let value: String =
866 parse_json_with_recovery(r#"{"title":"summary","type":"content"}"#).unwrap();
867 assert!(
868 value.contains("\"title\":\"summary\"") && value.contains("\"type\":\"content\""),
869 "unexpected value: {value}"
870 );
871 }
872}