langgraph-platform 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 531a708f8f8929dd7f1e7199b22db36a44fbf247a5f112a4369747f80bb23a7e
4
+ data.tar.gz: 5aa0040571033f2e6df926d2056c4be0435de08c411c912bd16fb77e27a79afe
5
+ SHA512:
6
+ metadata.gz: b89d5aebdb0946e58e43d01324cf4126b683aa523d562907e2f89bb6bdf3cef4bcbac88474268283bf856ace40101cc068cc352016fe155a7537abce53263608
7
+ data.tar.gz: c2ccfa1cff88be79ac0f605c2193b782af89fd253987bbf61bdfd3d1a1a023ab7cc55e537bd80920fc5a1f5958366af8d5a175d331ba095a18d0735a36fb94f8
data/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2024-01-01
11
+
12
+ ### Added
13
+ - Initial release of the LangGraph Platform Ruby SDK
14
+ - Complete API client implementation for LangGraph Platform
15
+ - Support for all major resources:
16
+ - Assistants (create, read, update, delete, search)
17
+ - Threads (create, read, update, delete, state management)
18
+ - Runs (create, stream, wait, list, cancel)
19
+ - Crons (create, read, update, delete, enable/disable)
20
+ - Store (get, put, delete, list, batch operations)
21
+ - MCP (tools, resources, prompts, logging)
22
+ - Server-Sent Events (SSE) streaming support
23
+ - Comprehensive error handling with specific error classes
24
+ - Request validation and parameter sanitization
25
+ - Retry mechanism with exponential backoff
26
+ - Configuration management with environment variable support
27
+ - Full test suite with RSpec, VCR, and WebMock
28
+ - Documentation with YARD
29
+ - Usage examples for basic, streaming, and advanced workflows
30
+ - Ruby 2.7+ compatibility
31
+ - Thread-safe operations
32
+ - Zeitwerk autoloading
33
+
34
+ ### Dependencies
35
+ - faraday (~> 2.0) - HTTP client
36
+ - faraday-net_http (~> 3.0) - HTTP adapter
37
+ - multi_json (~> 1.15) - JSON parsing
38
+ - zeitwerk (~> 2.6) - Code loading
39
+
40
+ ### Development Dependencies
41
+ - rspec (~> 3.12) - Testing framework
42
+ - vcr (~> 6.0) - HTTP interaction recording
43
+ - webmock (~> 3.18) - HTTP request stubbing
44
+ - rubocop (~> 1.50) - Code linting
45
+ - yard (~> 0.9) - Documentation generation
46
+ - pry (~> 0.14) - Debugging
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Gys Muller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,406 @@
1
+ # LangGraph Platform Ruby SDK
2
+
3
+ An unoffical Ruby SDK for interacting with the [LangGraph Platform API](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html). This gem provides a Ruby-idiomatic interface for managing assistants, threads, runs, crons, store operations, and Model Context Protocol (MCP) interactions.
4
+
5
+ ## Current Status
6
+
7
+ This is a work in progress. The SDK is not yet complete and fully tested.
8
+
9
+ ## Roadmap
10
+
11
+ - [x] Assistants
12
+ - [x] Threads
13
+ - [x] Runs
14
+ - [ ] Crons
15
+ - [ ] Store Operations
16
+ - [ ] Model Context Protocol (MCP)
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'langgraph-platform'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ ```bash
29
+ $ bundle install
30
+ ```
31
+
32
+ Or install it yourself as:
33
+
34
+ ```bash
35
+ $ gem install langgraph-platform
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```ruby
41
+ require 'langgraph_platform'
42
+
43
+ # Initialize the client
44
+ client = LanggraphPlatform::Client.new(
45
+ api_key: 'your-api-key-here',
46
+ base_url: 'https://api.langchain.com' # For local development, use http://127.0.0.1:2024
47
+ )
48
+
49
+ # Create an assistant
50
+ assistant = client.assistants.create(
51
+ graph_id: 'my-graph',
52
+ name: 'My Assistant',
53
+ config: { recursion_limit: 10 }
54
+ )
55
+
56
+ # Create a thread
57
+ thread = client.threads.create(
58
+ metadata: { user_id: 'user123' }
59
+ )
60
+
61
+ # Run the assistant
62
+ run = client.runs.create(
63
+ thread.thread_id,
64
+ assistant_id: assistant.assistant_id,
65
+ input: { message: 'Hello, world!' }
66
+ )
67
+
68
+ puts "Run status: #{run.status}"
69
+ ```
70
+
71
+ ## Configuration
72
+
73
+ ### Environment Variables
74
+
75
+ The SDK can be configured using environment variables:
76
+
77
+ - `LANGGRAPH_API_KEY`: Your LangGraph Platform API key (use fake-api-key for local development)
78
+ - `LANGGRAPH_BASE_URL`: Custom base URL (defaults to `https://api.langchain.com`)
79
+
80
+ ### Client Configuration
81
+
82
+ ```ruby
83
+ client = LanggraphPlatform::Client.new(
84
+ api_key: 'your-api-key',
85
+ base_url: 'https://custom-api.example.com',
86
+ timeout: 30,
87
+ retries: 3
88
+ )
89
+
90
+ # Or configure after initialization
91
+ client.configure do |config|
92
+ config.timeout = 60
93
+ config.retries = 5
94
+ end
95
+ ```
96
+
97
+ ## Features
98
+
99
+ ### Assistants
100
+
101
+ ```ruby
102
+ # Create an assistant
103
+ assistant = client.assistants.create(
104
+ graph_id: 'my-graph',
105
+ name: 'My Assistant',
106
+ config: { recursion_limit: 10 },
107
+ metadata: { version: '1.0' }
108
+ )
109
+
110
+ # Find an assistant
111
+ assistant = client.assistants.find('assistant-id')
112
+
113
+ # Search assistants
114
+ assistants = client.assistants.search(
115
+ metadata: { version: '1.0' },
116
+ limit: 10
117
+ )
118
+
119
+ # Update an assistant
120
+ client.assistants.update('assistant-id', name: 'Updated Name')
121
+
122
+ # Delete an assistant
123
+ client.assistants.delete('assistant-id')
124
+ ```
125
+
126
+ ### Threads
127
+
128
+ ```ruby
129
+ # Create a thread
130
+ thread = client.threads.create(
131
+ metadata: { user_id: 'user123' }
132
+ )
133
+
134
+ # Get thread state
135
+ state = client.threads.state(thread.thread_id)
136
+
137
+ # Update thread state
138
+ client.threads.update_state(
139
+ thread.thread_id,
140
+ values: { key: 'value' }
141
+ )
142
+
143
+ # Get thread history
144
+ history = client.threads.history(thread.thread_id, limit: 10)
145
+
146
+ # Search threads
147
+ threads = client.threads.search(
148
+ status: 'idle',
149
+ limit: 10
150
+ )
151
+ ```
152
+
153
+ ### Runs
154
+
155
+ ```ruby
156
+ # Create a run
157
+ run = client.runs.create(
158
+ thread.thread_id,
159
+ assistant_id: assistant.assistant_id,
160
+ input: { message: 'Hello!' }
161
+ )
162
+
163
+ # Create a run with webhook
164
+ run = client.runs.create(
165
+ thread.thread_id,
166
+ assistant_id: assistant.assistant_id,
167
+ input: { message: 'Hello!' },
168
+ webhook: 'https://your-app.com/webhooks/langgraph'
169
+ )
170
+
171
+ # Stream a run
172
+ client.runs.stream(
173
+ thread.thread_id,
174
+ assistant_id: assistant.assistant_id,
175
+ input: { message: 'Hello!' }
176
+ ) do |type, data, id, reconnection_time|
177
+ puts "Event: #{type}, Data: #{data}"
178
+ end
179
+
180
+ # Stream a run with webhook
181
+ client.runs.stream(
182
+ thread.thread_id,
183
+ assistant_id: assistant.assistant_id,
184
+ input: { message: 'Hello!' },
185
+ webhook: 'https://your-app.com/webhooks/langgraph'
186
+ ) do |type, data, id, reconnection_time|
187
+ puts "Event: #{type}, Data: #{data}"
188
+ end
189
+
190
+ # Wait for a run to complete
191
+ result = client.runs.wait(
192
+ thread.thread_id,
193
+ assistant_id: assistant.assistant_id,
194
+ input: { message: 'Hello!' }
195
+ )
196
+
197
+ # Wait for a run with webhook
198
+ result = client.runs.wait(
199
+ thread.thread_id,
200
+ assistant_id: assistant.assistant_id,
201
+ input: { message: 'Hello!' },
202
+ webhook: 'https://your-app.com/webhooks/langgraph'
203
+ )
204
+
205
+ # List runs
206
+ runs = client.runs.list(thread.thread_id)
207
+
208
+ # Cancel a run
209
+ client.runs.cancel(thread.thread_id, run.run_id)
210
+ ```
211
+
212
+ ### Crons
213
+
214
+ **Under development and untested.**
215
+
216
+ ```ruby
217
+ # Create a cron job
218
+ cron = client.crons.create(
219
+ assistant_id: assistant.assistant_id,
220
+ schedule: '0 */6 * * *', # Every 6 hours
221
+ payload: { task: 'periodic_task' }
222
+ )
223
+
224
+ # List cron jobs
225
+ crons = client.crons.list(assistant_id: assistant.assistant_id)
226
+
227
+ # Enable/disable cron jobs
228
+ client.crons.enable(cron.cron_id)
229
+ client.crons.disable(cron.cron_id)
230
+ ```
231
+
232
+ ### Store Operations
233
+
234
+ **Under development and untested.**
235
+
236
+ ```ruby
237
+ # Store data
238
+ client.store.put('namespace', 'key', { data: 'value' })
239
+
240
+ # Retrieve data
241
+ item = client.store.get('namespace', 'key')
242
+
243
+ # List items in namespace
244
+ items = client.store.list('namespace', prefix: 'user_')
245
+
246
+ # Delete data
247
+ client.store.delete('namespace', 'key')
248
+
249
+ # Batch operations
250
+ client.store.batch_put([
251
+ { namespace: 'ns1', key: 'key1', value: 'value1' },
252
+ { namespace: 'ns1', key: 'key2', value: 'value2' }
253
+ ])
254
+ ```
255
+
256
+ ### Model Context Protocol (MCP)
257
+
258
+ **Under development and untested.**
259
+
260
+ ```ruby
261
+ # List available tools
262
+ tools = client.mcp.list_tools
263
+
264
+ # Call a tool
265
+ result = client.mcp.call_tool(
266
+ 'tool_name',
267
+ arguments: { param: 'value' }
268
+ )
269
+
270
+ # List resources
271
+ resources = client.mcp.list_resources
272
+
273
+ # Read a resource
274
+ content = client.mcp.read_resource('resource://example')
275
+
276
+ # Work with prompts
277
+ prompts = client.mcp.list_prompts
278
+ result = client.mcp.complete_prompt('prompt_name', arguments: {})
279
+ ```
280
+
281
+ ## Webhooks
282
+
283
+ The SDK supports webhooks that are called after LangGraph API calls complete. Webhooks can be used with any run creation method:
284
+
285
+ ```ruby
286
+ # Thread-based runs with webhooks
287
+ run = client.runs.create(
288
+ thread.thread_id,
289
+ assistant_id: assistant.assistant_id,
290
+ input: { message: 'Process this data' },
291
+ webhook: 'https://your-app.com/webhooks/langgraph'
292
+ )
293
+
294
+ # Stateless runs with webhooks
295
+ client.runs.create_stateless(
296
+ assistant_id: assistant.assistant_id,
297
+ input: { message: 'Process this data' },
298
+ webhook: 'https://your-app.com/webhooks/langgraph'
299
+ )
300
+
301
+ # Streaming runs with webhooks
302
+ client.runs.stream(
303
+ thread.thread_id,
304
+ assistant_id: assistant.assistant_id,
305
+ input: { message: 'Process this data' },
306
+ webhook: 'https://your-app.com/webhooks/langgraph'
307
+ ) do |type, data, id, reconnection_time|
308
+ # Handle streaming events
309
+ end
310
+ ```
311
+
312
+ **Webhook Requirements:**
313
+ - Must be a valid URI (up to 65,536 characters)
314
+ - Should handle HTTP POST requests from LangGraph Platform
315
+ - Called after the API call completes
316
+
317
+ ## Streaming
318
+
319
+ The SDK supports Server-Sent Events (SSE) streaming for real-time responses. Streaming blocks receive four parameters directly from the SSE parser:
320
+
321
+ - `type`: Event type (e.g., 'messages/partial', 'updates', 'values', 'error')
322
+ - `data`: Parsed JSON data or raw string content
323
+ - `id`: Event ID (optional)
324
+ - `reconnection_time`: SSE reconnection time in milliseconds (optional)
325
+
326
+ ```ruby
327
+ client.runs.stream(
328
+ thread.thread_id,
329
+ assistant_id: assistant.assistant_id,
330
+ input: { message: 'Tell me a story' }
331
+ ) do |type, data, id, reconnection_time|
332
+ case type
333
+ when 'data'
334
+ puts "Data: #{data}"
335
+ when 'message'
336
+ puts "Message: #{data}"
337
+ when 'error'
338
+ puts "Error: #{data}"
339
+ when 'end'
340
+ puts "Stream ended"
341
+ end
342
+ end
343
+ ```
344
+
345
+ ## Error Handling
346
+
347
+ The SDK provides specific error classes for different API errors:
348
+
349
+ ```ruby
350
+ begin
351
+ assistant = client.assistants.find('nonexistent-id')
352
+ rescue LanggraphPlatform::Errors::NotFoundError => e
353
+ puts "Assistant not found: #{e.message}"
354
+ rescue LanggraphPlatform::Errors::UnauthorizedError => e
355
+ puts "Authentication failed: #{e.message}"
356
+ rescue LanggraphPlatform::Errors::APIError => e
357
+ puts "API error: #{e.message}"
358
+ end
359
+ ```
360
+
361
+ Available error classes:
362
+ - `LanggraphPlatform::Errors::APIError` - Base error class
363
+ - `LanggraphPlatform::Errors::BadRequestError` - 400 errors
364
+ - `LanggraphPlatform::Errors::UnauthorizedError` - 401 errors
365
+ - `LanggraphPlatform::Errors::NotFoundError` - 404 errors
366
+ - `LanggraphPlatform::Errors::ConflictError` - 409 errors
367
+ - `LanggraphPlatform::Errors::ValidationError` - 422 errors
368
+ - `LanggraphPlatform::Errors::ServerError` - 500+ errors
369
+
370
+ ## Examples
371
+
372
+ Check out the `examples/` directory for more comprehensive examples:
373
+
374
+ - `examples/basic_usage.rb` - Basic CRUD operations
375
+ - `examples/streaming_example.rb` - Streaming functionality
376
+
377
+ ## Development
378
+
379
+ To run the tests:
380
+
381
+ ```bash
382
+ $ bundle exec rspec
383
+ ```
384
+
385
+ To run RuboCop:
386
+
387
+ ```bash
388
+ $ bundle exec rubocop
389
+ ```
390
+
391
+ ## Acknowledgments
392
+
393
+ - Thanks to the LangGraph team for their work on the SDKs and for providing the API reference.
394
+ - Thanks to the Shopify team for [event_stream_parser gem](https://github.com/shopify/event_stream_parser).
395
+
396
+ ## Contributing
397
+
398
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gysmuller/langgraph-platform.
399
+
400
+ ## License
401
+
402
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
403
+
404
+ ## Changelog
405
+
406
+ See [CHANGELOG.md](CHANGELOG.md) for a list of changes.
@@ -0,0 +1,146 @@
1
+ module LanggraphPlatform
2
+ class BaseClient
3
+ def initialize(configuration)
4
+ @configuration = configuration
5
+ @http_client = build_http_client
6
+ end
7
+
8
+ def get(path, params = {})
9
+ request(:get, path, params: params)
10
+ end
11
+
12
+ def post(path, body = {})
13
+ request(:post, path, body: body)
14
+ end
15
+
16
+ def patch(path, body = {})
17
+ request(:patch, path, body: body)
18
+ end
19
+
20
+ def put(path, body = {})
21
+ request(:put, path, body: body)
22
+ end
23
+
24
+ def delete(path)
25
+ request(:delete, path)
26
+ end
27
+
28
+ def stream(path, body = {}, &block)
29
+ response = @http_client.headers(
30
+ 'Accept' => 'text/event-stream',
31
+ 'Cache-Control' => 'no-cache'
32
+ ).timeout(@configuration.timeout).post("#{@configuration.base_url}#{path}", json: body)
33
+
34
+ if block_given?
35
+ stream_events(response, &block)
36
+ else
37
+ response
38
+ end
39
+ rescue HTTP::Error => e
40
+ raise Errors::APIError, "Stream request failed: #{e.message}"
41
+ end
42
+
43
+ private
44
+
45
+ def stream_events(response, &block)
46
+ parser = EventStreamParser::Parser.new
47
+
48
+ response.body.each do |chunk|
49
+ parser.feed(chunk) do |type, data, id, reconnection_time|
50
+ # Parse JSON data if present
51
+ parsed_data = parse_data_safely(data)
52
+
53
+ # Pass parameters directly to block
54
+ block.call(type, parsed_data, id, reconnection_time)
55
+ end
56
+ end
57
+ rescue StandardError => e
58
+ warn "Error processing SSE stream: #{e.message}" if @configuration.debug
59
+ raise
60
+ end
61
+
62
+ def parse_data_safely(data_string)
63
+ return data_string if data_string.nil? || data_string.empty?
64
+
65
+ MultiJson.load(data_string)
66
+ rescue MultiJson::ParseError
67
+ # Return as string if JSON parsing fails
68
+ data_string
69
+ end
70
+
71
+ def build_http_client
72
+ HTTP.headers(
73
+ 'Authorization' => "Bearer #{@configuration.api_key}",
74
+ 'User-Agent' => @configuration.user_agent,
75
+ 'Content-Type' => 'application/json'
76
+ ).timeout(@configuration.timeout)
77
+ end
78
+
79
+ def request(method, path, params: {}, body: {})
80
+ retries = 0
81
+
82
+ begin
83
+ url = "#{@configuration.base_url}#{path}"
84
+ response = case method
85
+ when :get
86
+ @http_client.get(url, params: params)
87
+ when :post
88
+ @http_client.post(url, json: body)
89
+ when :patch
90
+ @http_client.patch(url, json: body)
91
+ when :put
92
+ @http_client.put(url, json: body)
93
+ when :delete
94
+ @http_client.delete(url)
95
+ end
96
+
97
+ handle_response(response)
98
+ rescue HTTP::Error => e
99
+ retries += 1
100
+ unless retries <= @configuration.retries
101
+ raise Errors::APIError, "HTTP request failed after #{@configuration.retries} retries: #{e.message}"
102
+ end
103
+
104
+ sleep(2**retries)
105
+ retry
106
+ end
107
+ end
108
+
109
+ def handle_response(response) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
110
+ case response.status
111
+ when 200..299
112
+ # Handle empty responses (e.g., 204 No Content)
113
+ return nil if response.body.nil? || response.body.empty?
114
+
115
+ response.parse
116
+ when 400
117
+ raise Errors::BadRequestError, extract_error_message(response)
118
+ when 401
119
+ raise Errors::UnauthorizedError, extract_error_message(response)
120
+ when 404
121
+ raise Errors::NotFoundError, extract_error_message(response)
122
+ when 409
123
+ raise Errors::ConflictError, extract_error_message(response)
124
+ when 422
125
+ raise Errors::ValidationError, extract_error_message(response)
126
+ when 500..599
127
+ raise Errors::ServerError, extract_error_message(response)
128
+ else
129
+ raise Errors::APIError, "Unexpected response status: #{response.status}"
130
+ end
131
+ end
132
+
133
+ def extract_error_message(response)
134
+ body = response.parse
135
+ if body.is_a?(Hash) && body['error']
136
+ body['error']
137
+ elsif body.is_a?(Hash) && body['message']
138
+ body['message']
139
+ else
140
+ body.to_s
141
+ end
142
+ rescue StandardError
143
+ response.to_s
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,42 @@
1
+ module LanggraphPlatform
2
+ class Client
3
+ attr_reader :assistants, :threads, :runs, :crons, :store, :mcp, :configuration
4
+
5
+ def initialize(api_key: nil, base_url: nil, **options)
6
+ @configuration = Configuration.new(api_key: api_key, base_url: base_url)
7
+ @configuration.timeout = options[:timeout] if options[:timeout]
8
+ @configuration.retries = options[:retries] if options[:retries]
9
+ @configuration.user_agent = options[:user_agent] if options[:user_agent]
10
+
11
+ validate_configuration!
12
+
13
+ @http_client = BaseClient.new(@configuration)
14
+
15
+ initialize_resources
16
+ end
17
+
18
+ def configure
19
+ yield(@configuration) if block_given?
20
+ @http_client = BaseClient.new(@configuration)
21
+ initialize_resources
22
+ end
23
+
24
+ private
25
+
26
+ def validate_configuration!
27
+ return if @configuration.valid?
28
+
29
+ raise Errors::UnauthorizedError,
30
+ 'API key is required. Set it via the api_key parameter or LANGGRAPH_API_KEY environment variable.'
31
+ end
32
+
33
+ def initialize_resources
34
+ @assistants = Resources::Assistants.new(@http_client)
35
+ @threads = Resources::Threads.new(@http_client)
36
+ @runs = Resources::Runs.new(@http_client)
37
+ @crons = Resources::Crons.new(@http_client)
38
+ @store = Resources::Store.new(@http_client)
39
+ @mcp = Resources::Mcp.new(@http_client)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ module LanggraphPlatform
2
+ class Configuration
3
+ attr_accessor :api_key, :base_url, :timeout, :retries, :user_agent
4
+
5
+ def initialize(api_key: nil, base_url: nil)
6
+ @api_key = api_key || ENV.fetch('LANGGRAPH_API_KEY', nil)
7
+ @base_url = base_url || ENV['LANGGRAPH_BASE_URL'] || 'https://api.langchain.com'
8
+ @timeout = 30
9
+ @retries = 3
10
+ @user_agent = "langgraph-platform-ruby/#{VERSION}"
11
+ end
12
+
13
+ def valid?
14
+ !@api_key.nil? && !@api_key.empty?
15
+ end
16
+ end
17
+ end