Skip to main content

lc_callbacks/
langsmith_client.rs

1// lc-callbacks/src/langsmith_client.rs
2//! LangSmith API client
3
4use reqwest::Client;
5use std::env;
6
7use super::run_tree::{RunCreate, RunTree, RunUpdate};
8
9/// LangSmith configuration
10#[derive(Debug, Clone)]
11pub struct LangSmithConfig {
12    /// API key (starts with "ls_")
13    pub api_key: String,
14
15    /// API endpoint URL
16    pub api_url: String,
17
18    /// Workspace ID (required for org accounts)
19    pub workspace_id: Option<String>,
20
21    /// Project name
22    pub project_name: String,
23
24    /// Whether tracing is enabled
25    pub tracing_enabled: bool,
26}
27
28impl LangSmithConfig {
29    /// Create config from environment variables
30    pub fn from_env() -> Result<Self, String> {
31        let api_key = env::var("LANGSMITH_API_KEY")
32            .map_err(|_| "LANGSMITH_API_KEY environment variable not set")?;
33
34        let tracing_enabled = env::var("LANGSMITH_TRACING")
35            .map(|v| v == "true")
36            .unwrap_or(true);
37
38        let project_name = env::var("LANGSMITH_PROJECT").unwrap_or_else(|_| "default".to_string());
39
40        let api_url = env::var("LANGSMITH_ENDPOINT")
41            .unwrap_or_else(|_| "https://api.smith.langchain.com".to_string());
42
43        let workspace_id = env::var("LANGSMITH_WORKSPACE_ID").ok();
44
45        Ok(Self {
46            api_key,
47            api_url,
48            workspace_id,
49            project_name,
50            tracing_enabled,
51        })
52    }
53
54    /// Create config with API key
55    pub fn new(api_key: impl Into<String>) -> Self {
56        Self {
57            api_key: api_key.into(),
58            api_url: "https://api.smith.langchain.com".to_string(),
59            workspace_id: None,
60            project_name: "default".to_string(),
61            tracing_enabled: true,
62        }
63    }
64
65    /// Set project name
66    pub fn with_project(mut self, project: impl Into<String>) -> Self {
67        self.project_name = project.into();
68        self
69    }
70
71    /// Set workspace ID
72    pub fn with_workspace(mut self, workspace_id: impl Into<String>) -> Self {
73        self.workspace_id = Some(workspace_id.into());
74        self
75    }
76
77    /// Set API endpoint
78    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
79        self.api_url = endpoint.into();
80        self
81    }
82
83    /// Enable or disable tracing
84    pub fn with_tracing(mut self, enabled: bool) -> Self {
85        self.tracing_enabled = enabled;
86        self
87    }
88}
89
90/// LangSmith API client
91pub struct LangSmithClient {
92    /// Configuration
93    pub config: LangSmithConfig,
94    http_client: Client,
95}
96
97impl LangSmithClient {
98    /// Create a new client
99    pub fn new(config: LangSmithConfig) -> Self {
100        Self {
101            config,
102            http_client: Client::new(),
103        }
104    }
105
106    /// Create client from environment variables
107    pub fn from_env() -> Result<Self, String> {
108        let config = LangSmithConfig::from_env()?;
109        Ok(Self::new(config))
110    }
111
112    /// Check if tracing is enabled
113    pub fn is_tracing_enabled(&self) -> bool {
114        self.config.tracing_enabled
115    }
116
117    /// Get the project name
118    pub fn project_name(&self) -> &str {
119        &self.config.project_name
120    }
121
122    /// Create a run (POST /runs)
123    pub async fn create_run(&self, run: &RunTree) -> Result<(), LangSmithError> {
124        if !self.config.tracing_enabled {
125            return Ok(());
126        }
127
128        let url = format!("{}/runs", self.config.api_url);
129        let mut run_create = RunCreate::from(run);
130        if run_create.session_name.is_none() {
131            run_create.session_name = Some(self.config.project_name.clone());
132        }
133
134        let mut request = self
135            .http_client
136            .post(&url)
137            .header("x-api-key", &self.config.api_key)
138            .json(&run_create);
139
140        if let Some(workspace_id) = &self.config.workspace_id {
141            request = request.header("x-tenant-id", workspace_id);
142        }
143
144        let response = request
145            .send()
146            .await
147            .map_err(|e| LangSmithError::Http(e.to_string()))?;
148
149        if !response.status().is_success() {
150            let status = response.status();
151            let body = response.text().await.unwrap_or_default();
152            return Err(LangSmithError::Api(format!("HTTP {}: {}", status, body)));
153        }
154
155        Ok(())
156    }
157
158    /// Update a run (PATCH /runs/{run_id})
159    pub async fn update_run(&self, run: &RunTree) -> Result<(), LangSmithError> {
160        if !self.config.tracing_enabled {
161            return Ok(());
162        }
163
164        let url = format!("{}/runs/{}", self.config.api_url, run.id);
165        let body = RunUpdate::from(run);
166
167        let mut request = self
168            .http_client
169            .patch(&url)
170            .header("x-api-key", &self.config.api_key)
171            .json(&body);
172
173        if let Some(workspace_id) = &self.config.workspace_id {
174            request = request.header("x-tenant-id", workspace_id);
175        }
176
177        let response = request
178            .send()
179            .await
180            .map_err(|e| LangSmithError::Http(e.to_string()))?;
181
182        if !response.status().is_success() {
183            let status = response.status();
184            let body = response.text().await.unwrap_or_default();
185            return Err(LangSmithError::Api(format!("HTTP {}: {}", status, body)));
186        }
187
188        Ok(())
189    }
190}
191
192/// LangSmith error type
193#[derive(Debug)]
194pub enum LangSmithError {
195    Http(String),
196    Api(String),
197    Config(String),
198}
199
200impl std::fmt::Display for LangSmithError {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        match self {
203            Self::Http(msg) => write!(f, "HTTP error: {}", msg),
204            Self::Api(msg) => write!(f, "API error: {}", msg),
205            Self::Config(msg) => write!(f, "Config error: {}", msg),
206        }
207    }
208}
209
210impl std::error::Error for LangSmithError {}