talk 0.1.1

A Rust library for creating controlled LLM agents with behavioral guidelines, tool integration, and multi-step conversation journeys
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
//! Error types for the Talk library
//!
//! This module provides comprehensive error types using thiserror for all Talk operations.

use crate::types::{GuidelineId, JourneyId, SessionId, StepId, ToolId};
use thiserror::Error;

/// Main error type for Talk library operations
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum AgentError {
    /// LLM provider error
    #[error("LLM provider error: {0}")]
    LLMProvider(#[from] Box<dyn std::error::Error + Send + Sync>),

    /// Provider error (for specific provider failures)
    #[error("Provider error: {0}")]
    ProviderError(String),

    /// Session not found
    #[error("Session not found: {0}")]
    SessionNotFound(SessionId),

    /// Session already exists
    #[error("Session already exists: {0}")]
    SessionAlreadyExists(SessionId),

    /// Guideline matching failed
    #[error("Guideline matching failed: {0}")]
    GuidelineMatch(String),

    /// Guideline not found
    #[error("Guideline not found: {0}")]
    GuidelineNotFound(GuidelineId),

    /// Tool execution error
    #[error("Tool execution error: {0}")]
    ToolExecution(#[from] ToolError),

    /// Journey execution error
    #[error("Journey execution error: {0}")]
    JourneyExecution(#[from] JourneyError),

    /// Journey error (simple string message)
    #[error("Journey error: {0}")]
    Journey(String),

    /// Storage error
    #[error("Storage error: {0}")]
    Storage(#[from] StorageError),

    /// Serialization error
    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    /// Configuration error
    #[error("Configuration error: {0}")]
    Configuration(String),

    /// Invalid input
    #[error("Invalid input: {0}")]
    InvalidInput(String),

    /// Tool not found
    #[error("Tool not found: {0}")]
    ToolNotFound(ToolId),

    /// Tool already registered with same name
    #[error("Tool already registered: {0}")]
    ToolAlreadyRegistered(String),

    /// Tool execution failed
    #[error("Tool execution failed for {tool_name}: {reason}")]
    ToolExecutionFailed { tool_name: String, reason: String },

    /// Invalid tool parameters
    #[error("Invalid tool parameters for {tool_name}: {reason}")]
    InvalidToolParameters { tool_name: String, reason: String },

    /// Tool execution timeout
    #[error("Tool execution timeout for {tool_name} after {timeout:?}")]
    ToolTimeout {
        tool_name: String,
        timeout: std::time::Duration,
    },

    /// Internal error (should not happen in normal operation)
    #[error("Internal error: {0}")]
    Internal(String),
}

/// Storage-related errors
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum StorageError {
    /// Connection failed
    #[error("Storage connection failed: {0}")]
    Connection(String),

    /// Query failed
    #[error("Storage query failed: {0}")]
    Query(String),

    /// Serialization failed
    #[error("Storage serialization failed: {0}")]
    Serialization(String),

    /// Deserialization failed
    #[error("Storage deserialization failed: {0}")]
    Deserialization(String),

    /// Resource not found
    #[error("Resource not found: {0}")]
    NotFound(String),

    /// Resource already exists
    #[error("Resource already exists: {0}")]
    AlreadyExists(String),

    /// Storage backend not available
    #[error("Storage backend not available: {0}")]
    BackendUnavailable(String),

    /// Internal storage error
    #[error("Internal storage error: {0}")]
    Internal(String),
}

/// Guideline-related errors
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum GuidelineError {
    /// Invalid guideline condition
    #[error("Invalid guideline condition: {0}")]
    InvalidCondition(String),

    /// Invalid guideline action
    #[error("Invalid guideline action: {0}")]
    InvalidAction(String),

    /// Guideline compilation failed (for regex patterns)
    #[error("Guideline compilation failed: {0}")]
    CompilationFailed(String),

    /// Guideline not found
    #[error("Guideline not found: {0}")]
    NotFound(GuidelineId),

    /// Guideline already exists
    #[error("Guideline already exists: {0}")]
    AlreadyExists(GuidelineId),

    /// Multiple guidelines matched with same priority
    #[error("Multiple guidelines matched with same priority: {0:?}")]
    AmbiguousMatch(Vec<GuidelineId>),

    /// No guideline matched
    #[error("No guideline matched for input: {0}")]
    NoMatch(String),

    /// Internal guideline error
    #[error("Internal guideline error: {0}")]
    Internal(String),
}

/// Tool-related errors
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ToolError {
    /// Tool not found
    #[error("Tool not found: {0}")]
    NotFound(ToolId),

    /// Tool already exists
    #[error("Tool already exists: {0}")]
    AlreadyExists(ToolId),

    /// Tool execution timeout
    #[error("Tool execution timeout after {timeout_ms}ms: {tool_id}")]
    Timeout { tool_id: ToolId, timeout_ms: u64 },

    /// Tool execution failed
    #[error("Tool execution failed for {tool_id}: {message}")]
    ExecutionFailed { tool_id: ToolId, message: String },

    /// Invalid tool parameters
    #[error("Invalid tool parameters for {tool_id}: {message}")]
    InvalidParameters { tool_id: ToolId, message: String },

    /// Tool output deserialization failed
    #[error("Tool output deserialization failed: {0}")]
    OutputDeserialization(String),

    /// Internal tool error
    #[error("Internal tool error: {0}")]
    Internal(String),
}

/// Journey-related errors
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum JourneyError {
    /// Journey not found
    #[error("Journey not found: {0}")]
    NotFound(JourneyId),

    /// Journey already exists
    #[error("Journey already exists: {0}")]
    AlreadyExists(JourneyId),

    /// Journey step not found
    #[error("Journey step not found: {step_id} in journey {journey_id}")]
    StepNotFound {
        journey_id: JourneyId,
        step_id: StepId,
    },

    /// Invalid journey transition
    #[error(
        "Invalid journey transition from step {from_step} to {to_step} in journey {journey_id}"
    )]
    InvalidTransition {
        journey_id: JourneyId,
        from_step: StepId,
        to_step: StepId,
    },

    /// Journey already started
    #[error("Journey already started: {0}")]
    AlreadyStarted(JourneyId),

    /// Journey not started
    #[error("Journey not started: {0}")]
    NotStarted(JourneyId),

    /// Journey already completed
    #[error("Journey already completed: {0}")]
    AlreadyCompleted(JourneyId),

    /// Journey has no initial step
    #[error("Journey has no initial step: {0}")]
    NoInitialStep(JourneyId),

    /// Circular journey detected
    #[error("Circular journey detected: {0}")]
    CircularJourney(JourneyId),

    /// Internal journey error
    #[error("Internal journey error: {0}")]
    Internal(String),
}

/// Type alias for Talk library Result
pub type Result<T> = std::result::Result<T, AgentError>;

/// Type alias for Storage Result
pub type StorageResult<T> = std::result::Result<T, StorageError>;

/// Type alias for Guideline Result
pub type GuidelineResult<T> = std::result::Result<T, GuidelineError>;

/// Type alias for Tool Result
pub type ToolResult<T> = std::result::Result<T, ToolError>;

/// Type alias for Journey Result
pub type JourneyResult<T> = std::result::Result<T, JourneyError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_agent_error_display() {
        let session_id = SessionId::new();
        let err = AgentError::SessionNotFound(session_id);
        let display = format!("{}", err);
        assert!(display.contains("Session not found"));
        assert!(display.contains(&session_id.to_string()));
    }

    #[test]
    fn test_storage_error_display() {
        let err = StorageError::Connection("connection refused".to_string());
        let display = format!("{}", err);
        assert!(display.contains("Storage connection failed"));
        assert!(display.contains("connection refused"));
    }

    #[test]
    fn test_guideline_error_display() {
        let guideline_id = GuidelineId::new();
        let err = GuidelineError::NotFound(guideline_id);
        let display = format!("{}", err);
        assert!(display.contains("Guideline not found"));
        assert!(display.contains(&guideline_id.to_string()));
    }

    #[test]
    fn test_tool_error_timeout_display() {
        let tool_id = ToolId::new();
        let err = ToolError::Timeout {
            tool_id,
            timeout_ms: 5000,
        };
        let display = format!("{}", err);
        assert!(display.contains("Tool execution timeout"));
        assert!(display.contains("5000ms"));
        assert!(display.contains(&tool_id.to_string()));
    }

    #[test]
    fn test_tool_error_execution_failed_display() {
        let tool_id = ToolId::new();
        let err = ToolError::ExecutionFailed {
            tool_id,
            message: "API call failed".to_string(),
        };
        let display = format!("{}", err);
        assert!(display.contains("Tool execution failed"));
        assert!(display.contains("API call failed"));
        assert!(display.contains(&tool_id.to_string()));
    }

    #[test]
    fn test_journey_error_step_not_found_display() {
        let journey_id = JourneyId::new();
        let step_id = StepId::new();
        let err = JourneyError::StepNotFound {
            journey_id,
            step_id,
        };
        let display = format!("{}", err);
        assert!(display.contains("Journey step not found"));
        assert!(display.contains(&journey_id.to_string()));
        assert!(display.contains(&step_id.to_string()));
    }

    #[test]
    fn test_journey_error_invalid_transition_display() {
        let journey_id = JourneyId::new();
        let from_step = StepId::new();
        let to_step = StepId::new();
        let err = JourneyError::InvalidTransition {
            journey_id,
            from_step,
            to_step,
        };
        let display = format!("{}", err);
        assert!(display.contains("Invalid journey transition"));
        assert!(display.contains(&journey_id.to_string()));
        assert!(display.contains(&from_step.to_string()));
        assert!(display.contains(&to_step.to_string()));
    }

    #[test]
    fn test_error_conversion_storage_to_agent() {
        let storage_err = StorageError::Connection("test".to_string());
        let agent_err: AgentError = storage_err.into();
        assert!(matches!(agent_err, AgentError::Storage(_)));
    }

    #[test]
    fn test_error_conversion_tool_to_agent() {
        let tool_err = ToolError::NotFound(ToolId::new());
        let agent_err: AgentError = tool_err.into();
        assert!(matches!(agent_err, AgentError::ToolExecution(_)));
    }

    #[test]
    fn test_error_conversion_journey_to_agent() {
        let journey_err = JourneyError::NotFound(JourneyId::new());
        let agent_err: AgentError = journey_err.into();
        assert!(matches!(agent_err, AgentError::JourneyExecution(_)));
    }

    #[test]
    fn test_result_type_aliases() {
        fn returns_result() -> Result<()> {
            Ok(())
        }

        fn returns_storage_result() -> StorageResult<()> {
            Ok(())
        }

        fn returns_guideline_result() -> GuidelineResult<()> {
            Ok(())
        }

        fn returns_tool_result() -> ToolResult<()> {
            Ok(())
        }

        fn returns_journey_result() -> JourneyResult<()> {
            Ok(())
        }

        assert!(returns_result().is_ok());
        assert!(returns_storage_result().is_ok());
        assert!(returns_guideline_result().is_ok());
        assert!(returns_tool_result().is_ok());
        assert!(returns_journey_result().is_ok());
    }
}