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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +142 -0
- data/LICENSE +190 -0
- data/README.md +517 -0
- data/examples/agentic_workflows/llm_chat.rb +106 -0
- data/examples/dynamic_workflow.rb +177 -0
- data/examples/event_handler.rb +94 -0
- data/examples/event_listener_examples.rb +430 -0
- data/examples/helloworld/greetings_worker.rb +24 -0
- data/examples/helloworld/helloworld.rb +99 -0
- data/examples/kitchensink.rb +213 -0
- data/examples/metadata_journey.rb +189 -0
- data/examples/metrics_example.rb +284 -0
- data/examples/new_dsl_demo.rb +141 -0
- data/examples/orkes/http_poll.rb +83 -0
- data/examples/orkes/secrets_example.rb +69 -0
- data/examples/orkes/wait_for_webhook.rb +90 -0
- data/examples/prompt_journey.rb +245 -0
- data/examples/rag_workflow.rb +167 -0
- data/examples/schedule_journey.rb +244 -0
- data/examples/simple_worker.rb +125 -0
- data/examples/simple_workflow.rb +89 -0
- data/examples/task_context_example.rb +257 -0
- data/examples/task_listener_example.rb +192 -0
- data/examples/worker_configuration_example.rb +282 -0
- data/examples/workflow_dsl.rb +316 -0
- data/examples/workflow_ops.rb +305 -0
- data/lib/conductor/client/authorization_client.rb +238 -0
- data/lib/conductor/client/integration_client.rb +108 -0
- data/lib/conductor/client/metadata_client.rb +139 -0
- data/lib/conductor/client/prompt_client.rb +58 -0
- data/lib/conductor/client/scheduler_client.rb +132 -0
- data/lib/conductor/client/schema_client.rb +32 -0
- data/lib/conductor/client/secret_client.rb +48 -0
- data/lib/conductor/client/task_client.rb +168 -0
- data/lib/conductor/client/workflow_client.rb +242 -0
- data/lib/conductor/configuration/authentication_settings.rb +17 -0
- data/lib/conductor/configuration.rb +103 -0
- data/lib/conductor/exceptions.rb +86 -0
- data/lib/conductor/http/api/application_resource_api.rb +107 -0
- data/lib/conductor/http/api/authorization_resource_api.rb +56 -0
- data/lib/conductor/http/api/event_resource_api.rb +133 -0
- data/lib/conductor/http/api/gateway_auth_resource_api.rb +48 -0
- data/lib/conductor/http/api/group_resource_api.rb +76 -0
- data/lib/conductor/http/api/integration_resource_api.rb +145 -0
- data/lib/conductor/http/api/metadata_resource_api.rb +231 -0
- data/lib/conductor/http/api/prompt_resource_api.rb +81 -0
- data/lib/conductor/http/api/role_resource_api.rb +60 -0
- data/lib/conductor/http/api/scheduler_resource_api.rb +211 -0
- data/lib/conductor/http/api/schema_resource_api.rb +82 -0
- data/lib/conductor/http/api/secret_resource_api.rb +134 -0
- data/lib/conductor/http/api/task_resource_api.rb +321 -0
- data/lib/conductor/http/api/token_resource_api.rb +42 -0
- data/lib/conductor/http/api/user_resource_api.rb +59 -0
- data/lib/conductor/http/api/workflow_bulk_resource_api.rb +91 -0
- data/lib/conductor/http/api/workflow_resource_api.rb +451 -0
- data/lib/conductor/http/api_client.rb +437 -0
- data/lib/conductor/http/models/authentication_config.rb +67 -0
- data/lib/conductor/http/models/authorization_request.rb +39 -0
- data/lib/conductor/http/models/base_model.rb +162 -0
- data/lib/conductor/http/models/bulk_response.rb +39 -0
- data/lib/conductor/http/models/conductor_application.rb +39 -0
- data/lib/conductor/http/models/conductor_user.rb +53 -0
- data/lib/conductor/http/models/create_or_update_application_request.rb +24 -0
- data/lib/conductor/http/models/create_or_update_role_request.rb +27 -0
- data/lib/conductor/http/models/event_handler.rb +130 -0
- data/lib/conductor/http/models/generate_token_request.rb +27 -0
- data/lib/conductor/http/models/group.rb +36 -0
- data/lib/conductor/http/models/integration.rb +70 -0
- data/lib/conductor/http/models/integration_api.rb +53 -0
- data/lib/conductor/http/models/integration_api_update.rb +43 -0
- data/lib/conductor/http/models/integration_update.rb +36 -0
- data/lib/conductor/http/models/permission.rb +24 -0
- data/lib/conductor/http/models/poll_data.rb +33 -0
- data/lib/conductor/http/models/prompt_template.rb +59 -0
- data/lib/conductor/http/models/prompt_template_test_request.rb +43 -0
- data/lib/conductor/http/models/rerun_workflow_request.rb +37 -0
- data/lib/conductor/http/models/role.rb +27 -0
- data/lib/conductor/http/models/schema_def.rb +59 -0
- data/lib/conductor/http/models/search_result.rb +187 -0
- data/lib/conductor/http/models/skip_task_request.rb +27 -0
- data/lib/conductor/http/models/start_workflow_request.rb +68 -0
- data/lib/conductor/http/models/subject_ref.rb +35 -0
- data/lib/conductor/http/models/tag_object.rb +36 -0
- data/lib/conductor/http/models/target_ref.rb +39 -0
- data/lib/conductor/http/models/task.rb +156 -0
- data/lib/conductor/http/models/task_def.rb +95 -0
- data/lib/conductor/http/models/task_exec_log.rb +30 -0
- data/lib/conductor/http/models/task_result.rb +115 -0
- data/lib/conductor/http/models/task_result_status.rb +24 -0
- data/lib/conductor/http/models/token.rb +33 -0
- data/lib/conductor/http/models/upsert_group_request.rb +30 -0
- data/lib/conductor/http/models/upsert_user_request.rb +39 -0
- data/lib/conductor/http/models/workflow.rb +202 -0
- data/lib/conductor/http/models/workflow_def.rb +73 -0
- data/lib/conductor/http/models/workflow_schedule.rb +100 -0
- data/lib/conductor/http/models/workflow_state_update.rb +30 -0
- data/lib/conductor/http/models/workflow_status_constants.rb +57 -0
- data/lib/conductor/http/models/workflow_task.rb +169 -0
- data/lib/conductor/http/models/workflow_test_request.rb +67 -0
- data/lib/conductor/http/rest_client.rb +211 -0
- data/lib/conductor/orkes/models/access_key.rb +56 -0
- data/lib/conductor/orkes/models/granted_permission.rb +27 -0
- data/lib/conductor/orkes/models/metadata_tag.rb +15 -0
- data/lib/conductor/orkes/models/rate_limit_tag.rb +15 -0
- data/lib/conductor/orkes/orkes_clients.rb +69 -0
- data/lib/conductor/version.rb +5 -0
- data/lib/conductor/worker/events/conductor_event.rb +40 -0
- data/lib/conductor/worker/events/global_dispatcher.rb +37 -0
- data/lib/conductor/worker/events/http_events.rb +25 -0
- data/lib/conductor/worker/events/listener_registry.rb +40 -0
- data/lib/conductor/worker/events/listeners.rb +34 -0
- data/lib/conductor/worker/events/sync_event_dispatcher.rb +78 -0
- data/lib/conductor/worker/events/task_runner_events.rb +271 -0
- data/lib/conductor/worker/events/workflow_events.rb +49 -0
- data/lib/conductor/worker/fiber_executor.rb +532 -0
- data/lib/conductor/worker/ractor_task_runner.rb +501 -0
- data/lib/conductor/worker/task_context.rb +114 -0
- data/lib/conductor/worker/task_definition_registrar.rb +322 -0
- data/lib/conductor/worker/task_handler.rb +360 -0
- data/lib/conductor/worker/task_in_progress.rb +60 -0
- data/lib/conductor/worker/task_runner.rb +538 -0
- data/lib/conductor/worker/telemetry/metrics_collector.rb +196 -0
- data/lib/conductor/worker/telemetry/prometheus_backend.rb +224 -0
- data/lib/conductor/worker/worker.rb +355 -0
- data/lib/conductor/worker/worker_config.rb +154 -0
- data/lib/conductor/worker/worker_registry.rb +71 -0
- data/lib/conductor/workflow/dsl/input_ref.rb +37 -0
- data/lib/conductor/workflow/dsl/output_ref.rb +44 -0
- data/lib/conductor/workflow/dsl/parallel_builder.rb +49 -0
- data/lib/conductor/workflow/dsl/switch_builder.rb +74 -0
- data/lib/conductor/workflow/dsl/task_ref.rb +178 -0
- data/lib/conductor/workflow/dsl/workflow_builder.rb +1016 -0
- data/lib/conductor/workflow/dsl/workflow_definition.rb +150 -0
- data/lib/conductor/workflow/llm/chat_message.rb +47 -0
- data/lib/conductor/workflow/llm/embedding_model.rb +19 -0
- data/lib/conductor/workflow/llm/tool_call.rb +43 -0
- data/lib/conductor/workflow/llm/tool_spec.rb +46 -0
- data/lib/conductor/workflow/task_type.rb +68 -0
- data/lib/conductor/workflow/timeout_policy.rb +31 -0
- data/lib/conductor/workflow/workflow_executor.rb +373 -0
- data/lib/conductor.rb +192 -0
- metadata +359 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'logger'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require_relative '../configuration'
|
|
7
|
+
require_relative '../http/api_client'
|
|
8
|
+
require_relative '../http/api/workflow_resource_api'
|
|
9
|
+
require_relative '../http/api/metadata_resource_api'
|
|
10
|
+
require_relative '../http/api/task_resource_api'
|
|
11
|
+
require_relative '../http/models/start_workflow_request'
|
|
12
|
+
require_relative '../worker/events/workflow_events'
|
|
13
|
+
require_relative '../worker/events/sync_event_dispatcher'
|
|
14
|
+
|
|
15
|
+
module Conductor
|
|
16
|
+
module Workflow
|
|
17
|
+
# WorkflowExecutor provides a high-level interface for executing workflows
|
|
18
|
+
# Supports both synchronous (wait for completion) and asynchronous execution
|
|
19
|
+
class WorkflowExecutor
|
|
20
|
+
attr_reader :workflow_api, :metadata_api, :task_api, :event_dispatcher
|
|
21
|
+
|
|
22
|
+
# Initialize WorkflowExecutor
|
|
23
|
+
# @param [Configuration] configuration Optional configuration
|
|
24
|
+
# @param [Worker::Events::SyncEventDispatcher, nil] event_dispatcher Optional event dispatcher
|
|
25
|
+
def initialize(configuration = nil, event_dispatcher: nil, logger: nil)
|
|
26
|
+
@configuration = configuration || Configuration.new
|
|
27
|
+
@logger = logger || Logger.new(File::NULL)
|
|
28
|
+
api_client = Http::ApiClient.new(configuration: @configuration)
|
|
29
|
+
@workflow_api = Http::Api::WorkflowResourceApi.new(api_client)
|
|
30
|
+
@metadata_api = Http::Api::MetadataResourceApi.new(api_client)
|
|
31
|
+
@task_api = Http::Api::TaskResourceApi.new(api_client)
|
|
32
|
+
@event_dispatcher = event_dispatcher || Worker::Events::SyncEventDispatcher.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# ==========================================
|
|
36
|
+
# Workflow Definition Operations
|
|
37
|
+
# ==========================================
|
|
38
|
+
|
|
39
|
+
# Register a workflow definition
|
|
40
|
+
# @param [WorkflowDef, ConductorWorkflow] workflow Workflow definition or ConductorWorkflow DSL
|
|
41
|
+
# @param [Boolean] overwrite Overwrite existing definition (default: true)
|
|
42
|
+
# @return [Object] Response
|
|
43
|
+
def register_workflow(workflow, overwrite: true)
|
|
44
|
+
workflow_def = workflow.respond_to?(:to_workflow_def) ? workflow.to_workflow_def : workflow
|
|
45
|
+
@metadata_api.update_workflows([workflow_def], overwrite: overwrite)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ==========================================
|
|
49
|
+
# Workflow Execution Operations
|
|
50
|
+
# ==========================================
|
|
51
|
+
|
|
52
|
+
# Start a workflow asynchronously (returns immediately with workflow ID)
|
|
53
|
+
# @param [StartWorkflowRequest] request Start workflow request
|
|
54
|
+
# @return [String] Workflow ID
|
|
55
|
+
def start_workflow(request)
|
|
56
|
+
publish_workflow_input_size(request)
|
|
57
|
+
@workflow_api.start_workflow(request)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
publish_workflow_start_error(request, e)
|
|
60
|
+
raise
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Start multiple workflows
|
|
64
|
+
# @param [Array<StartWorkflowRequest>] requests List of start workflow requests
|
|
65
|
+
# @return [Array<String>] List of workflow IDs
|
|
66
|
+
def start_workflows(*requests)
|
|
67
|
+
requests.flatten.map { |request| start_workflow(request) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Execute a workflow synchronously and wait for completion
|
|
71
|
+
# @param [StartWorkflowRequest] request Start workflow request
|
|
72
|
+
# @param [String] wait_until_task_ref Wait until this task completes (optional)
|
|
73
|
+
# @param [Integer] wait_for_seconds Maximum time to wait (default: 10)
|
|
74
|
+
# @param [String] request_id Unique request ID for idempotency (auto-generated if not provided)
|
|
75
|
+
# @return [WorkflowRun] Workflow run result
|
|
76
|
+
def execute_workflow(request, wait_until_task_ref: nil, wait_for_seconds: 10, request_id: nil)
|
|
77
|
+
request_id ||= SecureRandom.uuid
|
|
78
|
+
|
|
79
|
+
@workflow_api.execute_workflow(
|
|
80
|
+
request,
|
|
81
|
+
name: request.name,
|
|
82
|
+
version: request.version || 1,
|
|
83
|
+
request_id: request_id,
|
|
84
|
+
wait_until_task_ref: wait_until_task_ref,
|
|
85
|
+
wait_for_seconds: wait_for_seconds
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Execute a workflow by name with input (convenience method)
|
|
90
|
+
# @param [String] name Workflow name
|
|
91
|
+
# @param [Hash] input Workflow input (default: {})
|
|
92
|
+
# @param [Integer] version Workflow version (optional)
|
|
93
|
+
# @param [String] correlation_id Correlation ID (optional)
|
|
94
|
+
# @param [String] domain Task domain for all tasks (optional)
|
|
95
|
+
# @param [String] wait_until_task_ref Wait until this task completes (optional)
|
|
96
|
+
# @param [Integer] wait_for_seconds Maximum time to wait (default: 10)
|
|
97
|
+
# @param [String] request_id Unique request ID (optional)
|
|
98
|
+
# @return [WorkflowRun] Workflow run result
|
|
99
|
+
def execute(name, input: {}, version: nil, correlation_id: nil, domain: nil,
|
|
100
|
+
wait_until_task_ref: nil, wait_for_seconds: 10, request_id: nil)
|
|
101
|
+
request = Http::Models::StartWorkflowRequest.new(
|
|
102
|
+
name: name,
|
|
103
|
+
input: input,
|
|
104
|
+
version: version,
|
|
105
|
+
correlation_id: correlation_id
|
|
106
|
+
)
|
|
107
|
+
request.task_to_domain = { '*' => domain } if domain
|
|
108
|
+
|
|
109
|
+
execute_workflow(
|
|
110
|
+
request,
|
|
111
|
+
wait_until_task_ref: wait_until_task_ref,
|
|
112
|
+
wait_for_seconds: wait_for_seconds,
|
|
113
|
+
request_id: request_id
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ==========================================
|
|
118
|
+
# Workflow Status Operations
|
|
119
|
+
# ==========================================
|
|
120
|
+
|
|
121
|
+
# Get workflow execution details
|
|
122
|
+
# @param [String] workflow_id Workflow ID
|
|
123
|
+
# @param [Boolean] include_tasks Include task details (default: true)
|
|
124
|
+
# @return [Workflow] Workflow object
|
|
125
|
+
def get_workflow(workflow_id, include_tasks: true)
|
|
126
|
+
@workflow_api.get_execution_status(workflow_id, include_tasks: include_tasks)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get workflow status (lightweight)
|
|
130
|
+
# @param [String] workflow_id Workflow ID
|
|
131
|
+
# @param [Boolean] include_output Include workflow output (default: false)
|
|
132
|
+
# @param [Boolean] include_variables Include workflow variables (default: false)
|
|
133
|
+
# @return [Hash] Workflow status
|
|
134
|
+
def get_workflow_status(workflow_id, include_output: false, include_variables: false)
|
|
135
|
+
@workflow_api.get_workflow_status(
|
|
136
|
+
workflow_id,
|
|
137
|
+
include_output: include_output,
|
|
138
|
+
include_variables: include_variables
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# ==========================================
|
|
143
|
+
# Workflow Control Operations
|
|
144
|
+
# ==========================================
|
|
145
|
+
|
|
146
|
+
# Pause a running workflow
|
|
147
|
+
# @param [String] workflow_id Workflow ID
|
|
148
|
+
# @return [void]
|
|
149
|
+
def pause(workflow_id)
|
|
150
|
+
@workflow_api.pause_workflow(workflow_id)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Resume a paused workflow
|
|
154
|
+
# @param [String] workflow_id Workflow ID
|
|
155
|
+
# @return [void]
|
|
156
|
+
def resume(workflow_id)
|
|
157
|
+
@workflow_api.resume_workflow(workflow_id)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Terminate a running workflow
|
|
161
|
+
# @param [String] workflow_id Workflow ID
|
|
162
|
+
# @param [String] reason Termination reason (optional)
|
|
163
|
+
# @return [void]
|
|
164
|
+
def terminate(workflow_id, reason: nil)
|
|
165
|
+
@workflow_api.terminate(workflow_id, reason: reason)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Restart a completed workflow
|
|
169
|
+
# @param [String] workflow_id Workflow ID
|
|
170
|
+
# @param [Boolean] use_latest_definitions Use latest workflow definition (default: false)
|
|
171
|
+
# @return [void]
|
|
172
|
+
def restart(workflow_id, use_latest_definitions: false)
|
|
173
|
+
@workflow_api.restart(workflow_id, use_latest_def: use_latest_definitions)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Retry a failed workflow
|
|
177
|
+
# @param [String] workflow_id Workflow ID
|
|
178
|
+
# @param [Boolean] resume_subworkflow_tasks Resume subworkflow tasks (default: false)
|
|
179
|
+
# @return [void]
|
|
180
|
+
def retry(workflow_id, resume_subworkflow_tasks: false)
|
|
181
|
+
@workflow_api.retry(workflow_id, resume_subworkflow_tasks: resume_subworkflow_tasks)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Rerun a workflow from a specific task
|
|
185
|
+
# @param [String] workflow_id Workflow ID
|
|
186
|
+
# @param [Hash, RerunWorkflowRequest] rerun_request Rerun configuration
|
|
187
|
+
# @return [String] New workflow ID
|
|
188
|
+
def rerun(workflow_id, rerun_request)
|
|
189
|
+
@workflow_api.rerun(workflow_id, rerun_request)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Remove (delete) a workflow permanently
|
|
193
|
+
# @param [String] workflow_id Workflow ID
|
|
194
|
+
# @param [Boolean] archive_workflow Archive before deleting (default: true)
|
|
195
|
+
# @return [void]
|
|
196
|
+
def remove_workflow(workflow_id, archive_workflow: true)
|
|
197
|
+
@workflow_api.delete(workflow_id, archive_workflow: archive_workflow)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Skip a task in a running workflow
|
|
201
|
+
# @param [String] workflow_id Workflow ID
|
|
202
|
+
# @param [String] task_reference_name Task reference name to skip
|
|
203
|
+
# @param [Hash] request Skip task request (optional)
|
|
204
|
+
# @return [void]
|
|
205
|
+
def skip_task_from_workflow(workflow_id, task_reference_name, request: nil)
|
|
206
|
+
@workflow_api.skip_task_from_workflow(workflow_id, task_reference_name, request: request)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# ==========================================
|
|
210
|
+
# Task Operations
|
|
211
|
+
# ==========================================
|
|
212
|
+
|
|
213
|
+
# Update a task result
|
|
214
|
+
# @param [String] workflow_id Workflow ID
|
|
215
|
+
# @param [String] task_id Task ID
|
|
216
|
+
# @param [Hash] task_output Task output data
|
|
217
|
+
# @param [String] status Task status (COMPLETED, FAILED, etc.)
|
|
218
|
+
# @return [String] Task update result
|
|
219
|
+
def update_task(workflow_id, task_id, task_output, status)
|
|
220
|
+
task_result = Http::Models::TaskResult.new(
|
|
221
|
+
workflow_instance_id: workflow_id,
|
|
222
|
+
task_id: task_id,
|
|
223
|
+
output_data: task_output,
|
|
224
|
+
status: status
|
|
225
|
+
)
|
|
226
|
+
@task_api.update_task(task_result)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Update a task by reference name
|
|
230
|
+
# @param [String] workflow_id Workflow ID
|
|
231
|
+
# @param [String] task_reference_name Task reference name
|
|
232
|
+
# @param [Hash] task_output Task output data
|
|
233
|
+
# @param [String] status Task status
|
|
234
|
+
# @return [String] Task update result
|
|
235
|
+
def update_task_by_ref_name(workflow_id, task_reference_name, task_output, status)
|
|
236
|
+
@task_api.update_task_by_ref_name(
|
|
237
|
+
task_output,
|
|
238
|
+
workflow_id: workflow_id,
|
|
239
|
+
task_ref_name: task_reference_name,
|
|
240
|
+
status: status
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Get a task by ID
|
|
245
|
+
# @param [String] task_id Task ID
|
|
246
|
+
# @return [Task] Task object
|
|
247
|
+
def get_task(task_id)
|
|
248
|
+
@task_api.get_task(task_id)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# ==========================================
|
|
252
|
+
# Correlation ID Operations
|
|
253
|
+
# ==========================================
|
|
254
|
+
|
|
255
|
+
# Get workflows by correlation ID
|
|
256
|
+
# @param [String] workflow_name Workflow name
|
|
257
|
+
# @param [String] correlation_id Correlation ID
|
|
258
|
+
# @param [Boolean] include_closed Include closed workflows (default: false)
|
|
259
|
+
# @param [Boolean] include_tasks Include task details (default: false)
|
|
260
|
+
# @return [Array<Workflow>] List of workflows
|
|
261
|
+
def get_by_correlation_id(workflow_name, correlation_id, include_closed: false, include_tasks: false)
|
|
262
|
+
@workflow_api.get_workflows(
|
|
263
|
+
workflow_name,
|
|
264
|
+
correlation_id,
|
|
265
|
+
include_closed: include_closed,
|
|
266
|
+
include_tasks: include_tasks
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Get workflows by multiple correlation IDs
|
|
271
|
+
# @param [String] workflow_name Workflow name
|
|
272
|
+
# @param [Array<String>] correlation_ids List of correlation IDs
|
|
273
|
+
# @param [Boolean] include_closed Include closed workflows (default: false)
|
|
274
|
+
# @param [Boolean] include_tasks Include task details (default: false)
|
|
275
|
+
# @return [Hash<String, Array<Workflow>>] Map of correlation ID to workflows
|
|
276
|
+
def get_by_correlation_ids(workflow_name, correlation_ids, include_closed: false, include_tasks: false)
|
|
277
|
+
# NOTE: This would require a batch API endpoint; for now, iterate
|
|
278
|
+
result = {}
|
|
279
|
+
correlation_ids.each do |correlation_id|
|
|
280
|
+
result[correlation_id] = get_by_correlation_id(
|
|
281
|
+
workflow_name,
|
|
282
|
+
correlation_id,
|
|
283
|
+
include_closed: include_closed,
|
|
284
|
+
include_tasks: include_tasks
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
result
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# ==========================================
|
|
291
|
+
# Polling Helpers
|
|
292
|
+
# ==========================================
|
|
293
|
+
|
|
294
|
+
# Wait for a workflow to complete
|
|
295
|
+
# @param [String] workflow_id Workflow ID
|
|
296
|
+
# @param [Integer] timeout_seconds Maximum time to wait (default: 60)
|
|
297
|
+
# @param [Float] poll_interval_seconds Polling interval (default: 1.0)
|
|
298
|
+
# @return [Workflow] Completed workflow
|
|
299
|
+
# @raise [Timeout::Error] If workflow doesn't complete within timeout
|
|
300
|
+
def wait_for_workflow(workflow_id, timeout_seconds: 60, poll_interval_seconds: 1.0)
|
|
301
|
+
deadline = Time.now + timeout_seconds
|
|
302
|
+
|
|
303
|
+
loop do
|
|
304
|
+
workflow = get_workflow(workflow_id, include_tasks: false)
|
|
305
|
+
return workflow if workflow.terminal?
|
|
306
|
+
|
|
307
|
+
raise Timeout::Error, "Workflow #{workflow_id} did not complete within #{timeout_seconds} seconds" if Time.now >= deadline
|
|
308
|
+
|
|
309
|
+
sleep(poll_interval_seconds)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Execute a workflow and wait for completion (with polling fallback)
|
|
314
|
+
# @param [String] name Workflow name
|
|
315
|
+
# @param [Hash] input Workflow input
|
|
316
|
+
# @param [Integer] timeout_seconds Maximum time to wait
|
|
317
|
+
# @param [Hash] options Additional options (version, correlation_id, etc.)
|
|
318
|
+
# @return [Workflow] Completed workflow
|
|
319
|
+
def execute_and_wait(name, input: {}, timeout_seconds: 60, **options)
|
|
320
|
+
# First try synchronous execution
|
|
321
|
+
result = execute(
|
|
322
|
+
name,
|
|
323
|
+
input: input,
|
|
324
|
+
wait_for_seconds: [timeout_seconds, 30].min, # Server-side wait capped at 30s typically
|
|
325
|
+
**options
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# If still running, poll for completion
|
|
329
|
+
if result.running?
|
|
330
|
+
wait_for_workflow(result.workflow_id, timeout_seconds: timeout_seconds)
|
|
331
|
+
else
|
|
332
|
+
get_workflow(result.workflow_id)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
private
|
|
337
|
+
|
|
338
|
+
def publish_workflow_input_size(request)
|
|
339
|
+
return unless @event_dispatcher.has_listeners?(Worker::Events::WorkflowInputSize)
|
|
340
|
+
|
|
341
|
+
name = request.respond_to?(:name) ? request.name : nil
|
|
342
|
+
return unless name
|
|
343
|
+
|
|
344
|
+
input_bytes = begin
|
|
345
|
+
(request.respond_to?(:input) ? request.input : nil).to_json.bytesize
|
|
346
|
+
rescue StandardError
|
|
347
|
+
0
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
@event_dispatcher.publish(Worker::Events::WorkflowInputSize.new(
|
|
351
|
+
workflow_type: name,
|
|
352
|
+
version: request.respond_to?(:version) ? request.version : nil,
|
|
353
|
+
size_bytes: input_bytes
|
|
354
|
+
))
|
|
355
|
+
rescue StandardError => e
|
|
356
|
+
@logger.debug { "Telemetry error (non-fatal): #{e.class}: #{e.message}" }
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def publish_workflow_start_error(request, error)
|
|
360
|
+
wf_name = (request.respond_to?(:name) ? request.name : nil) || 'unknown'
|
|
361
|
+
wf_version = request.respond_to?(:version) ? request.version : nil
|
|
362
|
+
|
|
363
|
+
@event_dispatcher.publish(Worker::Events::WorkflowStartError.new(
|
|
364
|
+
workflow_type: wf_name,
|
|
365
|
+
version: wf_version,
|
|
366
|
+
cause: error
|
|
367
|
+
))
|
|
368
|
+
rescue StandardError => e
|
|
369
|
+
@logger.debug { "Telemetry error (non-fatal): #{e.class}: #{e.message}" }
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
data/lib/conductor.rb
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'conductor/version'
|
|
4
|
+
require_relative 'conductor/configuration'
|
|
5
|
+
require_relative 'conductor/configuration/authentication_settings'
|
|
6
|
+
require_relative 'conductor/exceptions'
|
|
7
|
+
require_relative 'conductor/http/rest_client'
|
|
8
|
+
require_relative 'conductor/http/api_client'
|
|
9
|
+
require_relative 'conductor/http/models/base_model'
|
|
10
|
+
require_relative 'conductor/http/models/token'
|
|
11
|
+
require_relative 'conductor/http/models/task_result_status'
|
|
12
|
+
require_relative 'conductor/http/models/task'
|
|
13
|
+
require_relative 'conductor/http/models/task_result'
|
|
14
|
+
require_relative 'conductor/http/models/workflow_status_constants'
|
|
15
|
+
require_relative 'conductor/http/models/start_workflow_request'
|
|
16
|
+
require_relative 'conductor/http/models/workflow_task'
|
|
17
|
+
require_relative 'conductor/http/models/workflow_def'
|
|
18
|
+
require_relative 'conductor/http/models/workflow'
|
|
19
|
+
require_relative 'conductor/http/models/task_def'
|
|
20
|
+
require_relative 'conductor/http/models/task_exec_log'
|
|
21
|
+
require_relative 'conductor/http/models/rerun_workflow_request'
|
|
22
|
+
require_relative 'conductor/http/models/skip_task_request'
|
|
23
|
+
require_relative 'conductor/http/models/bulk_response'
|
|
24
|
+
require_relative 'conductor/http/models/poll_data'
|
|
25
|
+
require_relative 'conductor/http/models/workflow_state_update'
|
|
26
|
+
require_relative 'conductor/http/models/workflow_test_request'
|
|
27
|
+
require_relative 'conductor/http/models/search_result'
|
|
28
|
+
require_relative 'conductor/http/models/event_handler'
|
|
29
|
+
require_relative 'conductor/http/models/workflow_schedule'
|
|
30
|
+
# Orkes-specific models
|
|
31
|
+
require_relative 'conductor/http/models/tag_object'
|
|
32
|
+
require_relative 'conductor/http/models/subject_ref'
|
|
33
|
+
require_relative 'conductor/http/models/target_ref'
|
|
34
|
+
require_relative 'conductor/http/models/authorization_request'
|
|
35
|
+
require_relative 'conductor/http/models/permission'
|
|
36
|
+
require_relative 'conductor/http/models/role'
|
|
37
|
+
require_relative 'conductor/http/models/group'
|
|
38
|
+
require_relative 'conductor/http/models/conductor_user'
|
|
39
|
+
require_relative 'conductor/http/models/conductor_application'
|
|
40
|
+
require_relative 'conductor/http/models/upsert_user_request'
|
|
41
|
+
require_relative 'conductor/http/models/upsert_group_request'
|
|
42
|
+
require_relative 'conductor/http/models/create_or_update_application_request'
|
|
43
|
+
require_relative 'conductor/http/models/create_or_update_role_request'
|
|
44
|
+
require_relative 'conductor/http/models/generate_token_request'
|
|
45
|
+
require_relative 'conductor/http/models/authentication_config'
|
|
46
|
+
require_relative 'conductor/http/models/prompt_template'
|
|
47
|
+
require_relative 'conductor/http/models/prompt_template_test_request'
|
|
48
|
+
require_relative 'conductor/http/models/schema_def'
|
|
49
|
+
require_relative 'conductor/http/models/integration'
|
|
50
|
+
require_relative 'conductor/http/models/integration_api'
|
|
51
|
+
require_relative 'conductor/http/models/integration_update'
|
|
52
|
+
require_relative 'conductor/http/models/integration_api_update'
|
|
53
|
+
# OSS API resources
|
|
54
|
+
require_relative 'conductor/http/api/workflow_resource_api'
|
|
55
|
+
require_relative 'conductor/http/api/task_resource_api'
|
|
56
|
+
require_relative 'conductor/http/api/metadata_resource_api'
|
|
57
|
+
require_relative 'conductor/http/api/workflow_bulk_resource_api'
|
|
58
|
+
require_relative 'conductor/http/api/event_resource_api'
|
|
59
|
+
require_relative 'conductor/http/api/scheduler_resource_api'
|
|
60
|
+
# Orkes API resources
|
|
61
|
+
require_relative 'conductor/http/api/secret_resource_api'
|
|
62
|
+
require_relative 'conductor/http/api/application_resource_api'
|
|
63
|
+
require_relative 'conductor/http/api/user_resource_api'
|
|
64
|
+
require_relative 'conductor/http/api/group_resource_api'
|
|
65
|
+
require_relative 'conductor/http/api/authorization_resource_api'
|
|
66
|
+
require_relative 'conductor/http/api/role_resource_api'
|
|
67
|
+
require_relative 'conductor/http/api/token_resource_api'
|
|
68
|
+
require_relative 'conductor/http/api/gateway_auth_resource_api'
|
|
69
|
+
require_relative 'conductor/http/api/schema_resource_api'
|
|
70
|
+
require_relative 'conductor/http/api/integration_resource_api'
|
|
71
|
+
require_relative 'conductor/http/api/prompt_resource_api'
|
|
72
|
+
# OSS Clients
|
|
73
|
+
require_relative 'conductor/client/workflow_client'
|
|
74
|
+
require_relative 'conductor/client/task_client'
|
|
75
|
+
require_relative 'conductor/client/metadata_client'
|
|
76
|
+
require_relative 'conductor/client/scheduler_client'
|
|
77
|
+
# Orkes Clients
|
|
78
|
+
require_relative 'conductor/client/secret_client'
|
|
79
|
+
require_relative 'conductor/client/authorization_client'
|
|
80
|
+
require_relative 'conductor/client/schema_client'
|
|
81
|
+
require_relative 'conductor/client/integration_client'
|
|
82
|
+
require_relative 'conductor/client/prompt_client'
|
|
83
|
+
# Orkes-specific models
|
|
84
|
+
require_relative 'conductor/orkes/models/metadata_tag'
|
|
85
|
+
require_relative 'conductor/orkes/models/rate_limit_tag'
|
|
86
|
+
require_relative 'conductor/orkes/models/access_key'
|
|
87
|
+
require_relative 'conductor/orkes/models/granted_permission'
|
|
88
|
+
# Orkes factory
|
|
89
|
+
require_relative 'conductor/orkes/orkes_clients'
|
|
90
|
+
# Worker Infrastructure
|
|
91
|
+
require_relative 'conductor/worker/events/conductor_event'
|
|
92
|
+
require_relative 'conductor/worker/events/task_runner_events'
|
|
93
|
+
require_relative 'conductor/worker/events/workflow_events'
|
|
94
|
+
require_relative 'conductor/worker/events/http_events'
|
|
95
|
+
require_relative 'conductor/worker/events/sync_event_dispatcher'
|
|
96
|
+
require_relative 'conductor/worker/events/global_dispatcher'
|
|
97
|
+
require_relative 'conductor/worker/events/listeners'
|
|
98
|
+
require_relative 'conductor/worker/events/listener_registry'
|
|
99
|
+
require_relative 'conductor/worker/task_context'
|
|
100
|
+
require_relative 'conductor/worker/task_in_progress'
|
|
101
|
+
require_relative 'conductor/worker/worker_config'
|
|
102
|
+
require_relative 'conductor/worker/worker_registry'
|
|
103
|
+
require_relative 'conductor/worker/worker'
|
|
104
|
+
require_relative 'conductor/worker/task_runner'
|
|
105
|
+
require_relative 'conductor/worker/task_handler'
|
|
106
|
+
require_relative 'conductor/worker/telemetry/metrics_collector'
|
|
107
|
+
require_relative 'conductor/worker/task_definition_registrar'
|
|
108
|
+
# Optional executors - these are lazy-loaded when their dependencies are available
|
|
109
|
+
# require_relative 'conductor/worker/ractor_task_runner' # Requires Ruby 3.1+
|
|
110
|
+
# require_relative 'conductor/worker/fiber_executor' # Requires async gem
|
|
111
|
+
# require_relative 'conductor/worker/telemetry/prometheus_backend' # Requires prometheus-client gem
|
|
112
|
+
|
|
113
|
+
# Workflow DSL
|
|
114
|
+
require_relative 'conductor/workflow/task_type'
|
|
115
|
+
require_relative 'conductor/workflow/timeout_policy'
|
|
116
|
+
require_relative 'conductor/workflow/workflow_executor'
|
|
117
|
+
# LLM/AI helpers (used by DSL)
|
|
118
|
+
require_relative 'conductor/workflow/llm/chat_message'
|
|
119
|
+
require_relative 'conductor/workflow/llm/tool_call'
|
|
120
|
+
require_relative 'conductor/workflow/llm/tool_spec'
|
|
121
|
+
require_relative 'conductor/workflow/llm/embedding_model'
|
|
122
|
+
# Workflow DSL
|
|
123
|
+
require_relative 'conductor/workflow/dsl/output_ref'
|
|
124
|
+
require_relative 'conductor/workflow/dsl/input_ref'
|
|
125
|
+
require_relative 'conductor/workflow/dsl/task_ref'
|
|
126
|
+
require_relative 'conductor/workflow/dsl/workflow_builder'
|
|
127
|
+
require_relative 'conductor/workflow/dsl/parallel_builder'
|
|
128
|
+
require_relative 'conductor/workflow/dsl/switch_builder'
|
|
129
|
+
require_relative 'conductor/workflow/dsl/workflow_definition'
|
|
130
|
+
|
|
131
|
+
# Main Conductor module
|
|
132
|
+
# Provides convenience methods for configuration
|
|
133
|
+
module Conductor
|
|
134
|
+
class << self
|
|
135
|
+
# Get or set the default configuration
|
|
136
|
+
# @return [Conductor::Configuration] The default configuration
|
|
137
|
+
def config
|
|
138
|
+
@config ||= Conductor::Configuration.new
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Set the default configuration
|
|
142
|
+
# @param [Conductor::Configuration] configuration The configuration to set
|
|
143
|
+
attr_writer :config
|
|
144
|
+
|
|
145
|
+
# Configure Conductor with a block
|
|
146
|
+
# @yield [Conductor::Configuration] The configuration object
|
|
147
|
+
# @example
|
|
148
|
+
# Conductor.configure do |config|
|
|
149
|
+
# config.server_url = 'http://localhost:7001/api'
|
|
150
|
+
# config.authentication_settings = Conductor::Configuration::AuthenticationSettings.new(
|
|
151
|
+
# key_id: 'my_key',
|
|
152
|
+
# key_secret: 'my_secret'
|
|
153
|
+
# )
|
|
154
|
+
# end
|
|
155
|
+
def configure
|
|
156
|
+
yield(config) if block_given?
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Define a workflow using the new Ruby-idiomatic DSL
|
|
160
|
+
# @param name [Symbol, String] Workflow name
|
|
161
|
+
# @param version [Integer, nil] Workflow version (optional)
|
|
162
|
+
# @param description [String, nil] Workflow description (optional)
|
|
163
|
+
# @param executor [WorkflowExecutor, nil] Optional executor for .register() and .execute()
|
|
164
|
+
# @yield Block containing workflow definition
|
|
165
|
+
# @return [Workflow::Dsl::WorkflowDefinition] The workflow definition
|
|
166
|
+
# @example
|
|
167
|
+
# workflow = Conductor.workflow :order_processing, version: 1, executor: executor do
|
|
168
|
+
# user = simple :get_user, user_id: wf[:user_id]
|
|
169
|
+
# order = simple :create_order, user_email: user[:email]
|
|
170
|
+
#
|
|
171
|
+
# parallel do
|
|
172
|
+
# simple :send_confirmation, email: user[:email]
|
|
173
|
+
# simple :update_inventory, order_id: order[:id]
|
|
174
|
+
# end
|
|
175
|
+
#
|
|
176
|
+
# output order_id: order[:id], status: 'created'
|
|
177
|
+
# end
|
|
178
|
+
#
|
|
179
|
+
# workflow.register(overwrite: true)
|
|
180
|
+
# result = workflow.execute(input: { user_id: 123 })
|
|
181
|
+
def workflow(name, version: nil, description: nil, executor: nil, &block)
|
|
182
|
+
builder = Workflow::Dsl::WorkflowBuilder.new(
|
|
183
|
+
name.to_s,
|
|
184
|
+
version: version,
|
|
185
|
+
description: description,
|
|
186
|
+
executor: executor
|
|
187
|
+
)
|
|
188
|
+
builder.instance_eval(&block)
|
|
189
|
+
Workflow::Dsl::WorkflowDefinition.new(builder, executor: executor)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|