lc_callbacks/
langsmith_client.rs1use reqwest::Client;
5use std::env;
6
7use super::run_tree::{RunCreate, RunTree, RunUpdate};
8
9#[derive(Debug, Clone)]
11pub struct LangSmithConfig {
12 pub api_key: String,
14
15 pub api_url: String,
17
18 pub workspace_id: Option<String>,
20
21 pub project_name: String,
23
24 pub tracing_enabled: bool,
26}
27
28impl LangSmithConfig {
29 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 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 pub fn with_project(mut self, project: impl Into<String>) -> Self {
67 self.project_name = project.into();
68 self
69 }
70
71 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 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
79 self.api_url = endpoint.into();
80 self
81 }
82
83 pub fn with_tracing(mut self, enabled: bool) -> Self {
85 self.tracing_enabled = enabled;
86 self
87 }
88}
89
90pub struct LangSmithClient {
92 pub config: LangSmithConfig,
94 http_client: Client,
95}
96
97impl LangSmithClient {
98 pub fn new(config: LangSmithConfig) -> Self {
100 Self {
101 config,
102 http_client: Client::new(),
103 }
104 }
105
106 pub fn from_env() -> Result<Self, String> {
108 let config = LangSmithConfig::from_env()?;
109 Ok(Self::new(config))
110 }
111
112 pub fn is_tracing_enabled(&self) -> bool {
114 self.config.tracing_enabled
115 }
116
117 pub fn project_name(&self) -> &str {
119 &self.config.project_name
120 }
121
122 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 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#[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 {}