1#[cfg(feature = "agent")]
4pub mod expandable;
5pub mod frame;
6
7use self::frame::IntentFrame;
8use std::str::FromStr;
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13pub enum IntentError {
14 #[error("Extraction failed: Tag '{tag}' not found in response")]
15 TagNotFound { tag: String },
16
17 #[error("Parsing failed: Could not parse '{value}' into a valid intent")]
18 ParseFailed { value: String },
19
20 #[error(transparent)]
21 Other(#[from] anyhow::Error),
22}
23
24pub type IntentExtractionError = IntentError;
26
27#[derive(Debug, Error)]
29pub enum ParseError {
30 #[error("Failed to parse intent: {0}")]
31 ParseError(String),
32}
33
34pub fn extract_intent_from_response<T>(
37 response: &str,
38 tag: &str,
39) -> Result<T, IntentExtractionError>
40where
41 T: FromStr,
42 T::Err: std::fmt::Display,
43{
44 use crate::extract::FlexibleExtractor;
45 use crate::extract::core::ContentExtractor;
46
47 let extractor = FlexibleExtractor::new();
48 let extracted_str =
49 extractor
50 .extract_tagged(response, tag)
51 .ok_or_else(|| IntentError::TagNotFound {
52 tag: tag.to_string(),
53 })?;
54
55 T::from_str(&extracted_str).map_err(|e| IntentError::ParseFailed {
56 value: format!("{}: {}", extracted_str, e),
57 })
58}
59
60pub trait IntentExtractor<T>
64where
65 T: FromStr,
66{
67 fn extract_intent(&self, text: &str) -> Result<T, IntentError>;
69}
70
71#[deprecated(
77 since = "0.8.0",
78 note = "Please use `IntentFrame` instead for better safety and clarity."
79)]
80pub struct PromptBasedExtractor {
81 frame: IntentFrame,
82}
83
84#[allow(deprecated)]
85impl PromptBasedExtractor {
86 pub fn new(tag: &str) -> Self {
88 Self {
89 frame: IntentFrame::new(tag, tag),
90 }
91 }
92}
93
94#[allow(deprecated)]
95impl<T> IntentExtractor<T> for PromptBasedExtractor
96where
97 T: FromStr,
98{
99 fn extract_intent(&self, text: &str) -> Result<T, IntentError> {
100 self.frame.extract_intent(text)
101 }
102}
103
104#[cfg(test)]
105#[allow(deprecated)]
106mod tests {
107 use super::*;
108
109 #[derive(Debug, PartialEq)]
110 enum TestIntent {
111 Login,
112 Logout,
113 }
114
115 impl FromStr for TestIntent {
116 type Err = String;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 match s {
120 "Login" => Ok(TestIntent::Login),
121 "Logout" => Ok(TestIntent::Logout),
122 _ => Err(format!("Unknown intent: {}", s)),
123 }
124 }
125 }
126
127 #[test]
128 fn test_extract_intent_success() {
129 let extractor = PromptBasedExtractor::new("intent");
130 let text = "<intent>Login</intent>";
131 let result: Result<TestIntent, _> = IntentExtractor::extract_intent(&extractor, text);
132 assert_eq!(result.unwrap(), TestIntent::Login);
133 }
134
135 #[test]
136 fn test_extract_intent_tag_not_found() {
137 let extractor = PromptBasedExtractor::new("intent");
138 let text = "No intent tag here";
139 let result: Result<TestIntent, _> = IntentExtractor::extract_intent(&extractor, text);
140
141 match result {
142 Err(IntentError::TagNotFound { tag }) => {
143 assert_eq!(tag, "intent");
144 }
145 _ => panic!("Expected TagNotFound error"),
146 }
147 }
148
149 #[test]
150 fn test_extract_intent_parse_failed() {
151 let extractor = PromptBasedExtractor::new("intent");
152 let text = "<intent>Invalid</intent>";
153 let result: Result<TestIntent, _> = IntentExtractor::extract_intent(&extractor, text);
154
155 match result {
156 Err(IntentError::ParseFailed { value }) => {
157 assert_eq!(value, "Invalid");
158 }
159 _ => panic!("Expected ParseFailed error"),
160 }
161 }
162}