conductor_ruby 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.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/LICENSE +190 -0
  4. data/README.md +517 -0
  5. data/examples/agentic_workflows/llm_chat.rb +106 -0
  6. data/examples/dynamic_workflow.rb +177 -0
  7. data/examples/event_handler.rb +94 -0
  8. data/examples/event_listener_examples.rb +430 -0
  9. data/examples/helloworld/greetings_worker.rb +24 -0
  10. data/examples/helloworld/helloworld.rb +99 -0
  11. data/examples/kitchensink.rb +213 -0
  12. data/examples/metadata_journey.rb +189 -0
  13. data/examples/metrics_example.rb +284 -0
  14. data/examples/new_dsl_demo.rb +141 -0
  15. data/examples/orkes/http_poll.rb +83 -0
  16. data/examples/orkes/secrets_example.rb +69 -0
  17. data/examples/orkes/wait_for_webhook.rb +90 -0
  18. data/examples/prompt_journey.rb +245 -0
  19. data/examples/rag_workflow.rb +167 -0
  20. data/examples/schedule_journey.rb +244 -0
  21. data/examples/simple_worker.rb +125 -0
  22. data/examples/simple_workflow.rb +89 -0
  23. data/examples/task_context_example.rb +257 -0
  24. data/examples/task_listener_example.rb +192 -0
  25. data/examples/worker_configuration_example.rb +282 -0
  26. data/examples/workflow_dsl.rb +316 -0
  27. data/examples/workflow_ops.rb +305 -0
  28. data/lib/conductor/client/authorization_client.rb +238 -0
  29. data/lib/conductor/client/integration_client.rb +108 -0
  30. data/lib/conductor/client/metadata_client.rb +139 -0
  31. data/lib/conductor/client/prompt_client.rb +58 -0
  32. data/lib/conductor/client/scheduler_client.rb +132 -0
  33. data/lib/conductor/client/schema_client.rb +32 -0
  34. data/lib/conductor/client/secret_client.rb +48 -0
  35. data/lib/conductor/client/task_client.rb +168 -0
  36. data/lib/conductor/client/workflow_client.rb +242 -0
  37. data/lib/conductor/configuration/authentication_settings.rb +17 -0
  38. data/lib/conductor/configuration.rb +103 -0
  39. data/lib/conductor/exceptions.rb +86 -0
  40. data/lib/conductor/http/api/application_resource_api.rb +107 -0
  41. data/lib/conductor/http/api/authorization_resource_api.rb +56 -0
  42. data/lib/conductor/http/api/event_resource_api.rb +133 -0
  43. data/lib/conductor/http/api/gateway_auth_resource_api.rb +48 -0
  44. data/lib/conductor/http/api/group_resource_api.rb +76 -0
  45. data/lib/conductor/http/api/integration_resource_api.rb +145 -0
  46. data/lib/conductor/http/api/metadata_resource_api.rb +231 -0
  47. data/lib/conductor/http/api/prompt_resource_api.rb +81 -0
  48. data/lib/conductor/http/api/role_resource_api.rb +60 -0
  49. data/lib/conductor/http/api/scheduler_resource_api.rb +211 -0
  50. data/lib/conductor/http/api/schema_resource_api.rb +82 -0
  51. data/lib/conductor/http/api/secret_resource_api.rb +134 -0
  52. data/lib/conductor/http/api/task_resource_api.rb +321 -0
  53. data/lib/conductor/http/api/token_resource_api.rb +42 -0
  54. data/lib/conductor/http/api/user_resource_api.rb +59 -0
  55. data/lib/conductor/http/api/workflow_bulk_resource_api.rb +91 -0
  56. data/lib/conductor/http/api/workflow_resource_api.rb +451 -0
  57. data/lib/conductor/http/api_client.rb +437 -0
  58. data/lib/conductor/http/models/authentication_config.rb +67 -0
  59. data/lib/conductor/http/models/authorization_request.rb +39 -0
  60. data/lib/conductor/http/models/base_model.rb +162 -0
  61. data/lib/conductor/http/models/bulk_response.rb +39 -0
  62. data/lib/conductor/http/models/conductor_application.rb +39 -0
  63. data/lib/conductor/http/models/conductor_user.rb +53 -0
  64. data/lib/conductor/http/models/create_or_update_application_request.rb +24 -0
  65. data/lib/conductor/http/models/create_or_update_role_request.rb +27 -0
  66. data/lib/conductor/http/models/event_handler.rb +130 -0
  67. data/lib/conductor/http/models/generate_token_request.rb +27 -0
  68. data/lib/conductor/http/models/group.rb +36 -0
  69. data/lib/conductor/http/models/integration.rb +70 -0
  70. data/lib/conductor/http/models/integration_api.rb +53 -0
  71. data/lib/conductor/http/models/integration_api_update.rb +43 -0
  72. data/lib/conductor/http/models/integration_update.rb +36 -0
  73. data/lib/conductor/http/models/permission.rb +24 -0
  74. data/lib/conductor/http/models/poll_data.rb +33 -0
  75. data/lib/conductor/http/models/prompt_template.rb +59 -0
  76. data/lib/conductor/http/models/prompt_template_test_request.rb +43 -0
  77. data/lib/conductor/http/models/rerun_workflow_request.rb +37 -0
  78. data/lib/conductor/http/models/role.rb +27 -0
  79. data/lib/conductor/http/models/schema_def.rb +59 -0
  80. data/lib/conductor/http/models/search_result.rb +187 -0
  81. data/lib/conductor/http/models/skip_task_request.rb +27 -0
  82. data/lib/conductor/http/models/start_workflow_request.rb +68 -0
  83. data/lib/conductor/http/models/subject_ref.rb +35 -0
  84. data/lib/conductor/http/models/tag_object.rb +36 -0
  85. data/lib/conductor/http/models/target_ref.rb +39 -0
  86. data/lib/conductor/http/models/task.rb +156 -0
  87. data/lib/conductor/http/models/task_def.rb +95 -0
  88. data/lib/conductor/http/models/task_exec_log.rb +30 -0
  89. data/lib/conductor/http/models/task_result.rb +115 -0
  90. data/lib/conductor/http/models/task_result_status.rb +24 -0
  91. data/lib/conductor/http/models/token.rb +33 -0
  92. data/lib/conductor/http/models/upsert_group_request.rb +30 -0
  93. data/lib/conductor/http/models/upsert_user_request.rb +39 -0
  94. data/lib/conductor/http/models/workflow.rb +202 -0
  95. data/lib/conductor/http/models/workflow_def.rb +73 -0
  96. data/lib/conductor/http/models/workflow_schedule.rb +100 -0
  97. data/lib/conductor/http/models/workflow_state_update.rb +30 -0
  98. data/lib/conductor/http/models/workflow_status_constants.rb +57 -0
  99. data/lib/conductor/http/models/workflow_task.rb +169 -0
  100. data/lib/conductor/http/models/workflow_test_request.rb +67 -0
  101. data/lib/conductor/http/rest_client.rb +211 -0
  102. data/lib/conductor/orkes/models/access_key.rb +56 -0
  103. data/lib/conductor/orkes/models/granted_permission.rb +27 -0
  104. data/lib/conductor/orkes/models/metadata_tag.rb +15 -0
  105. data/lib/conductor/orkes/models/rate_limit_tag.rb +15 -0
  106. data/lib/conductor/orkes/orkes_clients.rb +69 -0
  107. data/lib/conductor/version.rb +5 -0
  108. data/lib/conductor/worker/events/conductor_event.rb +40 -0
  109. data/lib/conductor/worker/events/global_dispatcher.rb +37 -0
  110. data/lib/conductor/worker/events/http_events.rb +25 -0
  111. data/lib/conductor/worker/events/listener_registry.rb +40 -0
  112. data/lib/conductor/worker/events/listeners.rb +34 -0
  113. data/lib/conductor/worker/events/sync_event_dispatcher.rb +78 -0
  114. data/lib/conductor/worker/events/task_runner_events.rb +271 -0
  115. data/lib/conductor/worker/events/workflow_events.rb +49 -0
  116. data/lib/conductor/worker/fiber_executor.rb +532 -0
  117. data/lib/conductor/worker/ractor_task_runner.rb +501 -0
  118. data/lib/conductor/worker/task_context.rb +114 -0
  119. data/lib/conductor/worker/task_definition_registrar.rb +322 -0
  120. data/lib/conductor/worker/task_handler.rb +360 -0
  121. data/lib/conductor/worker/task_in_progress.rb +60 -0
  122. data/lib/conductor/worker/task_runner.rb +538 -0
  123. data/lib/conductor/worker/telemetry/metrics_collector.rb +196 -0
  124. data/lib/conductor/worker/telemetry/prometheus_backend.rb +224 -0
  125. data/lib/conductor/worker/worker.rb +355 -0
  126. data/lib/conductor/worker/worker_config.rb +154 -0
  127. data/lib/conductor/worker/worker_registry.rb +71 -0
  128. data/lib/conductor/workflow/dsl/input_ref.rb +37 -0
  129. data/lib/conductor/workflow/dsl/output_ref.rb +44 -0
  130. data/lib/conductor/workflow/dsl/parallel_builder.rb +49 -0
  131. data/lib/conductor/workflow/dsl/switch_builder.rb +74 -0
  132. data/lib/conductor/workflow/dsl/task_ref.rb +178 -0
  133. data/lib/conductor/workflow/dsl/workflow_builder.rb +1016 -0
  134. data/lib/conductor/workflow/dsl/workflow_definition.rb +150 -0
  135. data/lib/conductor/workflow/llm/chat_message.rb +47 -0
  136. data/lib/conductor/workflow/llm/embedding_model.rb +19 -0
  137. data/lib/conductor/workflow/llm/tool_call.rb +43 -0
  138. data/lib/conductor/workflow/llm/tool_spec.rb +46 -0
  139. data/lib/conductor/workflow/task_type.rb +68 -0
  140. data/lib/conductor/workflow/timeout_policy.rb +31 -0
  141. data/lib/conductor/workflow/workflow_executor.rb +373 -0
  142. data/lib/conductor.rb +192 -0
  143. metadata +359 -0
data/README.md ADDED
@@ -0,0 +1,517 @@
1
+ # Conductor Ruby SDK
2
+
3
+ Official Ruby SDK for [Conductor OSS](https://github.com/conductor-oss/conductor) - a durable workflow orchestration engine.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/conductor_ruby.svg)](https://badge.fury.io/rb/conductor_ruby)
6
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
7
+
8
+ ## Features
9
+
10
+ - **Full Feature Parity** with Python SDK
11
+ - **Ruby-Idiomatic Workflow DSL** - Clean block-based syntax with 25+ task types
12
+ - **Worker Framework** - Multi-threaded task execution with class-based and block-based workers
13
+ - **LLM/AI Tasks** - Chat completion, embeddings, RAG, image/audio generation
14
+ - **Orkes Cloud Support** - Authentication, secrets, integrations, prompts
15
+ - **Comprehensive Testing** - 400+ unit tests, 110 integration tests
16
+
17
+ ## Installation
18
+
19
+ Add to your Gemfile:
20
+
21
+ ```ruby
22
+ gem 'conductor_ruby'
23
+ ```
24
+
25
+ Or install directly:
26
+
27
+ ```bash
28
+ gem install conductor_ruby
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### Hello World
34
+
35
+ ```ruby
36
+ require 'conductor'
37
+
38
+ # Configuration (reads CONDUCTOR_SERVER_URL from environment)
39
+ config = Conductor::Configuration.new
40
+
41
+ # Create clients
42
+ clients = Conductor::Orkes::OrkesClients.new(config)
43
+ executor = clients.get_workflow_executor
44
+
45
+ # Define a worker
46
+ class GreetWorker
47
+ include Conductor::Worker::WorkerModule
48
+ worker_task 'greet'
49
+
50
+ def execute(task)
51
+ name = get_input(task, 'name', 'World')
52
+ { 'result' => "Hello, #{name}!" }
53
+ end
54
+ end
55
+
56
+ # Build workflow using new DSL
57
+ workflow = Conductor.workflow :greetings, version: 1, executor: executor do
58
+ greet = simple :greet, name: wf[:name]
59
+ output result: greet[:result]
60
+ end
61
+
62
+ # Register and execute
63
+ workflow.register(overwrite: true)
64
+
65
+ # Start workers
66
+ runner = Conductor::Worker::TaskRunner.new(config)
67
+ runner.register_worker(GreetWorker.new)
68
+ runner.start
69
+
70
+ # Execute workflow
71
+ result = workflow.execute(input: { 'name' => 'Ruby' }, wait_for_seconds: 30)
72
+ puts "Result: #{result.output['result']}" # => "Hello, Ruby!"
73
+
74
+ runner.stop
75
+ ```
76
+
77
+ ## Workflow DSL
78
+
79
+ The SDK provides a clean, Ruby-idiomatic DSL for building workflows:
80
+
81
+ ```ruby
82
+ workflow = Conductor.workflow :order_processing, version: 1, executor: executor do
83
+ # Access workflow inputs with wf[:param]
84
+ user = simple :get_user, user_id: wf[:user_id]
85
+
86
+ # Reference task outputs with task[:field]
87
+ order = simple :validate_order, email: user[:email]
88
+
89
+ # HTTP calls
90
+ http :call_api, url: 'https://api.example.com', method: :post, body: { id: order[:id] }
91
+
92
+ # Parallel execution
93
+ parallel do
94
+ simple :ship_order, order_id: order[:id]
95
+ simple :send_confirmation, email: user[:email]
96
+ end
97
+
98
+ # Conditional branching
99
+ decide order[:region] do
100
+ on 'US' do
101
+ simple :us_shipping
102
+ end
103
+ on 'EU' do
104
+ simple :eu_shipping
105
+ end
106
+ otherwise do
107
+ terminate :failed, 'Unsupported region'
108
+ end
109
+ end
110
+
111
+ # Set workflow output
112
+ output tracking: order[:tracking_number], status: 'completed'
113
+ end
114
+
115
+ # Register and execute
116
+ workflow.register(overwrite: true)
117
+ result = workflow.execute(input: { user_id: 123 }, wait_for_seconds: 60)
118
+ ```
119
+
120
+ ### Task Methods Reference
121
+
122
+ #### Basic Tasks
123
+
124
+ ```ruby
125
+ # Simple task (worker execution)
126
+ result = simple :task_name, input1: 'value', input2: wf[:param]
127
+
128
+ # Inline code execution
129
+ jq :transform, query: '.items | map(.name)', input: previous[:data]
130
+ javascript :compute, script: 'return inputs.a + inputs.b', a: 1, b: 2
131
+
132
+ # Set workflow variables
133
+ set_variable :save_state, user_id: user[:id], status: 'active'
134
+
135
+ # Human/manual task
136
+ human :approval, display_name: 'Manager Approval', form_template: 'approval_form'
137
+ ```
138
+
139
+ #### HTTP Tasks
140
+
141
+ ```ruby
142
+ # HTTP request
143
+ http :call_api,
144
+ url: 'https://api.example.com/users',
145
+ method: :post,
146
+ headers: { 'Authorization' => 'Bearer ${workflow.secrets.api_token}' },
147
+ body: { name: wf[:name], email: wf[:email] }
148
+
149
+ # HTTP polling (wait for condition)
150
+ http_poll :wait_for_ready,
151
+ url: 'https://api.example.com/status/${workflow.input.job_id}',
152
+ method: :get,
153
+ termination_condition: '$.status == "ready"',
154
+ polling_interval: 5,
155
+ polling_strategy: :fixed
156
+ ```
157
+
158
+ #### Control Flow
159
+
160
+ ```ruby
161
+ # Parallel execution (fork/join)
162
+ parallel do
163
+ simple :branch_a
164
+ simple :branch_b
165
+ simple :branch_c
166
+ end
167
+
168
+ # Conditional branching
169
+ decide order[:status] do
170
+ on 'pending' do
171
+ simple :process_pending
172
+ end
173
+ on 'approved' do
174
+ simple :process_approved
175
+ end
176
+ otherwise do
177
+ simple :handle_unknown
178
+ end
179
+ end
180
+
181
+ # Conditional shortcuts
182
+ when_true user[:is_premium] do
183
+ simple :apply_discount
184
+ end
185
+
186
+ when_false order[:validated] do
187
+ terminate :failed, 'Order validation failed'
188
+ end
189
+
190
+ # Loop over items
191
+ loop_over users[:list], as: :user do
192
+ simple :process_user, user_id: iteration[:user][:id]
193
+ end
194
+
195
+ # Do-while loop
196
+ do_while :retry_loop, condition: '${retry_ref.output.success} == false' do
197
+ simple :retry_operation
198
+ end
199
+ ```
200
+
201
+ #### Sub-workflows
202
+
203
+ ```ruby
204
+ # Call another workflow
205
+ sub_workflow :process_order,
206
+ workflow_name: 'order_processor',
207
+ version: 2,
208
+ input: { order_id: wf[:order_id] }
209
+
210
+ # Start workflow (fire-and-forget)
211
+ start_workflow :trigger_notification,
212
+ workflow_name: 'send_notifications',
213
+ input: { user_id: user[:id] }
214
+
215
+ # Inline sub-workflow definition
216
+ inline_workflow :nested_process do
217
+ simple :step1
218
+ simple :step2
219
+ end
220
+ ```
221
+
222
+ #### Wait and Events
223
+
224
+ ```ruby
225
+ # Wait for duration
226
+ wait :pause, duration: '30s' # or '5m', '1h', '2d'
227
+
228
+ # Wait until specific time
229
+ wait :scheduled, until: '2024-12-25T00:00:00Z'
230
+
231
+ # Wait for external webhook
232
+ wait_for_webhook :external_callback,
233
+ matches: { 'type' => 'payment', 'order_id' => '${workflow.input.order_id}' }
234
+
235
+ # Publish event
236
+ event :notify, sink: 'conductor:workflow_events', payload: { status: 'completed' }
237
+ ```
238
+
239
+ #### Termination
240
+
241
+ ```ruby
242
+ # Complete workflow
243
+ terminate :success, 'Processing completed successfully'
244
+
245
+ # Fail workflow
246
+ terminate :failed, 'Validation error: missing required field'
247
+ ```
248
+
249
+ #### Dynamic Tasks
250
+
251
+ ```ruby
252
+ # Dynamic task name (resolved at runtime)
253
+ dynamic :run_handler, task_to_execute: wf[:handler_name]
254
+
255
+ # Dynamic fork (parallel tasks determined at runtime)
256
+ dynamic_fork :process_all,
257
+ tasks_input: wf[:items],
258
+ task_name: 'process_item'
259
+ ```
260
+
261
+ ### LLM/AI Tasks
262
+
263
+ ```ruby
264
+ workflow = Conductor.workflow :ai_assistant, executor: executor do
265
+ # Chat completion (messages auto-converted from simple format)
266
+ response = llm_chat :chat,
267
+ provider: 'openai',
268
+ model: 'gpt-4',
269
+ messages: [
270
+ { role: :system, message: 'You are a helpful assistant.' },
271
+ { role: :user, message: wf[:question] }
272
+ ],
273
+ temperature: 0.7
274
+
275
+ # Text completion
276
+ llm_text :complete,
277
+ provider: 'anthropic',
278
+ model: 'claude-3-sonnet',
279
+ prompt: 'Summarize: ${workflow.input.text}'
280
+
281
+ # Generate embeddings
282
+ embeddings = llm_embeddings :embed,
283
+ provider: 'openai',
284
+ model: 'text-embedding-3-small',
285
+ text: wf[:document]
286
+
287
+ # Store embeddings in vector DB
288
+ llm_store_embeddings :store,
289
+ provider: 'pinecone',
290
+ index: 'documents',
291
+ embeddings: embeddings[:embeddings],
292
+ metadata: { doc_id: wf[:doc_id] }
293
+
294
+ # Search embeddings
295
+ llm_search_embeddings :search,
296
+ provider: 'pinecone',
297
+ index: 'documents',
298
+ query: wf[:search_query],
299
+ max_results: 10
300
+
301
+ # Generate image
302
+ generate_image :create_image,
303
+ provider: 'openai',
304
+ model: 'dall-e-3',
305
+ prompt: 'A sunset over mountains',
306
+ size: '1024x1024'
307
+
308
+ # Generate audio (text-to-speech)
309
+ generate_audio :speak,
310
+ provider: 'openai',
311
+ model: 'tts-1',
312
+ text: response[:content],
313
+ voice: 'nova'
314
+
315
+ # MCP (Model Context Protocol) integration
316
+ tools = list_mcp_tools :get_tools, server_name: 'my_mcp_server'
317
+
318
+ call_mcp_tool :use_tool,
319
+ server_name: 'my_mcp_server',
320
+ tool_name: 'search_documents',
321
+ arguments: { query: wf[:query] }
322
+
323
+ output answer: response[:content]
324
+ end
325
+ ```
326
+
327
+ ### Output References
328
+
329
+ The DSL uses a clean syntax for referencing outputs:
330
+
331
+ ```ruby
332
+ # Workflow input reference
333
+ wf[:user_id] # => '${workflow.input.user_id}'
334
+
335
+ # Task output reference
336
+ task[:field] # => '${task_ref.output.field}'
337
+ task[:nested][:path] # => '${task_ref.output.nested.path}'
338
+
339
+ # Loop iteration references (inside loop_over)
340
+ iteration[:current_item] # Current item being processed
341
+ iteration[:index] # Current index (0-based)
342
+ iteration[:user][:name] # If `as: :user` specified
343
+ ```
344
+
345
+ ## Examples
346
+
347
+ The `examples/` directory contains comprehensive examples:
348
+
349
+ | Example | Description |
350
+ |---------|-------------|
351
+ | [`helloworld/`](examples/helloworld/) | Simplest complete example - worker + workflow + execution |
352
+ | [`workflow_dsl.rb`](examples/workflow_dsl.rb) | Comprehensive new DSL showcase |
353
+ | [`simple_worker.rb`](examples/simple_worker.rb) | Worker patterns: class-based, block-based, error handling |
354
+ | [`kitchensink.rb`](examples/kitchensink.rb) | All major task types using new DSL |
355
+ | [`dynamic_workflow.rb`](examples/dynamic_workflow.rb) | Create and execute workflows at runtime |
356
+ | [`workflow_ops.rb`](examples/workflow_ops.rb) | Lifecycle operations: pause, resume, restart, retry |
357
+ | [`agentic_workflows/`](examples/agentic_workflows/) | LLM chat and AI workflow examples |
358
+
359
+ Run examples:
360
+
361
+ ```bash
362
+ # Set environment variables
363
+ export CONDUCTOR_SERVER_URL=http://localhost:8080/api
364
+ # For Orkes Cloud:
365
+ # export CONDUCTOR_AUTH_KEY=your_key
366
+ # export CONDUCTOR_AUTH_SECRET=your_secret
367
+
368
+ # Run hello world
369
+ cd examples/helloworld && bundle exec ruby helloworld.rb
370
+
371
+ # Run DSL showcase
372
+ bundle exec ruby examples/workflow_dsl.rb
373
+
374
+ # Run kitchen sink
375
+ bundle exec ruby examples/kitchensink.rb
376
+ ```
377
+
378
+ ## Worker Framework
379
+
380
+ ### Class-Based Workers
381
+
382
+ ```ruby
383
+ class ImageProcessor
384
+ include Conductor::Worker::WorkerModule
385
+
386
+ worker_task 'process_image', poll_interval: 1, thread_count: 4
387
+
388
+ def execute(task)
389
+ url = get_input(task, 'image_url')
390
+ # Process image...
391
+
392
+ result = Conductor::Http::Models::TaskResult.complete
393
+ result.add_output_data('processed_url', processed_url)
394
+ result.log('Image processed successfully')
395
+ result
396
+ end
397
+ end
398
+ ```
399
+
400
+ ### Block-Based Workers
401
+
402
+ ```ruby
403
+ worker = Conductor::Worker.define('simple_task') do |task|
404
+ input = task.input_data['value']
405
+ { result: input * 2 } # Return hash for automatic TaskResult
406
+ end
407
+ ```
408
+
409
+ ### Running Workers
410
+
411
+ ```ruby
412
+ runner = Conductor::Worker::TaskRunner.new(config)
413
+ runner.register_worker(ImageProcessor.new)
414
+ runner.register_worker(worker)
415
+ runner.start(threads: 4)
416
+
417
+ # Graceful shutdown
418
+ trap('INT') { runner.stop }
419
+ sleep while runner.running?
420
+ ```
421
+
422
+ ## Configuration
423
+
424
+ ### Environment Variables
425
+
426
+ ```bash
427
+ export CONDUCTOR_SERVER_URL=http://localhost:8080/api
428
+ export CONDUCTOR_AUTH_KEY=your_key # For Orkes Cloud
429
+ export CONDUCTOR_AUTH_SECRET=your_secret # For Orkes Cloud
430
+ ```
431
+
432
+ ### Programmatic
433
+
434
+ ```ruby
435
+ config = Conductor::Configuration.new(
436
+ server_api_url: 'https://play.orkes.io/api',
437
+ auth_key: 'your_key',
438
+ auth_secret: 'your_secret',
439
+ auth_token_ttl_min: 45,
440
+ verify_ssl: true
441
+ )
442
+ ```
443
+
444
+ ## API Coverage
445
+
446
+ ### Resource APIs (17 classes)
447
+
448
+ | API | Description |
449
+ |-----|-------------|
450
+ | WorkflowResourceApi | Workflow execution and management |
451
+ | TaskResourceApi | Task polling and updates |
452
+ | MetadataResourceApi | Workflow/task definitions |
453
+ | SchedulerResourceApi | Scheduled workflows |
454
+ | EventResourceApi | Event handlers |
455
+ | WorkflowBulkResourceApi | Bulk operations |
456
+ | PromptResourceApi | AI prompt templates |
457
+ | SecretResourceApi | Secret management |
458
+ | IntegrationResourceApi | External integrations |
459
+ | + 8 more | Authorization, Users, Groups, Roles, etc. |
460
+
461
+ ### High-Level Clients (9 classes)
462
+
463
+ ```ruby
464
+ clients = Conductor::Orkes::OrkesClients.new(config)
465
+
466
+ workflow_client = clients.get_workflow_client
467
+ task_client = clients.get_task_client
468
+ metadata_client = clients.get_metadata_client
469
+ scheduler_client = clients.get_scheduler_client
470
+ prompt_client = clients.get_prompt_client
471
+ secret_client = clients.get_secret_client
472
+ authorization_client = clients.get_authorization_client
473
+ workflow_executor = clients.get_workflow_executor
474
+ ```
475
+
476
+ ## Testing
477
+
478
+ ```bash
479
+ # Unit tests
480
+ bundle exec rspec spec/conductor/
481
+
482
+ # Integration tests (requires Conductor server)
483
+ CONDUCTOR_SERVER_URL=http://localhost:8080/api bundle exec rspec spec/integration/
484
+ ```
485
+
486
+ ## Requirements
487
+
488
+ - Ruby 2.6+ (Ruby 3+ recommended)
489
+ - Conductor OSS 3.x or Orkes Cloud
490
+
491
+ ## Dependencies
492
+
493
+ - `faraday ~> 2.0` - HTTP client
494
+ - `faraday-net_http_persistent ~> 2.0` - Connection pooling
495
+ - `faraday-retry ~> 2.0` - Automatic retries
496
+ - `concurrent-ruby ~> 1.2` - Thread-safe concurrency
497
+
498
+ ## Contributing
499
+
500
+ 1. Fork the repository
501
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
502
+ 3. Run tests (`bundle exec rspec`)
503
+ 4. Commit your changes (`git commit -m 'Add amazing feature'`)
504
+ 5. Push to the branch (`git push origin feature/amazing-feature`)
505
+ 6. Open a Pull Request
506
+
507
+ ## License
508
+
509
+ Apache 2.0 - see [LICENSE](LICENSE) for details.
510
+
511
+ ## Links
512
+
513
+ - [Conductor OSS](https://github.com/conductor-oss/conductor)
514
+ - [Orkes Cloud](https://orkes.io)
515
+ - [Documentation](https://conductor-oss.org)
516
+ - [Python SDK](https://github.com/conductor-sdk/conductor-python)
517
+ - [Community Slack](https://join.slack.com/t/orkes-conductor/shared_invite/zt-2vdbx239s-Eacdyqya9giNLHfrCavfaA)
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # LLM Chat Workflow Example
5
+ #
6
+ # Demonstrates building a conversational AI workflow using the new Ruby DSL
7
+ # with LLM Chat Complete task and message history.
8
+ #
9
+ # New DSL Features Shown:
10
+ # - llm_chat() method with automatic message conversion
11
+ # - Pass messages as simple hashes instead of ChatMessage objects
12
+ #
13
+ # Usage:
14
+ # bundle exec ruby examples/agentic_workflows/llm_chat.rb
15
+
16
+ require_relative '../../lib/conductor'
17
+
18
+ LLM_PROVIDER = ENV.fetch('LLM_PROVIDER', 'openai')
19
+ LLM_MODEL = ENV.fetch('LLM_MODEL', 'gpt-4o-mini')
20
+
21
+ def create_chat_workflow(executor)
22
+ # Create workflow using the new Ruby-idiomatic DSL
23
+ Conductor.workflow :llm_chat_ruby, version: 1, executor: executor do
24
+ description 'Simple LLM chat workflow using new DSL'
25
+
26
+ # Chat completion task with messages as hashes (auto-converted)
27
+ # The DSL automatically converts hash messages to ChatMessage objects
28
+ chat = llm_chat :chat,
29
+ provider: LLM_PROVIDER,
30
+ model: LLM_MODEL,
31
+ messages: [
32
+ {
33
+ role: :system,
34
+ message: <<~MSG
35
+ You are a helpful assistant specializing in software development.
36
+ You provide clear, concise answers with code examples when appropriate.
37
+ Keep responses focused and practical.
38
+ MSG
39
+ },
40
+ {
41
+ role: :user,
42
+ message: '${workflow.input.user_message}'
43
+ }
44
+ ],
45
+ temperature: 0.7,
46
+ max_tokens: 1024
47
+
48
+ # Set workflow output to the chat response
49
+ output response: chat[:result]
50
+ end
51
+ end
52
+
53
+ def main
54
+ config = Conductor::Configuration.new
55
+
56
+ puts '=' * 70
57
+ puts 'LLM Chat Workflow Example (New DSL)'
58
+ puts '=' * 70
59
+ puts "Server: #{config.server_url}"
60
+ puts "LLM: #{LLM_PROVIDER}/#{LLM_MODEL}"
61
+ puts
62
+
63
+ clients = Conductor::Orkes::OrkesClients.new(config)
64
+ workflow_executor = clients.get_workflow_executor
65
+
66
+ # Create workflow with executor (required for register/execute)
67
+ workflow = create_chat_workflow(workflow_executor)
68
+
69
+ # Register workflow
70
+ workflow.register(overwrite: true)
71
+ puts "Registered workflow: #{workflow.name}"
72
+
73
+ # Test questions
74
+ questions = [
75
+ 'What is the difference between a Hash and an Array in Ruby?',
76
+ 'How do I handle exceptions in Ruby?',
77
+ 'Explain Ruby blocks in one sentence.'
78
+ ]
79
+
80
+ questions.each_with_index do |question, i|
81
+ puts "\n--- Question #{i + 1} ---"
82
+ puts "Q: #{question}"
83
+
84
+ # Execute using the workflow's execute method
85
+ result = workflow.execute(
86
+ input: { 'user_message' => question },
87
+ wait_for_seconds: 30
88
+ )
89
+
90
+ puts "A: #{result.output['response']}"
91
+ end
92
+
93
+ puts "\n#{'=' * 70}"
94
+ puts 'Chat workflow demo complete!'
95
+ end
96
+
97
+ if __FILE__ == $PROGRAM_NAME
98
+ begin
99
+ main
100
+ rescue Conductor::ApiError => e
101
+ puts "API Error: #{e.message}"
102
+ rescue StandardError => e
103
+ puts "Error: #{e.message}"
104
+ puts e.backtrace.first(3).join("\n")
105
+ end
106
+ end