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