llm_toolkit/lib.rs
1//! 'llm-toolkit' - A low-level Rust toolkit for the LLM last mile problem.
2//!
3//! This library provides a set of sharp, reliable, and unopinionated "tools"
4//! for building robust LLM-powered applications in Rust. It focuses on solving
5//! the common and frustrating problems that occur at the boundary between a
6//! strongly-typed Rust application and the unstructured, often unpredictable
7//! string-based responses from LLM APIs.
8
9// Allow the crate to reference itself by name, which is needed for proc macros
10// to work correctly in examples, tests, and bins
11extern crate self as llm_toolkit;
12
13/// A derive macro to implement the `ToPrompt` trait for structs.
14///
15/// This macro is available only when the `derive` feature is enabled.
16/// See the [crate-level documentation](index.html#2-structured-prompts-with-derivetoprompt) for usage examples.
17#[cfg(feature = "derive")]
18pub use llm_toolkit_macros::ToPrompt;
19
20/// A derive macro to implement the `ToPromptSet` trait for structs.
21///
22/// This macro is available only when the `derive` feature is enabled.
23#[cfg(feature = "derive")]
24pub use llm_toolkit_macros::ToPromptSet;
25
26/// A derive macro to implement the `ToPromptFor` trait for structs.
27///
28/// This macro is available only when the `derive` feature is enabled.
29#[cfg(feature = "derive")]
30pub use llm_toolkit_macros::ToPromptFor;
31
32/// A macro for creating examples sections in prompts.
33///
34/// This macro is available only when the `derive` feature is enabled.
35#[cfg(feature = "derive")]
36pub use llm_toolkit_macros::examples_section;
37
38/// A procedural attribute macro for defining intent enums with automatic prompt and extractor generation.
39///
40/// This macro is available only when the `derive` feature is enabled.
41#[cfg(feature = "derive")]
42pub use llm_toolkit_macros::define_intent;
43
44/// A derive macro to implement the `Agent` trait for structs.
45///
46/// This macro is available only when the `agent` feature is enabled.
47/// It automatically generates an Agent implementation that uses ClaudeCodeAgent
48/// internally and deserializes responses into a structured output type.
49///
50/// # Example
51///
52/// ```ignore
53/// use llm_toolkit_macros::Agent;
54/// use serde::{Deserialize, Serialize};
55///
56/// #[derive(Serialize, Deserialize)]
57/// struct MyOutput {
58/// result: String,
59/// }
60///
61/// #[derive(Agent)]
62/// #[agent(expertise = "My expertise", output = "MyOutput")]
63/// struct MyAgent;
64/// ```
65#[cfg(feature = "agent")]
66pub use llm_toolkit_macros::Agent;
67
68/// An attribute macro to define agent structs with automatic trait implementations.
69///
70/// This macro is available only when the `agent` feature is enabled.
71#[cfg(feature = "agent")]
72pub use llm_toolkit_macros::agent;
73
74/// A derive macro to implement the `TypeMarker` trait for structs.
75///
76/// This macro is available only when the `agent` feature is enabled.
77/// It automatically generates a TypeMarker implementation that provides
78/// a type identifier string for type-based orchestrator output retrieval.
79///
80/// # Example
81///
82/// ```ignore
83/// use llm_toolkit::TypeMarker;
84/// use serde::{Deserialize, Serialize};
85///
86/// #[derive(Serialize, Deserialize, TypeMarker)]
87/// struct MyResponse {
88/// #[serde(default = "default_type")]
89/// __type: String,
90/// result: String,
91/// }
92///
93/// fn default_type() -> String {
94/// "MyResponse".to_string()
95/// }
96/// ```
97#[cfg(feature = "agent")]
98pub use llm_toolkit_macros::{TypeMarker, type_marker};
99
100pub mod extract;
101pub mod intent;
102pub mod multimodal;
103pub mod prompt;
104
105#[cfg(feature = "agent")]
106pub mod agent;
107
108#[cfg(feature = "agent")]
109pub mod orchestrator;
110
111pub use extract::{FlexibleExtractor, MarkdownCodeBlockExtractor};
112pub use intent::frame::IntentFrame;
113#[allow(deprecated)]
114pub use intent::{IntentError, IntentExtractor, PromptBasedExtractor};
115pub use multimodal::ImageData;
116pub use prompt::{PromptPart, PromptSetError, ToPrompt, ToPromptFor, ToPromptSet};
117
118#[cfg(feature = "agent")]
119pub use agent::{Agent, AgentError};
120
121#[cfg(feature = "agent")]
122pub use orchestrator::{
123 BlueprintWorkflow, Orchestrator, OrchestratorError, StrategyMap, TypeMarker,
124};
125
126use extract::ParseError;
127
128/// Extracts a JSON string from a raw LLM response string.
129///
130/// This function uses a `FlexibleExtractor` with its standard strategies
131/// to find and extract a JSON object from a string that may contain extraneous
132/// text, such as explanations or Markdown code blocks.
133///
134/// For more advanced control over extraction strategies, see the `extract::FlexibleExtractor` struct.
135///
136/// # Returns
137///
138/// A `Result` containing the extracted JSON `String` on success, or a `ParseError`
139/// if no JSON could be extracted.
140pub fn extract_json(text: &str) -> Result<String, ParseError> {
141 let extractor = FlexibleExtractor::new();
142 // Note: The standard strategies in the copied code are TaggedContent("answer"), JsonBrackets, FirstJsonObject.
143 // We will add a markdown strategy later during refactoring.
144 extractor.extract(text)
145}
146
147/// Extracts content from any Markdown code block in the text.
148///
149/// This function searches for the first code block (delimited by triple backticks)
150/// and returns its content. The code block can have any language specifier or none at all.
151///
152/// # Returns
153///
154/// A `Result` containing the extracted code block content on success, or a `ParseError`
155/// if no code block is found.
156pub fn extract_markdown_block(text: &str) -> Result<String, ParseError> {
157 let extractor = MarkdownCodeBlockExtractor::new();
158 extractor.extract(text)
159}
160
161/// Extracts content from a Markdown code block with a specific language.
162///
163/// This function searches for a code block with the specified language hint
164/// (e.g., ```rust, ```python) and returns its content.
165///
166/// # Arguments
167///
168/// * `text` - The text containing the markdown code block
169/// * `lang` - The language specifier to match (e.g., "rust", "python")
170///
171/// # Returns
172///
173/// A `Result` containing the extracted code block content on success, or a `ParseError`
174/// if no code block with the specified language is found.
175pub fn extract_markdown_block_with_lang(text: &str, lang: &str) -> Result<String, ParseError> {
176 let extractor = MarkdownCodeBlockExtractor::with_language(lang.to_string());
177 extractor.extract(text)
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_json_extraction() {
186 let input = "Some text before {\"key\": \"value\"} and after.";
187 assert_eq!(extract_json(input).unwrap(), "{\"key\": \"value\"}");
188 }
189
190 #[test]
191 fn test_standard_extraction_from_tagged_content() {
192 let text = "<answer>{\"type\": \"success\"}</answer>";
193 let result = extract_json(text);
194 assert!(result.is_ok());
195 assert_eq!(result.unwrap(), "{\"type\": \"success\"}");
196 }
197
198 #[test]
199 fn test_markdown_extraction() {
200 // Test simple code block with no language
201 let text1 = "Here is some code:\n```\nlet x = 42;\n```\nAnd some text after.";
202 let result1 = extract_markdown_block(text1);
203 assert!(result1.is_ok());
204 assert_eq!(result1.unwrap(), "let x = 42;");
205
206 // Test code block with specific language (rust)
207 let text2 = "Here's Rust code:\n```rust\nfn main() {
208 println!(\"Hello\");
209}
210```";
211 let result2 = extract_markdown_block_with_lang(text2, "rust");
212 assert!(result2.is_ok());
213 assert_eq!(result2.unwrap(), "fn main() {\n println!(\"Hello\");\n}");
214
215 // Test extracting rust block when json block is also present
216 let text3 = r#"\nFirst a JSON block:
217```json
218{"key": "value"}
219```
220
221Then a Rust block:
222```rust
223let data = vec![1, 2, 3];
224```
225"#;
226 let result3 = extract_markdown_block_with_lang(text3, "rust");
227 assert!(result3.is_ok());
228 assert_eq!(result3.unwrap(), "let data = vec![1, 2, 3];");
229
230 // Test case where no code block is found
231 let text4 = "This text has no code blocks at all.";
232 let result4 = extract_markdown_block(text4);
233 assert!(result4.is_err());
234
235 // Test with messy surrounding text and newlines
236 let text5 = r#"\nLots of text before...
237
238
239 ```python
240def hello():
241 print("world")
242 return True
243 ```
244
245
246And more text after with various spacing.
247"#;
248 let result5 = extract_markdown_block_with_lang(text5, "python");
249 assert!(result5.is_ok());
250 assert_eq!(
251 result5.unwrap(),
252 "def hello():\n print(\"world\")\n return True"
253 );
254 }
255}