langsmithrb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1023 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module Langsmith
7
+ class Client
8
+ attr_reader :api_key, :api_url
9
+
10
+ # Initialize a new LangSmith client
11
+ #
12
+ # @param api_key [String] LangSmith API key
13
+ # @param api_url [String] LangSmith API URL
14
+ def initialize(api_key: nil, api_url: nil)
15
+ @api_key = api_key || ENV["LANGSMITH_API_KEY"] || Langsmith.api_key
16
+ @api_url = api_url || ENV["LANGSMITH_API_URL"] || Langsmith.api_url || Langsmith::DEFAULT_API_URL
17
+
18
+ raise Langsmith::Errors::AuthenticationError, "API key is required" unless @api_key
19
+ end
20
+
21
+ # Create a new run
22
+ #
23
+ # @param name [String] Name of the run
24
+ # @param run_type [String] Type of run (e.g., llm, chain, tool)
25
+ # @param project_name [String] Name of the project
26
+ # @param inputs [Hash] Input values for the run
27
+ # @param extra [Hash] Additional metadata for the run
28
+ # @return [Langsmith::Run] The created run
29
+ def create_run(name:, run_type:, project_name: nil, inputs: {}, extra: {})
30
+ data = {
31
+ name: name,
32
+ run_type: run_type,
33
+ inputs: inputs,
34
+ extra: extra
35
+ }
36
+ data[:project_name] = project_name if project_name
37
+
38
+ response = post("/runs", data)
39
+ Langsmith::Run.new(self, response)
40
+ end
41
+
42
+ # Update a run
43
+ #
44
+ # @param run_id [String] ID of the run to update
45
+ # @param outputs [Hash] Output values from the run
46
+ # @param end_time [Time] End time of the run
47
+ # @param error [String] Error message if the run failed
48
+ # @return [Langsmith::Run] The updated run
49
+ def update_run(run_id:, outputs: nil, end_time: nil, error: nil)
50
+ data = {}
51
+ data[:outputs] = outputs if outputs
52
+ data[:end_time] = end_time.iso8601 if end_time
53
+ data[:error] = error if error
54
+
55
+ response = patch("/runs/#{run_id}", data)
56
+ Langsmith::Run.new(self, response)
57
+ end
58
+
59
+ # Get a run by ID
60
+ #
61
+ # @param run_id [String] ID of the run to retrieve
62
+ # @return [Langsmith::Run] The requested run
63
+ def get_run(run_id:)
64
+ response = get("/runs/#{run_id}")
65
+ Langsmith::Run.new(self, response)
66
+ end
67
+
68
+ # List runs with optional filters
69
+ #
70
+ # @param project_name [String] Filter by project name
71
+ # @param run_type [String] Filter by run type
72
+ # @param limit [Integer] Maximum number of runs to return
73
+ # @return [Array<Langsmith::Run>] List of runs
74
+ def list_runs(project_name: nil, run_type: nil, limit: 100)
75
+ params = {}
76
+ params[:project_name] = project_name if project_name
77
+ params[:run_type] = run_type if run_type
78
+ params[:limit] = limit
79
+
80
+ response = get("/runs", params)
81
+ response.map { |run_data| Langsmith::Run.new(self, run_data) }
82
+ end
83
+
84
+ # Create a new project
85
+ #
86
+ # @param name [String] Name of the project
87
+ # @param description [String] Description of the project
88
+ # @return [Langsmith::Project] The created project
89
+ def create_project(name:, description: nil)
90
+ data = { name: name }
91
+ data[:description] = description if description
92
+
93
+ response = post("/projects", data)
94
+ Langsmith::Project.new(self, response)
95
+ end
96
+
97
+ # Get a project by name
98
+ #
99
+ # @param name [String] Name of the project to retrieve
100
+ # @return [Langsmith::Project] The requested project
101
+ def get_project(name:)
102
+ response = get("/projects/#{name}")
103
+ Langsmith::Project.new(self, response)
104
+ end
105
+
106
+ # List projects
107
+ #
108
+ # @param limit [Integer] Maximum number of projects to return
109
+ # @return [Array<Langsmith::Project>] List of projects
110
+ def list_projects(limit: 100)
111
+ params = { limit: limit }
112
+ response = get("/projects", params)
113
+ response.map { |project_data| Langsmith::Project.new(self, project_data) }
114
+ end
115
+
116
+ # Create a new dataset
117
+ #
118
+ # @param name [String] Name of the dataset
119
+ # @param description [String] Description of the dataset
120
+ # @return [Langsmith::Dataset] The created dataset
121
+ def create_dataset(name:, description: nil)
122
+ data = { name: name }
123
+ data[:description] = description if description
124
+
125
+ response = post("/datasets", data)
126
+ Langsmith::Dataset.new(self, response)
127
+ end
128
+
129
+ # Get a dataset by name
130
+ #
131
+ # @param name [String] Name of the dataset to retrieve
132
+ # @return [Langsmith::Dataset] The requested dataset
133
+ def get_dataset(name:)
134
+ response = get("/datasets/#{name}")
135
+ Langsmith::Dataset.new(self, response)
136
+ end
137
+
138
+ # List datasets
139
+ #
140
+ # @param limit [Integer] Maximum number of datasets to return
141
+ # @param offset [Integer] Number of datasets to skip
142
+ # @param name [String, nil] Filter by dataset name (optional)
143
+ # @param name_contains [String, nil] Filter by dataset name containing string (optional)
144
+ # @return [Array<Langsmith::Dataset>] List of datasets
145
+ def list_datasets(limit: 100, offset: 0, name: nil, name_contains: nil)
146
+ params = {
147
+ limit: limit,
148
+ offset: offset
149
+ }
150
+ params[:name] = name if name
151
+ params[:name_contains] = name_contains if name_contains
152
+
153
+ response = get("/datasets", params)
154
+ response.map { |dataset_data| Langsmith::Dataset.new(self, dataset_data) }
155
+ end
156
+
157
+ # Get a dataset by ID
158
+ #
159
+ # @param id [String] ID of the dataset to retrieve
160
+ # @return [Langsmith::Dataset] The requested dataset
161
+ def get_dataset_by_id(id:)
162
+ response = get("/datasets/#{id}")
163
+ Langsmith::Dataset.new(self, response)
164
+ end
165
+
166
+ # Update a dataset
167
+ #
168
+ # @param id [String] ID of the dataset to update
169
+ # @param name [String, nil] New name for the dataset (optional)
170
+ # @param description [String, nil] New description for the dataset (optional)
171
+ # @return [Langsmith::Dataset] The updated dataset
172
+ def update_dataset(id:, name: nil, description: nil)
173
+ data = {}
174
+ data[:name] = name if name
175
+ data[:description] = description if description
176
+
177
+ response = patch("/datasets/#{id}", data)
178
+ Langsmith::Dataset.new(self, response)
179
+ end
180
+
181
+ # Delete a dataset
182
+ #
183
+ # @param id [String] ID of the dataset to delete
184
+ # @return [Boolean] True if successful
185
+ def delete_dataset(id:)
186
+ delete("/datasets/#{id}")
187
+ true
188
+ end
189
+
190
+ # Get an example by ID
191
+ #
192
+ # @param id [String] ID of the example to retrieve
193
+ # @return [Langsmith::Example] The requested example
194
+ def get_example(id:)
195
+ response = get("/examples/#{id}")
196
+ Langsmith::Example.new(self, response)
197
+ end
198
+
199
+ # Update an example
200
+ #
201
+ # @param id [String] ID of the example to update
202
+ # @param inputs [Hash, nil] New input values (optional)
203
+ # @param outputs [Hash, nil] New output values (optional)
204
+ # @param metadata [Hash, nil] New metadata (optional)
205
+ # @return [Langsmith::Example] The updated example
206
+ def update_example(id:, inputs: nil, outputs: nil, metadata: nil)
207
+ data = {}
208
+ data[:inputs] = inputs if inputs
209
+ data[:outputs] = outputs if outputs
210
+ data[:metadata] = metadata if metadata
211
+
212
+ response = patch("/examples/#{id}", data)
213
+ Langsmith::Example.new(self, response)
214
+ end
215
+
216
+ # Delete an example
217
+ #
218
+ # @param id [String] ID of the example to delete
219
+ # @return [Boolean] True if successful
220
+ def delete_example(id:)
221
+ delete("/examples/#{id}")
222
+ true
223
+ end
224
+
225
+ # Create multiple examples in batch
226
+ #
227
+ # @param dataset_id [String] ID of the dataset to add examples to
228
+ # @param examples [Array<Hash>] Array of example data, each containing :inputs and optionally :outputs and :metadata
229
+ # @return [Array<Langsmith::Example>] The created examples
230
+ def create_examples_batch(dataset_id:, examples:)
231
+ data = examples.map do |example|
232
+ example_data = {
233
+ dataset_id: dataset_id,
234
+ inputs: example[:inputs]
235
+ }
236
+ example_data[:outputs] = example[:outputs] if example[:outputs]
237
+ example_data[:metadata] = example[:metadata] if example[:metadata]
238
+ example_data
239
+ end
240
+
241
+ response = post("/examples/batch", { examples: data })
242
+ response.map { |example_data| Langsmith::Example.new(self, example_data) }
243
+ end
244
+
245
+ # Get an evaluation by ID
246
+ #
247
+ # @param id [String] ID of the evaluation to retrieve
248
+ # @return [Langsmith::Evaluation] The requested evaluation
249
+ def get_evaluation(id:)
250
+ response = get("/evaluations/#{id}")
251
+ Langsmith::Evaluation.new(self, response)
252
+ end
253
+
254
+ # List evaluations with optional filters
255
+ #
256
+ # @param dataset_id [String, nil] Filter by dataset ID (optional)
257
+ # @param evaluator_name [String, nil] Filter by evaluator name (optional)
258
+ # @param run_id [String, nil] Filter by run ID (optional)
259
+ # @param status [String, nil] Filter by status (optional)
260
+ # @param limit [Integer] Maximum number of evaluations to return
261
+ # @param offset [Integer] Number of evaluations to skip
262
+ # @return [Array<Langsmith::Evaluation>] List of evaluations
263
+ def list_evaluations(dataset_id: nil, evaluator_name: nil, run_id: nil, status: nil, limit: 100, offset: 0)
264
+ params = {
265
+ limit: limit,
266
+ offset: offset
267
+ }
268
+ params[:dataset_id] = dataset_id if dataset_id
269
+ params[:evaluator_name] = evaluator_name if evaluator_name
270
+ params[:run_id] = run_id if run_id
271
+ params[:status] = status if status
272
+
273
+ response = get("/evaluations", params)
274
+ response.map { |eval_data| Langsmith::Evaluation.new(self, eval_data) }
275
+ end
276
+
277
+ # Update an evaluation's metadata
278
+ #
279
+ # @param id [String] ID of the evaluation to update
280
+ # @param metadata [Hash] New metadata for the evaluation
281
+ # @return [Langsmith::Evaluation] The updated evaluation
282
+ def update_evaluation_metadata(id:, metadata:)
283
+ data = { metadata: metadata }
284
+ response = patch("/evaluations/#{id}", data)
285
+ Langsmith::Evaluation.new(self, response)
286
+ end
287
+
288
+ # Cancel an evaluation
289
+ #
290
+ # @param id [String] ID of the evaluation to cancel
291
+ # @return [Langsmith::Evaluation] The canceled evaluation
292
+ def cancel_evaluation(id:)
293
+ response = post("/evaluations/#{id}/cancel")
294
+ Langsmith::Evaluation.new(self, response)
295
+ end
296
+
297
+ # Delete an evaluation
298
+ #
299
+ # @param id [String] ID of the evaluation to delete
300
+ # @return [Boolean] True if successful
301
+ def delete_evaluation(id:)
302
+ delete("/evaluations/#{id}")
303
+ true
304
+ end
305
+
306
+ # Create feedback for a run
307
+ #
308
+ # @param run_id [String] ID of the run to provide feedback for
309
+ # @param key [String] Feedback key (e.g., "correctness", "helpfulness")
310
+ # @param score [Float] Feedback score (typically 0.0 to 1.0)
311
+ # @param comment [String] Optional comment with the feedback
312
+ # @return [Langsmith::Feedback] The created feedback
313
+ def create_feedback(run_id:, key:, score:, comment: nil)
314
+ data = {
315
+ run_id: run_id,
316
+ key: key,
317
+ score: score
318
+ }
319
+ data[:comment] = comment if comment
320
+
321
+ response = post("/feedback", data)
322
+ Langsmith::Feedback.new(self, response)
323
+ end
324
+
325
+ # Get feedback for a run
326
+ #
327
+ # @param run_id [String] ID of the run to get feedback for
328
+ # @return [Array<Langsmith::Feedback>] List of feedback for the run
329
+ def get_feedback(run_id:)
330
+ params = { run_id: run_id }
331
+ response = get("/feedback", params)
332
+ response.map { |feedback_data| Langsmith::Feedback.new(self, feedback_data) }
333
+ end
334
+
335
+ # Create a new tracer session
336
+ #
337
+ # @param name [String] Name of the tracer session
338
+ # @param metadata [Hash] Additional metadata for the tracer session
339
+ # @return [Hash] The created tracer session data
340
+ def create_tracer_session(name:, metadata: {})
341
+ data = {
342
+ name: name,
343
+ metadata: metadata
344
+ }
345
+ post("/tracer-sessions", data)
346
+ end
347
+
348
+ # Get a tracer session by ID
349
+ #
350
+ # @param id [String] ID of the tracer session to retrieve
351
+ # @return [Hash] The requested tracer session data
352
+ def get_tracer_session(id:)
353
+ get("/tracer-sessions/#{id}")
354
+ end
355
+
356
+ # List tracer sessions with optional filters
357
+ #
358
+ # @param limit [Integer] Maximum number of tracer sessions to return
359
+ # @param offset [Integer] Number of tracer sessions to skip
360
+ # @return [Array<Hash>] List of tracer sessions
361
+ def list_tracer_sessions(limit: 100, offset: 0)
362
+ params = {
363
+ limit: limit,
364
+ offset: offset
365
+ }
366
+ get("/tracer-sessions", params)
367
+ end
368
+
369
+ # Get current organization information
370
+ #
371
+ # @return [Hash] Organization information
372
+ def get_current_organization
373
+ get("/orgs/current")
374
+ end
375
+
376
+ # List organizations the user belongs to
377
+ #
378
+ # @return [Array<Hash>] List of organizations
379
+ def list_organizations
380
+ get("/orgs")
381
+ end
382
+
383
+ # Get organization by ID
384
+ #
385
+ # @param id [String] ID of the organization to retrieve
386
+ # @return [Hash] The requested organization data
387
+ def get_organization(id:)
388
+ get("/orgs/#{id}")
389
+ end
390
+
391
+ # List API keys
392
+ #
393
+ # @return [Array<Hash>] List of API keys
394
+ def list_api_keys
395
+ get("/api-key")
396
+ end
397
+
398
+ # Create a new API key
399
+ #
400
+ # @param name [String] Name of the API key
401
+ # @param expires_at [Time, nil] Expiration time for the API key (optional)
402
+ # @return [Hash] The created API key data
403
+ def create_api_key(name:, expires_at: nil)
404
+ data = { name: name }
405
+ data[:expires_at] = expires_at.iso8601 if expires_at
406
+ post("/api-key", data)
407
+ end
408
+
409
+ # Delete an API key
410
+ #
411
+ # @param id [String] ID of the API key to delete
412
+ # @return [Boolean] True if successful
413
+ def delete_api_key(id:)
414
+ delete("/api-key/#{id}")
415
+ true
416
+ end
417
+
418
+ # Get tenant information
419
+ #
420
+ # @param id [String] ID of the tenant to retrieve
421
+ # @return [Hash] The requested tenant data
422
+ def get_tenant(id:)
423
+ get("/tenant/#{id}")
424
+ end
425
+
426
+ # List tenants
427
+ #
428
+ # @return [Array<Hash>] List of tenants
429
+ def list_tenants
430
+ get("/tenant")
431
+ end
432
+
433
+ # Create a new tenant
434
+ #
435
+ # @param name [String] Name of the tenant
436
+ # @param config [Hash] Configuration for the tenant
437
+ # @return [Hash] The created tenant data
438
+ def create_tenant(name:, config: {})
439
+ data = {
440
+ name: name,
441
+ config: config
442
+ }
443
+ post("/tenant", data)
444
+ end
445
+
446
+ # Update a tenant
447
+ #
448
+ # @param id [String] ID of the tenant to update
449
+ # @param name [String, nil] New name for the tenant (optional)
450
+ # @param config [Hash, nil] New configuration for the tenant (optional)
451
+ # @return [Hash] The updated tenant data
452
+ def update_tenant(id:, name: nil, config: nil)
453
+ data = {}
454
+ data[:name] = name if name
455
+ data[:config] = config if config
456
+ patch("/tenant/#{id}", data)
457
+ end
458
+
459
+ # Create a new tag
460
+ #
461
+ # @param name [String] Name of the tag
462
+ # @return [Hash] The created tag data
463
+ def create_tag(name:)
464
+ data = { name: name }
465
+ post("/tags", data)
466
+ end
467
+
468
+ # Get a tag by ID
469
+ #
470
+ # @param id [String] ID of the tag to retrieve
471
+ # @return [Hash] The requested tag data
472
+ def get_tag(id:)
473
+ get("/tags/#{id}")
474
+ end
475
+
476
+ # List tags
477
+ #
478
+ # @param limit [Integer] Maximum number of tags to return
479
+ # @param offset [Integer] Number of tags to skip
480
+ # @return [Array<Hash>] List of tags
481
+ def list_tags(limit: 100, offset: 0)
482
+ params = {
483
+ limit: limit,
484
+ offset: offset
485
+ }
486
+ get("/tags", params)
487
+ end
488
+
489
+ # Delete a tag
490
+ #
491
+ # @param id [String] ID of the tag to delete
492
+ # @return [Boolean] True if successful
493
+ def delete_tag(id:)
494
+ delete("/tags/#{id}")
495
+ true
496
+ end
497
+
498
+ # Create a new prompt
499
+ #
500
+ # @param name [String] Name of the prompt
501
+ # @param prompt_template [String] The prompt template
502
+ # @param metadata [Hash] Additional metadata for the prompt
503
+ # @return [Hash] The created prompt data
504
+ def create_prompt(name:, prompt_template:, metadata: {})
505
+ data = {
506
+ name: name,
507
+ prompt_template: prompt_template,
508
+ metadata: metadata
509
+ }
510
+ post("/prompts", data)
511
+ end
512
+
513
+ # Get a prompt by ID
514
+ #
515
+ # @param id [String] ID of the prompt to retrieve
516
+ # @return [Hash] The requested prompt data
517
+ def get_prompt(id:)
518
+ get("/prompts/#{id}")
519
+ end
520
+
521
+ # List prompts
522
+ #
523
+ # @param limit [Integer] Maximum number of prompts to return
524
+ # @param offset [Integer] Number of prompts to skip
525
+ # @return [Array<Hash>] List of prompts
526
+ def list_prompts(limit: 100, offset: 0)
527
+ params = {
528
+ limit: limit,
529
+ offset: offset
530
+ }
531
+ get("/prompts", params)
532
+ end
533
+
534
+ # Update a prompt
535
+ #
536
+ # @param id [String] ID of the prompt to update
537
+ # @param name [String, nil] New name for the prompt (optional)
538
+ # @param prompt_template [String, nil] New prompt template (optional)
539
+ # @param metadata [Hash, nil] New metadata for the prompt (optional)
540
+ # @return [Hash] The updated prompt data
541
+ def update_prompt(id:, name: nil, prompt_template: nil, metadata: nil)
542
+ data = {}
543
+ data[:name] = name if name
544
+ data[:prompt_template] = prompt_template if prompt_template
545
+ data[:metadata] = metadata if metadata
546
+ patch("/prompts/#{id}", data)
547
+ end
548
+
549
+ # Delete a prompt
550
+ #
551
+ # @param id [String] ID of the prompt to delete
552
+ # @return [Boolean] True if successful
553
+ def delete_prompt(id:)
554
+ delete("/prompts/#{id}")
555
+ true
556
+ end
557
+
558
+ # Create a new annotation queue
559
+ #
560
+ # @param name [String] Name of the annotation queue
561
+ # @param description [String] Description of the annotation queue
562
+ # @param metadata [Hash] Additional metadata for the annotation queue
563
+ # @return [Hash] The created annotation queue data
564
+ def create_annotation_queue(name:, description: nil, metadata: {})
565
+ data = {
566
+ name: name,
567
+ metadata: metadata
568
+ }
569
+ data[:description] = description if description
570
+ post("/annotation-queues", data)
571
+ end
572
+
573
+ # Get an annotation queue by ID
574
+ #
575
+ # @param id [String] ID of the annotation queue to retrieve
576
+ # @return [Hash] The requested annotation queue data
577
+ def get_annotation_queue(id:)
578
+ get("/annotation-queues/#{id}")
579
+ end
580
+
581
+ # List annotation queues
582
+ #
583
+ # @param limit [Integer] Maximum number of annotation queues to return
584
+ # @param offset [Integer] Number of annotation queues to skip
585
+ # @return [Array<Hash>] List of annotation queues
586
+ def list_annotation_queues(limit: 100, offset: 0)
587
+ params = {
588
+ limit: limit,
589
+ offset: offset
590
+ }
591
+ get("/annotation-queues", params)
592
+ end
593
+
594
+ # Update an annotation queue
595
+ #
596
+ # @param id [String] ID of the annotation queue to update
597
+ # @param name [String, nil] New name for the annotation queue (optional)
598
+ # @param description [String, nil] New description for the annotation queue (optional)
599
+ # @param metadata [Hash, nil] New metadata for the annotation queue (optional)
600
+ # @return [Hash] The updated annotation queue data
601
+ def update_annotation_queue(id:, name: nil, description: nil, metadata: nil)
602
+ data = {}
603
+ data[:name] = name if name
604
+ data[:description] = description if description
605
+ data[:metadata] = metadata if metadata
606
+ patch("/annotation-queues/#{id}", data)
607
+ end
608
+
609
+ # Delete an annotation queue
610
+ #
611
+ # @param id [String] ID of the annotation queue to delete
612
+ # @return [Boolean] True if successful
613
+ def delete_annotation_queue(id:)
614
+ delete("/annotation-queues/#{id}")
615
+ true
616
+ end
617
+
618
+ # Create a new feedback config
619
+ #
620
+ # @param name [String] Name of the feedback config
621
+ # @param type [String] Type of feedback config
622
+ # @param metadata [Hash] Additional metadata for the feedback config
623
+ # @return [Hash] The created feedback config data
624
+ def create_feedback_config(name:, type:, metadata: {})
625
+ data = {
626
+ name: name,
627
+ type: type,
628
+ metadata: metadata
629
+ }
630
+ post("/feedback-configs", data)
631
+ end
632
+
633
+ # Get a feedback config by ID
634
+ #
635
+ # @param id [String] ID of the feedback config to retrieve
636
+ # @return [Hash] The requested feedback config data
637
+ def get_feedback_config(id:)
638
+ get("/feedback-configs/#{id}")
639
+ end
640
+
641
+ # List feedback configs
642
+ #
643
+ # @param limit [Integer] Maximum number of feedback configs to return
644
+ # @param offset [Integer] Number of feedback configs to skip
645
+ # @return [Array<Hash>] List of feedback configs
646
+ def list_feedback_configs(limit: 100, offset: 0)
647
+ params = {
648
+ limit: limit,
649
+ offset: offset
650
+ }
651
+ get("/feedback-configs", params)
652
+ end
653
+
654
+ # Update a feedback config
655
+ #
656
+ # @param id [String] ID of the feedback config to update
657
+ # @param name [String, nil] New name for the feedback config (optional)
658
+ # @param type [String, nil] New type for the feedback config (optional)
659
+ # @param metadata [Hash, nil] New metadata for the feedback config (optional)
660
+ # @return [Hash] The updated feedback config data
661
+ def update_feedback_config(id:, name: nil, type: nil, metadata: nil)
662
+ data = {}
663
+ data[:name] = name if name
664
+ data[:type] = type if type
665
+ data[:metadata] = metadata if metadata
666
+ patch("/feedback-configs/#{id}", data)
667
+ end
668
+
669
+ # Delete a feedback config
670
+ #
671
+ # @param id [String] ID of the feedback config to delete
672
+ # @return [Boolean] True if successful
673
+ def delete_feedback_config(id:)
674
+ delete("/feedback-configs/#{id}")
675
+ true
676
+ end
677
+
678
+ # Get usage limits
679
+ #
680
+ # @return [Hash] Usage limits information
681
+ def get_usage_limits
682
+ get("/usage-limits")
683
+ end
684
+
685
+ # Get API info
686
+ #
687
+ # @return [Hash] API information
688
+ def get_api_info
689
+ get("/info")
690
+ end
691
+
692
+ # Get analytics for a project
693
+ #
694
+ # @param project_id [String] ID of the project to get analytics for
695
+ # @param start_time [Time, nil] Start time for analytics (optional)
696
+ # @param end_time [Time, nil] End time for analytics (optional)
697
+ # @param group_by [String, nil] Field to group analytics by (optional)
698
+ # @param metrics [Array<String>, nil] Metrics to include (optional)
699
+ # @param filters [Hash, nil] Filters to apply (optional)
700
+ # @return [Hash] Analytics data
701
+ def get_project_analytics(project_id:, start_time: nil, end_time: nil, group_by: nil, metrics: nil, filters: nil)
702
+ params = {}
703
+ params[:start_time] = start_time.iso8601 if start_time
704
+ params[:end_time] = end_time.iso8601 if end_time
705
+ params[:group_by] = group_by if group_by
706
+ params[:metrics] = metrics if metrics
707
+ params[:filters] = filters if filters
708
+
709
+ get("/projects/#{project_id}/analytics", params)
710
+ end
711
+
712
+ # Get analytics for a dataset
713
+ #
714
+ # @param dataset_id [String] ID of the dataset to get analytics for
715
+ # @param start_time [Time, nil] Start time for analytics (optional)
716
+ # @param end_time [Time, nil] End time for analytics (optional)
717
+ # @param group_by [String, nil] Field to group analytics by (optional)
718
+ # @param metrics [Array<String>, nil] Metrics to include (optional)
719
+ # @param filters [Hash, nil] Filters to apply (optional)
720
+ # @return [Hash] Analytics data
721
+ def get_dataset_analytics(dataset_id:, start_time: nil, end_time: nil, group_by: nil, metrics: nil, filters: nil)
722
+ params = {}
723
+ params[:start_time] = start_time.iso8601 if start_time
724
+ params[:end_time] = end_time.iso8601 if end_time
725
+ params[:group_by] = group_by if group_by
726
+ params[:metrics] = metrics if metrics
727
+ params[:filters] = filters if filters
728
+
729
+ get("/datasets/#{dataset_id}/analytics", params)
730
+ end
731
+
732
+ # Create a comment on a run
733
+ #
734
+ # @param run_id [String] ID of the run to comment on
735
+ # @param comment [String] Comment text
736
+ # @param metadata [Hash, nil] Additional metadata for the comment (optional)
737
+ # @return [Hash] The created comment data
738
+ def create_run_comment(run_id:, comment:, metadata: nil)
739
+ data = {
740
+ comment: comment
741
+ }
742
+ data[:metadata] = metadata if metadata
743
+
744
+ post("/runs/#{run_id}/comments", data)
745
+ end
746
+
747
+ # List comments on a run
748
+ #
749
+ # @param run_id [String] ID of the run to list comments for
750
+ # @param limit [Integer] Maximum number of comments to return
751
+ # @param offset [Integer] Number of comments to skip
752
+ # @return [Array<Hash>] List of comments
753
+ def list_run_comments(run_id:, limit: 100, offset: 0)
754
+ params = {
755
+ limit: limit,
756
+ offset: offset
757
+ }
758
+
759
+ get("/runs/#{run_id}/comments", params)
760
+ end
761
+
762
+ # Delete a comment
763
+ #
764
+ # @param comment_id [String] ID of the comment to delete
765
+ # @return [Boolean] True if successful
766
+ def delete_comment(comment_id:)
767
+ delete("/comments/#{comment_id}")
768
+ true
769
+ end
770
+
771
+ # Create an event
772
+ #
773
+ # @param name [String] Event name
774
+ # @param data [Hash] Event data
775
+ # @param metadata [Hash, nil] Additional metadata for the event (optional)
776
+ # @return [Hash] The created event data
777
+ def create_event(name:, data:, metadata: nil)
778
+ event_data = {
779
+ name: name,
780
+ data: data
781
+ }
782
+ event_data[:metadata] = metadata if metadata
783
+
784
+ post("/events", event_data)
785
+ end
786
+
787
+ # List events
788
+ #
789
+ # @param name [String, nil] Filter by event name (optional)
790
+ # @param start_time [Time, nil] Start time for events (optional)
791
+ # @param end_time [Time, nil] End time for events (optional)
792
+ # @param limit [Integer] Maximum number of events to return
793
+ # @param offset [Integer] Number of events to skip
794
+ # @return [Array<Hash>] List of events
795
+ def list_events(name: nil, start_time: nil, end_time: nil, limit: 100, offset: 0)
796
+ params = {
797
+ limit: limit,
798
+ offset: offset
799
+ }
800
+ params[:name] = name if name
801
+ params[:start_time] = start_time.iso8601 if start_time
802
+ params[:end_time] = end_time.iso8601 if end_time
803
+
804
+ get("/events", params)
805
+ end
806
+
807
+ # Get settings
808
+ #
809
+ # @return [Hash] Settings data
810
+ def get_settings
811
+ get("/settings")
812
+ end
813
+
814
+ # Update settings
815
+ #
816
+ # @param settings [Hash] Settings to update
817
+ # @return [Hash] Updated settings data
818
+ def update_settings(settings:)
819
+ patch("/settings", settings)
820
+ end
821
+
822
+ # Bulk tag runs
823
+ #
824
+ # @param run_ids [Array<String>] List of run IDs to tag
825
+ # @param tag_ids [Array<String>] List of tag IDs to apply
826
+ # @return [Hash] Result of the bulk operation
827
+ def bulk_tag_runs(run_ids:, tag_ids:)
828
+ data = {
829
+ run_ids: run_ids,
830
+ tag_ids: tag_ids
831
+ }
832
+
833
+ post("/runs/bulk/tag", data)
834
+ end
835
+
836
+ # Bulk untag runs
837
+ #
838
+ # @param run_ids [Array<String>] List of run IDs to untag
839
+ # @param tag_ids [Array<String>] List of tag IDs to remove
840
+ # @return [Hash] Result of the bulk operation
841
+ def bulk_untag_runs(run_ids:, tag_ids:)
842
+ data = {
843
+ run_ids: run_ids,
844
+ tag_ids: tag_ids
845
+ }
846
+
847
+ post("/runs/bulk/untag", data)
848
+ end
849
+
850
+ # Bulk delete runs
851
+ #
852
+ # @param run_ids [Array<String>] List of run IDs to delete
853
+ # @return [Hash] Result of the bulk operation
854
+ def bulk_delete_runs(run_ids:)
855
+ data = {
856
+ run_ids: run_ids
857
+ }
858
+
859
+ post("/runs/bulk/delete", data)
860
+ end
861
+
862
+ # Create a webhook
863
+ #
864
+ # @param url [String] Webhook URL
865
+ # @param event_types [Array<String>] List of event types to subscribe to
866
+ # @param metadata [Hash, nil] Additional metadata for the webhook (optional)
867
+ # @return [Hash] The created webhook data
868
+ def create_webhook(url:, event_types:, metadata: nil)
869
+ data = {
870
+ url: url,
871
+ event_types: event_types
872
+ }
873
+ data[:metadata] = metadata if metadata
874
+
875
+ post("/webhooks", data)
876
+ end
877
+
878
+ # Get a webhook by ID
879
+ #
880
+ # @param id [String] ID of the webhook to retrieve
881
+ # @return [Hash] The requested webhook data
882
+ def get_webhook(id:)
883
+ get("/webhooks/#{id}")
884
+ end
885
+
886
+ # List webhooks
887
+ #
888
+ # @param limit [Integer] Maximum number of webhooks to return
889
+ # @param offset [Integer] Number of webhooks to skip
890
+ # @return [Array<Hash>] List of webhooks
891
+ def list_webhooks(limit: 100, offset: 0)
892
+ params = {
893
+ limit: limit,
894
+ offset: offset
895
+ }
896
+
897
+ get("/webhooks", params)
898
+ end
899
+
900
+ # Update a webhook
901
+ #
902
+ # @param id [String] ID of the webhook to update
903
+ # @param url [String, nil] New webhook URL (optional)
904
+ # @param event_types [Array<String>, nil] New list of event types (optional)
905
+ # @param metadata [Hash, nil] New metadata for the webhook (optional)
906
+ # @param active [Boolean, nil] Whether the webhook is active (optional)
907
+ # @return [Hash] The updated webhook data
908
+ def update_webhook(id:, url: nil, event_types: nil, metadata: nil, active: nil)
909
+ data = {}
910
+ data[:url] = url if url
911
+ data[:event_types] = event_types if event_types
912
+ data[:metadata] = metadata if metadata
913
+ data[:active] = active unless active.nil?
914
+
915
+ patch("/webhooks/#{id}", data)
916
+ end
917
+
918
+ # Delete a webhook
919
+ #
920
+ # @param id [String] ID of the webhook to delete
921
+ # @return [Boolean] True if successful
922
+ def delete_webhook(id:)
923
+ delete("/webhooks/#{id}")
924
+ true
925
+ end
926
+
927
+ # List team members
928
+ #
929
+ # @param organization_id [String, nil] Organization ID (optional, defaults to current organization)
930
+ # @param limit [Integer] Maximum number of members to return
931
+ # @param offset [Integer] Number of members to skip
932
+ # @return [Array<Hash>] List of team members
933
+ def list_team_members(organization_id: nil, limit: 100, offset: 0)
934
+ params = {
935
+ limit: limit,
936
+ offset: offset
937
+ }
938
+ params[:organization_id] = organization_id if organization_id
939
+
940
+ get("/team/members", params)
941
+ end
942
+
943
+ # Invite a team member
944
+ #
945
+ # @param email [String] Email of the person to invite
946
+ # @param role [String] Role to assign to the new member
947
+ # @param organization_id [String, nil] Organization ID (optional, defaults to current organization)
948
+ # @return [Hash] The created invitation data
949
+ def invite_team_member(email:, role:, organization_id: nil)
950
+ data = {
951
+ email: email,
952
+ role: role
953
+ }
954
+ data[:organization_id] = organization_id if organization_id
955
+
956
+ post("/team/invite", data)
957
+ end
958
+
959
+ # Remove a team member
960
+ #
961
+ # @param user_id [String] ID of the user to remove
962
+ # @param organization_id [String, nil] Organization ID (optional, defaults to current organization)
963
+ # @return [Boolean] True if successful
964
+ def remove_team_member(user_id:, organization_id: nil)
965
+ data = {
966
+ user_id: user_id
967
+ }
968
+ data[:organization_id] = organization_id if organization_id
969
+
970
+ post("/team/remove", data)
971
+ true
972
+ end
973
+
974
+ # HTTP request methods
975
+
976
+ def get(path, params = {})
977
+ response = connection.get(path, params)
978
+ handle_response(response)
979
+ end
980
+
981
+ def post(path, data = {})
982
+ response = connection.post(path) do |req|
983
+ req.body = JSON.generate(data)
984
+ end
985
+ handle_response(response)
986
+ end
987
+
988
+ def patch(path, data = {})
989
+ response = connection.patch(path) do |req|
990
+ req.body = JSON.generate(data)
991
+ end
992
+ handle_response(response)
993
+ end
994
+
995
+ def delete(path)
996
+ response = connection.delete(path)
997
+ handle_response(response)
998
+ end
999
+
1000
+ private
1001
+
1002
+ def connection
1003
+ @connection ||= Faraday.new(url: @api_url) do |conn|
1004
+ conn.headers["Authorization"] = "Bearer #{@api_key}"
1005
+ conn.headers["Content-Type"] = "application/json"
1006
+ conn.adapter Faraday.default_adapter
1007
+ end
1008
+ end
1009
+
1010
+ def handle_response(response)
1011
+ case response.status
1012
+ when 200..299
1013
+ JSON.parse(response.body)
1014
+ when 401
1015
+ raise Langsmith::Errors::AuthenticationError, "Authentication failed: #{response.body}"
1016
+ when 404
1017
+ raise Langsmith::Errors::ResourceNotFoundError, "Resource not found: #{response.body}"
1018
+ else
1019
+ raise Langsmith::Errors::APIError, "API error (#{response.status}): #{response.body}"
1020
+ end
1021
+ end
1022
+ end
1023
+ end