tramex_tools/ai/
mod.rs

1//! AI connector module for trace explanation
2//!
3//! Provides a trait-based abstraction for AI chatbot APIs.
4//! Enable with the `ai` feature flag.
5
6pub mod mistral;
7
8use crate::data::Trace;
9use crate::errors::TramexError;
10
11/// Represents an HTTP request to be sent to an AI API
12#[derive(Debug, Clone)]
13pub struct AIRequest {
14    /// The full URL endpoint
15    pub url: String,
16    /// HTTP headers as (key, value) pairs
17    pub headers: Vec<(String, String)>,
18    /// JSON body as a string
19    pub body: String,
20}
21
22/// Status of an AI explanation request
23#[derive(Debug, Clone, Default)]
24pub enum AIExplainStatus {
25    /// No request has been made
26    #[default]
27    Idle,
28    /// Request is in flight
29    Loading,
30    /// Response received successfully
31    Done(String),
32    /// Request failed
33    Error(String),
34}
35
36/// Trait abstracting an AI chatbot connector.
37///
38/// Implementations build the HTTP request and parse the response.
39/// The actual HTTP call is handled by the UI layer (using ehttp).
40pub trait AIConnector: Send + Sync {
41    /// Human-readable name of the AI provider
42    fn name(&self) -> &'static str;
43
44    /// Build the HTTP request for explaining a trace.
45    ///
46    /// # Arguments
47    /// * `trace` - The trace to explain
48    /// * `api_key` - The API key for authentication
49    ///
50    /// # Errors
51    /// Returns an error if the request cannot be built
52    fn build_request(&self, trace: &Trace, api_key: &str) -> Result<AIRequest, TramexError>;
53
54    /// Parse the API response body into a human-readable explanation.
55    ///
56    /// # Arguments
57    /// * `response_body` - The raw JSON response from the API
58    ///
59    /// # Errors
60    /// Returns an error if the response cannot be parsed
61    fn parse_response(&self, response_body: &str) -> Result<String, TramexError>;
62}
63
64/// Available AI provider types
65#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
66pub enum AIProvider {
67    /// Mistral AI
68    #[default]
69    Mistral,
70}
71
72impl std::fmt::Display for AIProvider {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            Self::Mistral => write!(f, "Mistral"),
76        }
77    }
78}
79
80impl AIProvider {
81    /// Return the list of all available providers
82    pub fn all() -> &'static [AIProvider] {
83        &[AIProvider::Mistral]
84    }
85
86    /// Return available (display_name, model_id) pairs for this provider
87    pub fn available_models(&self) -> &'static [(&'static str, &'static str)] {
88        match self {
89            AIProvider::Mistral => &[
90                ("Small", "mistral-small-latest"),
91                ("Medium", "mistral-medium-latest"),
92                ("Large", "mistral-large-latest"),
93            ],
94        }
95    }
96
97    /// Return the default model ID for this provider
98    pub fn default_model(&self) -> &'static str {
99        match self {
100            AIProvider::Mistral => "mistral-medium-latest",
101        }
102    }
103}
104
105/// Create an AIConnector from a provider type and model ID
106pub fn create_connector(provider: &AIProvider, model: &str) -> Box<dyn AIConnector> {
107    match provider {
108        AIProvider::Mistral => Box::new(mistral::MistralConnector::with_model(model)),
109    }
110}