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,245 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Prompt Management Journey - Comprehensive Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates all Prompt Management APIs through building an
|
|
7
|
+
# AI-powered customer service system.
|
|
8
|
+
#
|
|
9
|
+
# APIs Covered:
|
|
10
|
+
# - save_prompt, get_prompt, get_prompts, delete_prompt
|
|
11
|
+
# - get_tags_for_prompt_template, update_tag_for_prompt_template, delete_tag_for_prompt_template
|
|
12
|
+
# - test_prompt
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bundle exec ruby examples/prompt_journey.rb
|
|
16
|
+
|
|
17
|
+
require_relative '../lib/conductor'
|
|
18
|
+
|
|
19
|
+
class PromptJourney
|
|
20
|
+
AI_INTEGRATION = ENV.fetch('AI_INTEGRATION', 'openai')
|
|
21
|
+
AI_MODEL = ENV.fetch('AI_MODEL', 'gpt-4o-mini')
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@config = Conductor::Configuration.new
|
|
25
|
+
@clients = Conductor::Orkes::OrkesClients.new(@config)
|
|
26
|
+
@prompt_client = @clients.get_prompt_client
|
|
27
|
+
@created_prompts = []
|
|
28
|
+
|
|
29
|
+
puts '=' * 70
|
|
30
|
+
puts 'Prompt Management Journey'
|
|
31
|
+
puts '=' * 70
|
|
32
|
+
puts "Server: #{@config.server_url}"
|
|
33
|
+
puts "AI Integration: #{AI_INTEGRATION}"
|
|
34
|
+
puts
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def run
|
|
38
|
+
create_prompts
|
|
39
|
+
retrieve_prompts
|
|
40
|
+
test_prompts
|
|
41
|
+
manage_tags
|
|
42
|
+
cleanup
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def create_prompts
|
|
48
|
+
puts "\n--- Creating Prompt Templates ---"
|
|
49
|
+
|
|
50
|
+
# Customer service greeting prompt
|
|
51
|
+
greeting_prompt = <<~PROMPT
|
|
52
|
+
You are a friendly customer service representative for TechMart.
|
|
53
|
+
|
|
54
|
+
Customer Name: ${customer_name}
|
|
55
|
+
Issue Category: ${issue_category}
|
|
56
|
+
|
|
57
|
+
Please greet the customer warmly and ask how you can help them today.
|
|
58
|
+
Keep your response concise and professional.
|
|
59
|
+
PROMPT
|
|
60
|
+
|
|
61
|
+
@prompt_client.save_prompt(
|
|
62
|
+
'cs_greeting_ruby',
|
|
63
|
+
greeting_prompt,
|
|
64
|
+
description: 'Customer service greeting template'
|
|
65
|
+
)
|
|
66
|
+
@created_prompts << 'cs_greeting_ruby'
|
|
67
|
+
puts 'Created prompt: cs_greeting_ruby'
|
|
68
|
+
|
|
69
|
+
# Product recommendation prompt
|
|
70
|
+
recommendation_prompt = <<~PROMPT
|
|
71
|
+
Based on the following customer preferences, recommend 3 products:
|
|
72
|
+
|
|
73
|
+
Budget: ${budget}
|
|
74
|
+
Category: ${category}
|
|
75
|
+
Previous Purchases: ${previous_purchases}
|
|
76
|
+
|
|
77
|
+
Format your response as a numbered list with brief descriptions.
|
|
78
|
+
PROMPT
|
|
79
|
+
|
|
80
|
+
@prompt_client.save_prompt(
|
|
81
|
+
'product_recommendation_ruby',
|
|
82
|
+
recommendation_prompt,
|
|
83
|
+
description: 'Product recommendation template'
|
|
84
|
+
)
|
|
85
|
+
@created_prompts << 'product_recommendation_ruby'
|
|
86
|
+
puts 'Created prompt: product_recommendation_ruby'
|
|
87
|
+
|
|
88
|
+
# Issue resolution prompt
|
|
89
|
+
resolution_prompt = <<~PROMPT
|
|
90
|
+
You are helping resolve a customer issue.
|
|
91
|
+
|
|
92
|
+
Issue: ${issue_description}
|
|
93
|
+
Product: ${product_name}
|
|
94
|
+
Purchase Date: ${purchase_date}
|
|
95
|
+
|
|
96
|
+
Provide a helpful resolution or next steps. Be empathetic and solution-focused.
|
|
97
|
+
PROMPT
|
|
98
|
+
|
|
99
|
+
@prompt_client.save_prompt(
|
|
100
|
+
'issue_resolution_ruby',
|
|
101
|
+
resolution_prompt,
|
|
102
|
+
description: 'Issue resolution template'
|
|
103
|
+
)
|
|
104
|
+
@created_prompts << 'issue_resolution_ruby'
|
|
105
|
+
puts 'Created prompt: issue_resolution_ruby'
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def retrieve_prompts
|
|
109
|
+
puts "\n--- Retrieving Prompts ---"
|
|
110
|
+
|
|
111
|
+
# Get specific prompt
|
|
112
|
+
prompt = @prompt_client.get_prompt('cs_greeting_ruby')
|
|
113
|
+
puts 'Retrieved prompt: cs_greeting_ruby'
|
|
114
|
+
puts " Description: #{begin
|
|
115
|
+
prompt['description'] || prompt.description
|
|
116
|
+
rescue StandardError
|
|
117
|
+
'N/A'
|
|
118
|
+
end}"
|
|
119
|
+
|
|
120
|
+
# Get all prompts
|
|
121
|
+
all_prompts = @prompt_client.get_prompts
|
|
122
|
+
puts "\nAll prompts (#{all_prompts.length} total):"
|
|
123
|
+
all_prompts.first(5).each do |p|
|
|
124
|
+
name = p.is_a?(Hash) ? p['name'] : p.name
|
|
125
|
+
puts " - #{name}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_prompts
|
|
130
|
+
puts "\n--- Testing Prompts ---"
|
|
131
|
+
|
|
132
|
+
# Test the greeting prompt
|
|
133
|
+
test_input = {
|
|
134
|
+
'customer_name' => 'John Smith',
|
|
135
|
+
'issue_category' => 'Technical Support'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
puts 'Testing cs_greeting_ruby with:'
|
|
139
|
+
test_input.each { |k, v| puts " #{k}: #{v}" }
|
|
140
|
+
|
|
141
|
+
begin
|
|
142
|
+
result = @prompt_client.test_prompt(
|
|
143
|
+
'cs_greeting_ruby',
|
|
144
|
+
test_input,
|
|
145
|
+
AI_INTEGRATION,
|
|
146
|
+
AI_MODEL
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
puts "\nAI Response:"
|
|
150
|
+
puts '-' * 40
|
|
151
|
+
response = begin
|
|
152
|
+
result.is_a?(Hash) ? result['response'] : result.response
|
|
153
|
+
rescue StandardError
|
|
154
|
+
result
|
|
155
|
+
end
|
|
156
|
+
puts response.to_s[0..500]
|
|
157
|
+
puts '-' * 40
|
|
158
|
+
rescue Conductor::ApiError => e
|
|
159
|
+
puts "Test failed (AI integration may not be configured): #{e.message}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Test product recommendation
|
|
163
|
+
puts "\nTesting product_recommendation_ruby..."
|
|
164
|
+
rec_input = {
|
|
165
|
+
'budget' => '$500',
|
|
166
|
+
'category' => 'Electronics',
|
|
167
|
+
'previous_purchases' => 'Laptop, Headphones'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
result = @prompt_client.test_prompt(
|
|
172
|
+
'product_recommendation_ruby',
|
|
173
|
+
rec_input,
|
|
174
|
+
AI_INTEGRATION,
|
|
175
|
+
AI_MODEL
|
|
176
|
+
)
|
|
177
|
+
response = begin
|
|
178
|
+
result.is_a?(Hash) ? result['response'] : result.response
|
|
179
|
+
rescue StandardError
|
|
180
|
+
result
|
|
181
|
+
end
|
|
182
|
+
puts "Response preview: #{response.to_s[0..200]}..."
|
|
183
|
+
rescue Conductor::ApiError => e
|
|
184
|
+
puts "Test failed: #{e.message}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def manage_tags
|
|
189
|
+
puts "\n--- Managing Tags ---"
|
|
190
|
+
|
|
191
|
+
tags = [
|
|
192
|
+
{ 'key' => 'department', 'value' => 'customer_service' },
|
|
193
|
+
{ 'key' => 'language', 'value' => 'english' },
|
|
194
|
+
{ 'key' => 'version', 'value' => 'v1' }
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
# Add tags
|
|
198
|
+
puts 'Adding tags to cs_greeting_ruby...'
|
|
199
|
+
@prompt_client.update_tag_for_prompt_template('cs_greeting_ruby', tags)
|
|
200
|
+
|
|
201
|
+
# Get tags
|
|
202
|
+
retrieved_tags = @prompt_client.get_tags_for_prompt_template('cs_greeting_ruby')
|
|
203
|
+
puts 'Retrieved tags:'
|
|
204
|
+
retrieved_tags.each do |tag|
|
|
205
|
+
key = tag.is_a?(Hash) ? tag['key'] : tag.key
|
|
206
|
+
value = tag.is_a?(Hash) ? tag['value'] : tag.value
|
|
207
|
+
puts " #{key}: #{value}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Delete specific tag
|
|
211
|
+
puts "\nDeleting 'version' tag..."
|
|
212
|
+
@prompt_client.delete_tag_for_prompt_template(
|
|
213
|
+
'cs_greeting_ruby',
|
|
214
|
+
[{ 'key' => 'version', 'value' => 'v1' }]
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Verify deletion
|
|
218
|
+
remaining_tags = @prompt_client.get_tags_for_prompt_template('cs_greeting_ruby')
|
|
219
|
+
puts "Remaining tags: #{remaining_tags.length}"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def cleanup
|
|
223
|
+
puts "\n--- Cleanup ---"
|
|
224
|
+
|
|
225
|
+
@created_prompts.each do |name|
|
|
226
|
+
@prompt_client.delete_prompt(name)
|
|
227
|
+
puts "Deleted prompt: #{name}"
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
puts "Could not delete #{name}: #{e.message}"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
puts "\nPrompt journey complete!"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
if __FILE__ == $PROGRAM_NAME
|
|
237
|
+
begin
|
|
238
|
+
PromptJourney.new.run
|
|
239
|
+
rescue Conductor::ApiError => e
|
|
240
|
+
puts "API Error: #{e.message}"
|
|
241
|
+
rescue StandardError => e
|
|
242
|
+
puts "Error: #{e.message}"
|
|
243
|
+
puts e.backtrace.first(3).join("\n")
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# RAG (Retrieval Augmented Generation) Workflow Example
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates a complete RAG pipeline using Conductor:
|
|
7
|
+
# 1. User provides text content as workflow input
|
|
8
|
+
# 2. Conductor indexes the text into a vector DB using embeddings
|
|
9
|
+
# 3. A search query retrieves relevant context from the vector store
|
|
10
|
+
# 4. An LLM generates an answer grounded in the retrieved context
|
|
11
|
+
#
|
|
12
|
+
# Prerequisites:
|
|
13
|
+
# 1. Orkes Conductor server with AI/LLM support
|
|
14
|
+
# 2. Configure integrations in Conductor:
|
|
15
|
+
# - Vector DB integration (e.g., "pinecone", "weaviate", "pgvector")
|
|
16
|
+
# - LLM provider (e.g., "openai", "azure_openai", "cohere")
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# bundle exec ruby examples/rag_workflow.rb
|
|
20
|
+
|
|
21
|
+
require_relative '../lib/conductor'
|
|
22
|
+
|
|
23
|
+
include Conductor::Workflow
|
|
24
|
+
|
|
25
|
+
# Configuration constants - adjust based on your integrations
|
|
26
|
+
VECTOR_DB = 'pinecone'
|
|
27
|
+
VECTOR_INDEX = 'demo_index'
|
|
28
|
+
NAMESPACE = 'demo_namespace'
|
|
29
|
+
EMBEDDING_PROVIDER = 'openai'
|
|
30
|
+
EMBEDDING_MODEL = 'text-embedding-3-small'
|
|
31
|
+
LLM_PROVIDER = 'openai'
|
|
32
|
+
LLM_MODEL = 'gpt-4o-mini'
|
|
33
|
+
|
|
34
|
+
def create_rag_workflow(workflow_client, workflow_executor)
|
|
35
|
+
workflow = ConductorWorkflow.new(
|
|
36
|
+
workflow_client,
|
|
37
|
+
'rag_pipeline_ruby',
|
|
38
|
+
version: 1,
|
|
39
|
+
executor: workflow_executor
|
|
40
|
+
)
|
|
41
|
+
workflow.description('RAG pipeline: index text -> search -> generate answer')
|
|
42
|
+
workflow.timeout_seconds(300)
|
|
43
|
+
|
|
44
|
+
# Step 1: Index the input text into vector DB
|
|
45
|
+
index_task = LlmIndexTextTask.new(
|
|
46
|
+
'index_text_ref',
|
|
47
|
+
VECTOR_DB,
|
|
48
|
+
VECTOR_INDEX,
|
|
49
|
+
namespace: NAMESPACE,
|
|
50
|
+
embedding_model: EMBEDDING_MODEL,
|
|
51
|
+
embedding_model_provider: EMBEDDING_PROVIDER
|
|
52
|
+
)
|
|
53
|
+
index_task.input('text', workflow.input('text'))
|
|
54
|
+
index_task.input('docId', workflow.input('doc_id'))
|
|
55
|
+
|
|
56
|
+
# Step 2: Wait for vector DB to commit (eventual consistency)
|
|
57
|
+
wait_task = WaitTask.new('wait_for_index', wait_for_seconds: 3)
|
|
58
|
+
|
|
59
|
+
# Step 3: Search the index with the user's question
|
|
60
|
+
search_task = LlmSearchIndexTask.new(
|
|
61
|
+
'search_index_ref',
|
|
62
|
+
VECTOR_DB,
|
|
63
|
+
VECTOR_INDEX,
|
|
64
|
+
namespace: NAMESPACE,
|
|
65
|
+
embedding_model: EMBEDDING_MODEL,
|
|
66
|
+
embedding_model_provider: EMBEDDING_PROVIDER
|
|
67
|
+
)
|
|
68
|
+
search_task.input('query', workflow.input('question'))
|
|
69
|
+
search_task.input('maxResults', 5)
|
|
70
|
+
|
|
71
|
+
# Step 4: Generate answer using retrieved context
|
|
72
|
+
system_prompt = <<~PROMPT
|
|
73
|
+
You are a helpful assistant. Answer the user's question based ONLY on the#{' '}
|
|
74
|
+
context provided below. If the context does not contain enough information, say so.
|
|
75
|
+
|
|
76
|
+
Context from knowledge base:
|
|
77
|
+
${search_index_ref.output.result}
|
|
78
|
+
PROMPT
|
|
79
|
+
|
|
80
|
+
answer_task = LlmChatCompleteTask.new(
|
|
81
|
+
'generate_answer_ref',
|
|
82
|
+
LLM_PROVIDER,
|
|
83
|
+
LLM_MODEL,
|
|
84
|
+
messages: [
|
|
85
|
+
ChatMessage.new(role: 'system', message: system_prompt),
|
|
86
|
+
ChatMessage.new(role: 'user', message: workflow.input('question'))
|
|
87
|
+
],
|
|
88
|
+
temperature: 0.2,
|
|
89
|
+
max_tokens: 1024
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Chain tasks
|
|
93
|
+
workflow >> index_task >> wait_task >> search_task >> answer_task
|
|
94
|
+
|
|
95
|
+
# Define outputs
|
|
96
|
+
workflow.output_parameter('retrieved_context', search_task.output('result'))
|
|
97
|
+
workflow.output_parameter('answer', answer_task.output('result'))
|
|
98
|
+
|
|
99
|
+
workflow
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def main
|
|
103
|
+
config = Conductor::Configuration.new
|
|
104
|
+
|
|
105
|
+
puts '=' * 70
|
|
106
|
+
puts 'Conductor Ruby SDK - RAG Workflow Example'
|
|
107
|
+
puts '=' * 70
|
|
108
|
+
puts
|
|
109
|
+
puts "Server: #{config.server_url}"
|
|
110
|
+
puts "Vector DB: #{VECTOR_DB}"
|
|
111
|
+
puts "LLM Provider: #{LLM_PROVIDER}"
|
|
112
|
+
puts
|
|
113
|
+
|
|
114
|
+
clients = Conductor::Orkes::OrkesClients.new(config)
|
|
115
|
+
workflow_executor = clients.get_workflow_executor
|
|
116
|
+
workflow_client = clients.get_workflow_client
|
|
117
|
+
|
|
118
|
+
# Create and register workflow
|
|
119
|
+
workflow = create_rag_workflow(workflow_client, workflow_executor)
|
|
120
|
+
workflow_executor.register_workflow(workflow, overwrite: true)
|
|
121
|
+
puts "Registered workflow: #{workflow.name}"
|
|
122
|
+
|
|
123
|
+
# Sample document and question
|
|
124
|
+
sample_text = <<~TEXT
|
|
125
|
+
Ruby is a dynamic, open source programming language with a focus on simplicity#{' '}
|
|
126
|
+
and productivity. It has an elegant syntax that is natural to read and easy to write.
|
|
127
|
+
Ruby was created by Yukihiro "Matz" Matsumoto in the mid-1990s in Japan.
|
|
128
|
+
Ruby on Rails, often simply called Rails, is a web application framework written in Ruby.
|
|
129
|
+
Rails was created by David Heinemeier Hansson and released in 2004.
|
|
130
|
+
TEXT
|
|
131
|
+
|
|
132
|
+
question = 'Who created Ruby and when?'
|
|
133
|
+
|
|
134
|
+
puts
|
|
135
|
+
puts 'Executing RAG workflow...'
|
|
136
|
+
puts "Document: #{sample_text.length} characters"
|
|
137
|
+
puts "Question: #{question}"
|
|
138
|
+
|
|
139
|
+
result = workflow_executor.execute(
|
|
140
|
+
workflow.name,
|
|
141
|
+
input: {
|
|
142
|
+
'text' => sample_text,
|
|
143
|
+
'doc_id' => 'ruby_intro',
|
|
144
|
+
'question' => question
|
|
145
|
+
},
|
|
146
|
+
wait_for_seconds: 60
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
puts
|
|
150
|
+
puts '-' * 70
|
|
151
|
+
puts "Status: #{result.status}"
|
|
152
|
+
puts "Answer: #{result.output['answer']}"
|
|
153
|
+
puts '-' * 70
|
|
154
|
+
puts
|
|
155
|
+
puts "View execution: #{config.ui_host}/execution/#{result.workflow_id}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if __FILE__ == $PROGRAM_NAME
|
|
159
|
+
begin
|
|
160
|
+
main
|
|
161
|
+
rescue Conductor::ApiError => e
|
|
162
|
+
puts "API Error: #{e.message}"
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
puts "Error: #{e.message}"
|
|
165
|
+
puts e.backtrace.first(3).join("\n")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Schedule Management Journey - Comprehensive Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates all Scheduler APIs through building an automated
|
|
7
|
+
# order processing system with scheduled workflows.
|
|
8
|
+
#
|
|
9
|
+
# APIs Covered:
|
|
10
|
+
# - save_schedule, get_schedule, get_all_schedules, delete_schedule
|
|
11
|
+
# - pause_schedule, resume_schedule, pause_all_schedules, resume_all_schedules
|
|
12
|
+
# - get_next_few_schedule_execution_times, search_schedule_executions
|
|
13
|
+
# - set_scheduler_tags, get_scheduler_tags, delete_scheduler_tags
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# bundle exec ruby examples/schedule_journey.rb
|
|
17
|
+
|
|
18
|
+
require_relative '../lib/conductor'
|
|
19
|
+
|
|
20
|
+
class ScheduleJourney
|
|
21
|
+
def initialize
|
|
22
|
+
@config = Conductor::Configuration.new
|
|
23
|
+
@clients = Conductor::Orkes::OrkesClients.new(@config)
|
|
24
|
+
@scheduler = @clients.get_scheduler_client
|
|
25
|
+
@metadata = @clients.get_metadata_client
|
|
26
|
+
@created_schedules = []
|
|
27
|
+
|
|
28
|
+
puts '=' * 70
|
|
29
|
+
puts 'Schedule Management Journey'
|
|
30
|
+
puts '=' * 70
|
|
31
|
+
puts "Server: #{@config.server_url}"
|
|
32
|
+
puts
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
setup_workflow
|
|
37
|
+
create_schedules
|
|
38
|
+
manage_schedules
|
|
39
|
+
work_with_tags
|
|
40
|
+
preview_and_search
|
|
41
|
+
cleanup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def setup_workflow
|
|
47
|
+
puts "\n--- Setting up test workflow ---"
|
|
48
|
+
|
|
49
|
+
# Create a simple workflow for scheduling
|
|
50
|
+
workflow_def = Conductor::Http::Models::WorkflowDef.new(
|
|
51
|
+
name: 'scheduled_order_processor',
|
|
52
|
+
version: 1,
|
|
53
|
+
description: 'Process orders on schedule',
|
|
54
|
+
tasks: [
|
|
55
|
+
{
|
|
56
|
+
'name' => 'process_orders',
|
|
57
|
+
'taskReferenceName' => 'process_ref',
|
|
58
|
+
'type' => 'HTTP',
|
|
59
|
+
'inputParameters' => {
|
|
60
|
+
'http_request' => {
|
|
61
|
+
'uri' => 'https://httpbin.org/post',
|
|
62
|
+
'method' => 'POST',
|
|
63
|
+
'body' => { 'timestamp' => '${workflow.input.timestamp}' }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@metadata.register_workflow_def(workflow_def, overwrite: true)
|
|
71
|
+
puts "Created workflow: #{workflow_def.name}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def create_schedules
|
|
75
|
+
puts "\n--- Creating Schedules ---"
|
|
76
|
+
|
|
77
|
+
# Schedule 1: Every minute (for demo)
|
|
78
|
+
schedule1 = {
|
|
79
|
+
'name' => 'order_processor_minutely',
|
|
80
|
+
'cronExpression' => '0 * * * * ?', # Every minute
|
|
81
|
+
'startWorkflowRequest' => {
|
|
82
|
+
'name' => 'scheduled_order_processor',
|
|
83
|
+
'version' => 1,
|
|
84
|
+
'input' => { 'type' => 'minutely' }
|
|
85
|
+
},
|
|
86
|
+
'paused' => true, # Start paused for demo
|
|
87
|
+
'scheduleStartTime' => (Time.now.to_i * 1000),
|
|
88
|
+
'zoneId' => 'America/New_York'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@scheduler.save_schedule(schedule1)
|
|
92
|
+
@created_schedules << schedule1['name']
|
|
93
|
+
puts "Created schedule: #{schedule1['name']} (every minute)"
|
|
94
|
+
|
|
95
|
+
# Schedule 2: Daily at midnight
|
|
96
|
+
schedule2 = {
|
|
97
|
+
'name' => 'order_processor_daily',
|
|
98
|
+
'cronExpression' => '0 0 0 * * ?', # Daily at midnight
|
|
99
|
+
'startWorkflowRequest' => {
|
|
100
|
+
'name' => 'scheduled_order_processor',
|
|
101
|
+
'version' => 1,
|
|
102
|
+
'input' => { 'type' => 'daily' }
|
|
103
|
+
},
|
|
104
|
+
'paused' => true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@scheduler.save_schedule(schedule2)
|
|
108
|
+
@created_schedules << schedule2['name']
|
|
109
|
+
puts "Created schedule: #{schedule2['name']} (daily at midnight)"
|
|
110
|
+
|
|
111
|
+
# Schedule 3: Weekly on Monday
|
|
112
|
+
schedule3 = {
|
|
113
|
+
'name' => 'order_processor_weekly',
|
|
114
|
+
'cronExpression' => '0 0 9 ? * MON', # Monday at 9 AM
|
|
115
|
+
'startWorkflowRequest' => {
|
|
116
|
+
'name' => 'scheduled_order_processor',
|
|
117
|
+
'version' => 1,
|
|
118
|
+
'input' => { 'type' => 'weekly' }
|
|
119
|
+
},
|
|
120
|
+
'paused' => true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@scheduler.save_schedule(schedule3)
|
|
124
|
+
@created_schedules << schedule3['name']
|
|
125
|
+
puts "Created schedule: #{schedule3['name']} (weekly on Monday)"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def manage_schedules
|
|
129
|
+
puts "\n--- Managing Schedules ---"
|
|
130
|
+
|
|
131
|
+
# Get a specific schedule
|
|
132
|
+
schedule = @scheduler.get_schedule('order_processor_minutely')
|
|
133
|
+
puts "Retrieved schedule: #{schedule['name']}"
|
|
134
|
+
puts " Cron: #{schedule['cronExpression']}"
|
|
135
|
+
puts " Paused: #{schedule['paused']}"
|
|
136
|
+
|
|
137
|
+
# Get all schedules
|
|
138
|
+
all_schedules = @scheduler.get_all_schedules
|
|
139
|
+
puts "\nAll schedules (#{all_schedules.length} total):"
|
|
140
|
+
all_schedules.first(5).each do |s|
|
|
141
|
+
name = s.is_a?(Hash) ? s['name'] : s.name
|
|
142
|
+
puts " - #{name}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Pause and resume individual schedule
|
|
146
|
+
puts "\nPausing order_processor_minutely..."
|
|
147
|
+
@scheduler.pause_schedule('order_processor_minutely')
|
|
148
|
+
|
|
149
|
+
puts 'Resuming order_processor_minutely...'
|
|
150
|
+
begin
|
|
151
|
+
@scheduler.resume_schedule('order_processor_minutely')
|
|
152
|
+
rescue Conductor::ApiError => e
|
|
153
|
+
puts " Note: #{e.message}" if e.message.include?('404')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Pause all schedules
|
|
157
|
+
puts "\nPausing all schedules..."
|
|
158
|
+
@scheduler.pause_all_schedules
|
|
159
|
+
|
|
160
|
+
# Resume all schedules
|
|
161
|
+
puts 'Resuming all schedules...'
|
|
162
|
+
@scheduler.resume_all_schedules
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def work_with_tags
|
|
166
|
+
puts "\n--- Working with Tags ---"
|
|
167
|
+
|
|
168
|
+
tags = [
|
|
169
|
+
{ 'key' => 'environment', 'value' => 'production' },
|
|
170
|
+
{ 'key' => 'team', 'value' => 'orders' }
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
# Set tags
|
|
174
|
+
puts 'Setting tags on order_processor_daily...'
|
|
175
|
+
@scheduler.set_scheduler_tags(tags, 'order_processor_daily')
|
|
176
|
+
|
|
177
|
+
# Get tags
|
|
178
|
+
retrieved_tags = @scheduler.get_scheduler_tags('order_processor_daily')
|
|
179
|
+
puts 'Retrieved tags:'
|
|
180
|
+
retrieved_tags.each do |tag|
|
|
181
|
+
key = tag.is_a?(Hash) ? tag['key'] : tag.key
|
|
182
|
+
value = tag.is_a?(Hash) ? tag['value'] : tag.value
|
|
183
|
+
puts " #{key}: #{value}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Delete tags
|
|
187
|
+
puts 'Deleting tags...'
|
|
188
|
+
@scheduler.delete_scheduler_tags(tags, 'order_processor_daily')
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def preview_and_search
|
|
192
|
+
puts "\n--- Preview and Search ---"
|
|
193
|
+
|
|
194
|
+
# Get next execution times
|
|
195
|
+
puts 'Next 5 execution times for order_processor_minutely:'
|
|
196
|
+
begin
|
|
197
|
+
times = @scheduler.get_next_few_schedule_execution_times(
|
|
198
|
+
'0 * * * * ?',
|
|
199
|
+
limit: 5
|
|
200
|
+
)
|
|
201
|
+
times.first(5).each { |t| puts " - #{Time.at(t / 1000)}" }
|
|
202
|
+
rescue StandardError => e
|
|
203
|
+
puts " Could not get execution times: #{e.message}"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Search executions
|
|
207
|
+
puts "\nSearching schedule executions..."
|
|
208
|
+
begin
|
|
209
|
+
results = @scheduler.search_schedule_executions(
|
|
210
|
+
start: 0,
|
|
211
|
+
size: 10,
|
|
212
|
+
query: '*:*'
|
|
213
|
+
)
|
|
214
|
+
count = results.is_a?(Hash) ? results['totalHits'] : results.total_hits
|
|
215
|
+
puts "Found #{count || 0} executions"
|
|
216
|
+
rescue StandardError => e
|
|
217
|
+
puts " Search returned: #{e.message}"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def cleanup
|
|
222
|
+
puts "\n--- Cleanup ---"
|
|
223
|
+
|
|
224
|
+
@created_schedules.each do |name|
|
|
225
|
+
@scheduler.delete_schedule(name)
|
|
226
|
+
puts "Deleted schedule: #{name}"
|
|
227
|
+
rescue StandardError => e
|
|
228
|
+
puts "Could not delete #{name}: #{e.message}"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
puts "\nSchedule journey complete!"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if __FILE__ == $PROGRAM_NAME
|
|
236
|
+
begin
|
|
237
|
+
ScheduleJourney.new.run
|
|
238
|
+
rescue Conductor::ApiError => e
|
|
239
|
+
puts "API Error: #{e.message}"
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
puts "Error: #{e.message}"
|
|
242
|
+
puts e.backtrace.first(3).join("\n")
|
|
243
|
+
end
|
|
244
|
+
end
|