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, LangSmithError> {
31 let api_key = env::var("LANGSMITH_API_KEY").map_err(|_| {
32 LangSmithError::Config("LANGSMITH_API_KEY environment variable not set".to_string())
33 })?;
34
35 let tracing_enabled = env::var("LANGSMITH_TRACING")
36 .map(|v| v == "true")
37 .unwrap_or(true);
38
39 let project_name = env::var("LANGSMITH_PROJECT").unwrap_or_else(|_| "default".to_string());
40
41 let api_url = env::var("LANGSMITH_ENDPOINT")
42 .unwrap_or_else(|_| "https://api.smith.langchain.com".to_string());
43
44 let workspace_id = env::var("LANGSMITH_WORKSPACE_ID").ok();
45
46 Ok(Self {
47 api_key,
48 api_url,
49 workspace_id,
50 project_name,
51 tracing_enabled,
52 })
53 }
54
55 pub fn new(api_key: impl Into<String>) -> Self {
57 Self {
58 api_key: api_key.into(),
59 api_url: "https://api.smith.langchain.com".to_string(),
60 workspace_id: None,
61 project_name: "default".to_string(),
62 tracing_enabled: true,
63 }
64 }
65
66 pub fn with_project(mut self, project: impl Into<String>) -> Self {
68 self.project_name = project.into();
69 self
70 }
71
72 pub fn with_workspace(mut self, workspace_id: impl Into<String>) -> Self {
74 self.workspace_id = Some(workspace_id.into());
75 self
76 }
77
78 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
80 self.api_url = endpoint.into();
81 self
82 }
83
84 pub fn with_tracing(mut self, enabled: bool) -> Self {
86 self.tracing_enabled = enabled;
87 self
88 }
89}
90
91pub struct LangSmithClient {
93 pub config: LangSmithConfig,
95 http_client: Client,
96}
97
98impl LangSmithClient {
99 pub fn new(config: LangSmithConfig) -> Self {
101 Self {
102 config,
103 http_client: Client::new(),
104 }
105 }
106
107 pub fn from_env() -> Result<Self, LangSmithError> {
109 let config = LangSmithConfig::from_env()?;
110 Ok(Self::new(config))
111 }
112
113 pub fn is_tracing_enabled(&self) -> bool {
115 self.config.tracing_enabled
116 }
117
118 pub fn project_name(&self) -> &str {
120 &self.config.project_name
121 }
122
123 pub async fn create_run(&self, run: &RunTree) -> Result<(), LangSmithError> {
125 if !self.config.tracing_enabled {
126 return Ok(());
127 }
128
129 let url = format!("{}/runs", self.config.api_url);
130 let mut run_create = RunCreate::from(run);
131 if run_create.session_name.is_none() {
132 run_create.session_name = Some(self.config.project_name.clone());
133 }
134
135 let mut request = self
136 .http_client
137 .post(&url)
138 .header("x-api-key", &self.config.api_key)
139 .json(&run_create);
140
141 if let Some(workspace_id) = &self.config.workspace_id {
142 request = request.header("x-tenant-id", workspace_id);
143 }
144
145 let response = request
146 .send()
147 .await
148 .map_err(|e| LangSmithError::Http(e.to_string()))?;
149
150 if !response.status().is_success() {
151 let status = response.status();
152 let body = response.text().await.unwrap_or_default();
153 return Err(LangSmithError::Api(format!("HTTP {}: {}", status, body)));
154 }
155
156 Ok(())
157 }
158
159 pub async fn update_run(&self, run: &RunTree) -> Result<(), LangSmithError> {
161 if !self.config.tracing_enabled {
162 return Ok(());
163 }
164
165 let url = format!("{}/runs/{}", self.config.api_url, run.id);
166 let body = RunUpdate::from(run);
167
168 let mut request = self
169 .http_client
170 .patch(&url)
171 .header("x-api-key", &self.config.api_key)
172 .json(&body);
173
174 if let Some(workspace_id) = &self.config.workspace_id {
175 request = request.header("x-tenant-id", workspace_id);
176 }
177
178 let response = request
179 .send()
180 .await
181 .map_err(|e| LangSmithError::Http(e.to_string()))?;
182
183 if !response.status().is_success() {
184 let status = response.status();
185 let body = response.text().await.unwrap_or_default();
186 return Err(LangSmithError::Api(format!("HTTP {}: {}", status, body)));
187 }
188
189 Ok(())
190 }
191}
192
193#[derive(Debug)]
195#[non_exhaustive]
196pub enum LangSmithError {
197 Http(String),
199 Api(String),
201 Config(String),
203}
204
205impl std::fmt::Display for LangSmithError {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 match self {
208 Self::Http(msg) => write!(f, "HTTP error: {}", msg),
209 Self::Api(msg) => write!(f, "API error: {}", msg),
210 Self::Config(msg) => write!(f, "Config error: {}", msg),
211 }
212 }
213}
214
215impl std::error::Error for LangSmithError {}