smart_prompt 0.4.4 → 0.5.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 +4 -4
- data/CHANGELOG.md +10 -10
- data/README.cn.md +307 -64
- data/README.md +311 -64
- data/Rakefile +10 -1
- data/config/anthropic_config.yml +151 -0
- data/config/image_generation_config.yml +22 -0
- data/config/multimodal_config.yml +85 -0
- data/config/sensenova_config.yml +63 -0
- data/config/zhipu_config.yml +73 -0
- data/examples/anthropic_basic_chat.rb +143 -0
- data/examples/anthropic_example.rb +232 -0
- data/examples/anthropic_multimodal.rb +212 -0
- data/examples/anthropic_streaming.rb +312 -0
- data/examples/anthropic_tool_calling.rb +393 -0
- data/examples/automatic_cleanup_example.rb +109 -0
- data/examples/history_management_examples.rb +522 -0
- data/examples/image_generation_example.rb +130 -0
- data/examples/monitoring_example.rb +121 -0
- data/examples/multimodal_example.rb +63 -0
- data/examples/relevance_based_strategy_example.rb +87 -0
- data/examples/sensenova_example.rb +129 -0
- data/examples/stt_example.rb +287 -0
- data/examples/tts_example.rb +244 -0
- data/examples/video_generation_example.rb +189 -0
- data/examples/zhipu_example.rb +151 -0
- data/lib/smart_prompt/anthropic_adapter.rb +363 -281
- data/lib/smart_prompt/compression_engine.rb +201 -0
- data/lib/smart_prompt/context_strategy.rb +22 -0
- data/lib/smart_prompt/conversation.rb +81 -191
- data/lib/smart_prompt/engine.rb +36 -19
- data/lib/smart_prompt/history_manager.rb +596 -0
- data/lib/smart_prompt/hybrid_strategy.rb +222 -0
- data/lib/smart_prompt/image_generation_adapter.rb +297 -0
- data/lib/smart_prompt/lru_cache.rb +133 -0
- data/lib/smart_prompt/message.rb +57 -0
- data/lib/smart_prompt/multimodal_adapter.rb +277 -0
- data/lib/smart_prompt/openai_adapter.rb +1 -25
- data/lib/smart_prompt/persistence_layer.rb +197 -0
- data/lib/smart_prompt/relevance_based_strategy.rb +221 -0
- data/lib/smart_prompt/sensenova_adapter.rb +410 -0
- data/lib/smart_prompt/session.rb +140 -0
- data/lib/smart_prompt/sliding_window_strategy.rb +100 -0
- data/lib/smart_prompt/stt_adapter.rb +381 -0
- data/lib/smart_prompt/summary_based_strategy.rb +152 -0
- data/lib/smart_prompt/token_counter.rb +74 -0
- data/lib/smart_prompt/tts_adapter.rb +403 -0
- data/lib/smart_prompt/version.rb +1 -1
- data/lib/smart_prompt/video_generation_adapter.rb +330 -0
- data/lib/smart_prompt/worker.rb +25 -3
- data/lib/smart_prompt/zhipu_adapter.rb +616 -0
- data/lib/smart_prompt.rb +22 -2
- data/workers/history_management_examples.rb +407 -0
- data/workers/image_generation_workers.rb +119 -0
- data/workers/multimodal_workers.rb +110 -0
- data/workers/sensenova_workers.rb +62 -0
- data/workers/stt_workers.rb +195 -0
- data/workers/tts_workers.rb +388 -0
- data/workers/video_generation_workers.rb +264 -0
- data/workers/zhipu_workers.rb +113 -0
- metadata +84 -8
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require './lib/smart_prompt'
|
|
5
|
+
require 'base64'
|
|
6
|
+
|
|
7
|
+
# Example: Multimodal (Text + Image) with Anthropic Claude
|
|
8
|
+
# This example demonstrates how to use Claude's vision capabilities
|
|
9
|
+
|
|
10
|
+
puts "=" * 60
|
|
11
|
+
puts "Anthropic Claude - Multimodal (Vision) Example"
|
|
12
|
+
puts "=" * 60
|
|
13
|
+
|
|
14
|
+
# Initialize the engine with Anthropic configuration
|
|
15
|
+
engine = SmartPrompt::Engine.new('config/anthropic_config.yml')
|
|
16
|
+
|
|
17
|
+
# Example 1: Analyze Image from URL
|
|
18
|
+
puts "\n1. Analyze Image from URL"
|
|
19
|
+
puts "-" * 60
|
|
20
|
+
|
|
21
|
+
SmartPrompt.define_worker :image_analyzer do
|
|
22
|
+
use "claude"
|
|
23
|
+
sys_msg("You are an expert at analyzing images and describing what you see in detail.")
|
|
24
|
+
prompt(params[:message])
|
|
25
|
+
send_msg
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Using a public image URL
|
|
29
|
+
image_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/400px-Cat03.jpg"
|
|
30
|
+
|
|
31
|
+
response = engine.call_worker(:image_analyzer, {
|
|
32
|
+
message: [
|
|
33
|
+
{ type: "text", text: "What do you see in this image? Describe it in detail." },
|
|
34
|
+
{ type: "image_url", image_url: image_url }
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
puts "User: [Sends image URL: #{image_url}]"
|
|
38
|
+
puts "User: What do you see in this image? Describe it in detail."
|
|
39
|
+
puts "\nClaude: #{response}\n"
|
|
40
|
+
|
|
41
|
+
# Example 2: Analyze Local Image using Base64
|
|
42
|
+
puts "\n2. Analyze Local Image (Base64 Encoding)"
|
|
43
|
+
puts "-" * 60
|
|
44
|
+
|
|
45
|
+
# Check if local image exists
|
|
46
|
+
local_image_path = "./product_images/smartphone_1.png"
|
|
47
|
+
|
|
48
|
+
if File.exist?(local_image_path)
|
|
49
|
+
# Read and encode image to base64
|
|
50
|
+
image_data = File.binread(local_image_path)
|
|
51
|
+
base64_image = Base64.strict_encode64(image_data)
|
|
52
|
+
|
|
53
|
+
# Determine media type
|
|
54
|
+
media_type = case File.extname(local_image_path).downcase
|
|
55
|
+
when '.jpg', '.jpeg' then 'image/jpeg'
|
|
56
|
+
when '.png' then 'image/png'
|
|
57
|
+
when '.gif' then 'image/gif'
|
|
58
|
+
when '.webp' then 'image/webp'
|
|
59
|
+
else 'image/jpeg'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
data_url = "data:#{media_type};base64,#{base64_image}"
|
|
63
|
+
|
|
64
|
+
response = engine.call_worker(:image_analyzer, {
|
|
65
|
+
message: [
|
|
66
|
+
{ type: "text", text: "Describe this product image. What features can you identify?" },
|
|
67
|
+
{ type: "image_url", image_url: data_url }
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
puts "User: [Sends local image: #{local_image_path}]"
|
|
71
|
+
puts "User: Describe this product image. What features can you identify?"
|
|
72
|
+
puts "\nClaude: #{response}\n"
|
|
73
|
+
else
|
|
74
|
+
puts "Local image not found at #{local_image_path}"
|
|
75
|
+
puts "Skipping base64 example.\n"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Example 3: Multiple Images Analysis
|
|
79
|
+
puts "\n3. Compare Multiple Images"
|
|
80
|
+
puts "-" * 60
|
|
81
|
+
|
|
82
|
+
SmartPrompt.define_worker :image_comparator do
|
|
83
|
+
use "claude"
|
|
84
|
+
sys_msg("You are an expert at comparing and contrasting images.")
|
|
85
|
+
prompt(params[:message])
|
|
86
|
+
send_msg
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
image1_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/400px-Cat03.jpg"
|
|
90
|
+
image2_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/400px-Cat_November_2010-1a.jpg"
|
|
91
|
+
|
|
92
|
+
response = engine.call_worker(:image_comparator, {
|
|
93
|
+
message: [
|
|
94
|
+
{ type: "text", text: "Compare these two images. What are the similarities and differences?" },
|
|
95
|
+
{ type: "image_url", image_url: image1_url },
|
|
96
|
+
{ type: "image_url", image_url: image2_url }
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
puts "User: [Sends two cat images]"
|
|
100
|
+
puts "User: Compare these two images. What are the similarities and differences?"
|
|
101
|
+
puts "\nClaude: #{response}\n"
|
|
102
|
+
|
|
103
|
+
# Example 4: Image + Text Context
|
|
104
|
+
puts "\n4. Image Analysis with Additional Context"
|
|
105
|
+
puts "-" * 60
|
|
106
|
+
|
|
107
|
+
SmartPrompt.define_worker :contextual_analyzer do
|
|
108
|
+
use "claude"
|
|
109
|
+
sys_msg("You are a product analyst helping with e-commerce listings.")
|
|
110
|
+
prompt(params[:message])
|
|
111
|
+
send_msg
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
product_image = "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/Smartphone.jpg/400px-Smartphone.jpg"
|
|
115
|
+
|
|
116
|
+
response = engine.call_worker(:contextual_analyzer, {
|
|
117
|
+
message: [
|
|
118
|
+
{ type: "text", text: "This is a product image for our online store. Please:" },
|
|
119
|
+
{ type: "text", text: "1. Describe the product" },
|
|
120
|
+
{ type: "text", text: "2. Suggest a catchy product title" },
|
|
121
|
+
{ type: "text", text: "3. Write a brief product description (2-3 sentences)" },
|
|
122
|
+
{ type: "text", text: "4. List 3-5 key features" },
|
|
123
|
+
{ type: "image_url", image_url: product_image }
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
puts "User: [Sends product image with detailed instructions]"
|
|
127
|
+
puts "\nClaude: #{response}\n"
|
|
128
|
+
|
|
129
|
+
# Example 5: OCR and Text Extraction
|
|
130
|
+
puts "\n5. Text Extraction from Image (OCR)"
|
|
131
|
+
puts "-" * 60
|
|
132
|
+
|
|
133
|
+
SmartPrompt.define_worker :ocr_extractor do
|
|
134
|
+
use "claude"
|
|
135
|
+
sys_msg("You are an expert at reading and extracting text from images.")
|
|
136
|
+
prompt(params[:message])
|
|
137
|
+
send_msg
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Using an image with text (e.g., a sign or document)
|
|
141
|
+
text_image_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/PDF_file_icon.svg/400px-PDF_file_icon.svg.png"
|
|
142
|
+
|
|
143
|
+
response = engine.call_worker(:ocr_extractor, {
|
|
144
|
+
message: [
|
|
145
|
+
{ type: "text", text: "Extract and transcribe any text you see in this image." },
|
|
146
|
+
{ type: "image_url", image_url: text_image_url }
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
puts "User: [Sends image with text]"
|
|
150
|
+
puts "User: Extract and transcribe any text you see in this image."
|
|
151
|
+
puts "\nClaude: #{response}\n"
|
|
152
|
+
|
|
153
|
+
# Example 6: Image Classification
|
|
154
|
+
puts "\n6. Image Classification and Categorization"
|
|
155
|
+
puts "-" * 60
|
|
156
|
+
|
|
157
|
+
SmartPrompt.define_worker :image_classifier do
|
|
158
|
+
use "claude"
|
|
159
|
+
sys_msg("You are an expert at categorizing and classifying images.")
|
|
160
|
+
prompt(params[:message])
|
|
161
|
+
send_msg
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
classify_image = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/400px-Cat03.jpg"
|
|
165
|
+
|
|
166
|
+
response = engine.call_worker(:image_classifier, {
|
|
167
|
+
message: [
|
|
168
|
+
{ type: "text", text: "Classify this image into categories. Provide:\n1. Main category\n2. Sub-categories\n3. Tags/keywords\n4. Suitable use cases" },
|
|
169
|
+
{ type: "image_url", image_url: classify_image }
|
|
170
|
+
]
|
|
171
|
+
})
|
|
172
|
+
puts "User: [Sends image for classification]"
|
|
173
|
+
puts "\nClaude: #{response}\n"
|
|
174
|
+
|
|
175
|
+
# Example 7: Image-based Question Answering
|
|
176
|
+
puts "\n7. Question Answering about Image Content"
|
|
177
|
+
puts "-" * 60
|
|
178
|
+
|
|
179
|
+
SmartPrompt.define_worker :image_qa do
|
|
180
|
+
use "claude"
|
|
181
|
+
sys_msg("You are helpful at answering questions about images.")
|
|
182
|
+
prompt(params[:message])
|
|
183
|
+
send_msg
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
qa_image = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/400px-Cat03.jpg"
|
|
187
|
+
|
|
188
|
+
questions = [
|
|
189
|
+
"What color is the animal?",
|
|
190
|
+
"What is the animal doing?",
|
|
191
|
+
"What is the setting or environment?"
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
questions.each_with_index do |question, index|
|
|
195
|
+
response = engine.call_worker(:image_qa, {
|
|
196
|
+
message: [
|
|
197
|
+
{ type: "image_url", image_url: qa_image },
|
|
198
|
+
{ type: "text", text: question }
|
|
199
|
+
]
|
|
200
|
+
})
|
|
201
|
+
puts "Q#{index + 1}: #{question}"
|
|
202
|
+
puts "A#{index + 1}: #{response}\n"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
puts "\n" + "=" * 60
|
|
206
|
+
puts "Multimodal examples completed!"
|
|
207
|
+
puts "=" * 60
|
|
208
|
+
puts "\nNote: Claude's vision capabilities work best with:"
|
|
209
|
+
puts "- Clear, well-lit images"
|
|
210
|
+
puts "- Images in JPEG, PNG, GIF, or WebP format"
|
|
211
|
+
puts "- Images up to 5MB in size"
|
|
212
|
+
puts "- Both URLs and base64-encoded images"
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require './lib/smart_prompt'
|
|
5
|
+
|
|
6
|
+
# Example: Streaming Responses with Anthropic Claude
|
|
7
|
+
# This example demonstrates how to use Claude's streaming capabilities
|
|
8
|
+
|
|
9
|
+
puts "=" * 60
|
|
10
|
+
puts "Anthropic Claude - Streaming Response Example"
|
|
11
|
+
puts "=" * 60
|
|
12
|
+
|
|
13
|
+
# Initialize the engine with Anthropic configuration
|
|
14
|
+
engine = SmartPrompt::Engine.new('config/anthropic_config.yml')
|
|
15
|
+
|
|
16
|
+
# Example 1: Basic Streaming
|
|
17
|
+
puts "\n1. Basic Streaming Response"
|
|
18
|
+
puts "-" * 60
|
|
19
|
+
|
|
20
|
+
SmartPrompt.define_worker :streaming_chat do
|
|
21
|
+
use "claude"
|
|
22
|
+
sys_msg("You are a helpful assistant.")
|
|
23
|
+
prompt(params[:message])
|
|
24
|
+
send_msg
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
print "User: Tell me a short story about a brave knight.\n"
|
|
28
|
+
print "Claude (streaming): "
|
|
29
|
+
|
|
30
|
+
engine.call_worker_by_stream(:streaming_chat, {
|
|
31
|
+
message: "Tell me a short story about a brave knight."
|
|
32
|
+
}) do |chunk, bytesize|
|
|
33
|
+
# Handle Anthropic streaming format
|
|
34
|
+
if chunk.is_a?(Hash)
|
|
35
|
+
if chunk["type"] == "content_block_delta"
|
|
36
|
+
text = chunk.dig("delta", "text")
|
|
37
|
+
print text if text
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts "\n"
|
|
43
|
+
|
|
44
|
+
# Example 2: Streaming with Different Event Types
|
|
45
|
+
puts "\n2. Streaming with Event Type Handling"
|
|
46
|
+
puts "-" * 60
|
|
47
|
+
|
|
48
|
+
SmartPrompt.define_worker :detailed_streaming do
|
|
49
|
+
use "claude"
|
|
50
|
+
sys_msg("You are a knowledgeable teacher.")
|
|
51
|
+
prompt(params[:message])
|
|
52
|
+
send_msg
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
print "User: Explain how photosynthesis works.\n"
|
|
56
|
+
print "Claude: "
|
|
57
|
+
|
|
58
|
+
message_started = false
|
|
59
|
+
content_started = false
|
|
60
|
+
|
|
61
|
+
engine.call_worker_by_stream(:detailed_streaming, {
|
|
62
|
+
message: "Explain how photosynthesis works in 3-4 sentences."
|
|
63
|
+
}) do |chunk, bytesize|
|
|
64
|
+
if chunk.is_a?(Hash)
|
|
65
|
+
case chunk["type"]
|
|
66
|
+
when "message_start"
|
|
67
|
+
message_started = true
|
|
68
|
+
# Message metadata available in chunk["message"]
|
|
69
|
+
when "content_block_start"
|
|
70
|
+
content_started = true
|
|
71
|
+
# Content block started
|
|
72
|
+
when "content_block_delta"
|
|
73
|
+
text = chunk.dig("delta", "text")
|
|
74
|
+
print text if text
|
|
75
|
+
when "content_block_stop"
|
|
76
|
+
# Content block finished
|
|
77
|
+
when "message_delta"
|
|
78
|
+
# Message metadata update (e.g., stop_reason)
|
|
79
|
+
when "message_stop"
|
|
80
|
+
# Message completely finished
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
puts "\n"
|
|
86
|
+
|
|
87
|
+
# Example 3: Streaming Long-form Content
|
|
88
|
+
puts "\n3. Streaming Long-form Content"
|
|
89
|
+
puts "-" * 60
|
|
90
|
+
|
|
91
|
+
SmartPrompt.define_worker :long_form_streaming do
|
|
92
|
+
use "claude"
|
|
93
|
+
sys_msg("You are a creative writer who writes engaging stories.")
|
|
94
|
+
prompt(params[:message])
|
|
95
|
+
send_msg
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
print "User: Write a detailed story about a robot learning to paint.\n"
|
|
99
|
+
print "Claude: "
|
|
100
|
+
|
|
101
|
+
total_chars = 0
|
|
102
|
+
|
|
103
|
+
engine.call_worker_by_stream(:long_form_streaming, {
|
|
104
|
+
message: "Write a detailed story (about 200 words) about a robot learning to paint."
|
|
105
|
+
}) do |chunk, bytesize|
|
|
106
|
+
if chunk.is_a?(Hash) && chunk["type"] == "content_block_delta"
|
|
107
|
+
text = chunk.dig("delta", "text")
|
|
108
|
+
if text
|
|
109
|
+
print text
|
|
110
|
+
total_chars += text.length
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
puts "\n\n[Total characters streamed: #{total_chars}]"
|
|
116
|
+
|
|
117
|
+
# Example 4: Streaming with Progress Indicator
|
|
118
|
+
puts "\n4. Streaming with Progress Indicator"
|
|
119
|
+
puts "-" * 60
|
|
120
|
+
|
|
121
|
+
SmartPrompt.define_worker :progress_streaming do
|
|
122
|
+
use "claude"
|
|
123
|
+
sys_msg("You are a helpful coding assistant.")
|
|
124
|
+
prompt(params[:message])
|
|
125
|
+
send_msg
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
print "User: Write a Python function to calculate Fibonacci numbers.\n"
|
|
129
|
+
print "Claude: "
|
|
130
|
+
|
|
131
|
+
char_count = 0
|
|
132
|
+
last_dot_time = Time.now
|
|
133
|
+
|
|
134
|
+
engine.call_worker_by_stream(:progress_streaming, {
|
|
135
|
+
message: "Write a Python function to calculate Fibonacci numbers with comments."
|
|
136
|
+
}) do |chunk, bytesize|
|
|
137
|
+
if chunk.is_a?(Hash) && chunk["type"] == "content_block_delta"
|
|
138
|
+
text = chunk.dig("delta", "text")
|
|
139
|
+
if text
|
|
140
|
+
print text
|
|
141
|
+
char_count += text.length
|
|
142
|
+
|
|
143
|
+
# Print a progress indicator every 50 characters
|
|
144
|
+
if char_count % 50 == 0 && Time.now - last_dot_time > 0.5
|
|
145
|
+
# This is just for demonstration
|
|
146
|
+
last_dot_time = Time.now
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
puts "\n"
|
|
153
|
+
|
|
154
|
+
# Example 5: Streaming with Error Handling
|
|
155
|
+
puts "\n5. Streaming with Error Handling"
|
|
156
|
+
puts "-" * 60
|
|
157
|
+
|
|
158
|
+
SmartPrompt.define_worker :safe_streaming do
|
|
159
|
+
use "claude"
|
|
160
|
+
sys_msg("You are a helpful assistant.")
|
|
161
|
+
prompt(params[:message])
|
|
162
|
+
send_msg
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
print "User: What are the benefits of exercise?\n"
|
|
166
|
+
print "Claude: "
|
|
167
|
+
|
|
168
|
+
begin
|
|
169
|
+
engine.call_worker_by_stream(:safe_streaming, {
|
|
170
|
+
message: "What are the benefits of exercise? List 5 benefits."
|
|
171
|
+
}) do |chunk, bytesize|
|
|
172
|
+
if chunk.is_a?(Hash) && chunk["type"] == "content_block_delta"
|
|
173
|
+
text = chunk.dig("delta", "text")
|
|
174
|
+
print text if text
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
puts "\n[Stream completed successfully]"
|
|
178
|
+
rescue SmartPrompt::LLMAPIError => e
|
|
179
|
+
puts "\n[Error during streaming: #{e.message}]"
|
|
180
|
+
rescue StandardError => e
|
|
181
|
+
puts "\n[Unexpected error: #{e.message}]"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
puts ""
|
|
185
|
+
|
|
186
|
+
# Example 6: Streaming vs Non-Streaming Comparison
|
|
187
|
+
puts "\n6. Streaming vs Non-Streaming Comparison"
|
|
188
|
+
puts "-" * 60
|
|
189
|
+
|
|
190
|
+
question = "Explain the theory of relativity in simple terms."
|
|
191
|
+
|
|
192
|
+
# Non-streaming (traditional)
|
|
193
|
+
puts "Non-streaming mode:"
|
|
194
|
+
print "User: #{question}\n"
|
|
195
|
+
print "Claude: "
|
|
196
|
+
|
|
197
|
+
start_time = Time.now
|
|
198
|
+
|
|
199
|
+
SmartPrompt.define_worker :non_streaming_chat do
|
|
200
|
+
use "claude"
|
|
201
|
+
sys_msg("You are a physics teacher.")
|
|
202
|
+
prompt(params[:message])
|
|
203
|
+
send_msg
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
response = engine.call_worker(:non_streaming_chat, { message: question })
|
|
207
|
+
end_time = Time.now
|
|
208
|
+
|
|
209
|
+
puts response
|
|
210
|
+
puts "[Response received in #{(end_time - start_time).round(2)} seconds]\n"
|
|
211
|
+
|
|
212
|
+
# Streaming mode
|
|
213
|
+
puts "\nStreaming mode:"
|
|
214
|
+
print "User: #{question}\n"
|
|
215
|
+
print "Claude: "
|
|
216
|
+
|
|
217
|
+
start_time = Time.now
|
|
218
|
+
first_chunk_time = nil
|
|
219
|
+
|
|
220
|
+
SmartPrompt.define_worker :streaming_comparison do
|
|
221
|
+
use "claude"
|
|
222
|
+
sys_msg("You are a physics teacher.")
|
|
223
|
+
prompt(params[:message])
|
|
224
|
+
send_msg
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
engine.call_worker_by_stream(:streaming_comparison, { message: question }) do |chunk, bytesize|
|
|
228
|
+
if chunk.is_a?(Hash) && chunk["type"] == "content_block_delta"
|
|
229
|
+
first_chunk_time ||= Time.now
|
|
230
|
+
text = chunk.dig("delta", "text")
|
|
231
|
+
print text if text
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
end_time = Time.now
|
|
236
|
+
|
|
237
|
+
puts "\n[First chunk in #{(first_chunk_time - start_time).round(2)} seconds]"
|
|
238
|
+
puts "[Total time: #{(end_time - start_time).round(2)} seconds]\n"
|
|
239
|
+
|
|
240
|
+
# Example 7: Streaming with Different Models
|
|
241
|
+
puts "\n7. Streaming with Different Claude Models"
|
|
242
|
+
puts "-" * 60
|
|
243
|
+
|
|
244
|
+
question = "What is machine learning?"
|
|
245
|
+
|
|
246
|
+
# Claude 3.5 Sonnet
|
|
247
|
+
puts "Claude 3.5 Sonnet (streaming):"
|
|
248
|
+
print "User: #{question}\n"
|
|
249
|
+
print "Claude: "
|
|
250
|
+
|
|
251
|
+
SmartPrompt.define_worker :sonnet_streaming do
|
|
252
|
+
use "claude"
|
|
253
|
+
model "claude-3-5-sonnet-20241022"
|
|
254
|
+
prompt(params[:message])
|
|
255
|
+
send_msg
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
engine.call_worker_by_stream(:sonnet_streaming, { message: question }) do |chunk, bytesize|
|
|
259
|
+
if chunk.is_a?(Hash) && chunk["type"] == "content_block_delta"
|
|
260
|
+
text = chunk.dig("delta", "text")
|
|
261
|
+
print text if text
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
puts "\n"
|
|
266
|
+
|
|
267
|
+
# Claude 3.5 Haiku (faster)
|
|
268
|
+
puts "\nClaude 3.5 Haiku (streaming):"
|
|
269
|
+
print "User: #{question}\n"
|
|
270
|
+
print "Claude: "
|
|
271
|
+
|
|
272
|
+
SmartPrompt.define_worker :haiku_streaming do
|
|
273
|
+
use "claude_haiku"
|
|
274
|
+
model "claude-3-5-haiku-20241022"
|
|
275
|
+
prompt(params[:message])
|
|
276
|
+
send_msg
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
engine.call_worker_by_stream(:haiku_streaming, { message: question }) do |chunk, bytesize|
|
|
280
|
+
if chunk.is_a?(Hash) && chunk["type"] == "content_block_delta"
|
|
281
|
+
text = chunk.dig("delta", "text")
|
|
282
|
+
print text if text
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
puts "\n"
|
|
287
|
+
|
|
288
|
+
# Example 8: Streaming Best Practices
|
|
289
|
+
puts "\n8. Streaming Best Practices"
|
|
290
|
+
puts "-" * 60
|
|
291
|
+
|
|
292
|
+
puts "Best Practices for Streaming with Claude:"
|
|
293
|
+
puts "1. Use streaming for long-form content to improve perceived latency"
|
|
294
|
+
puts "2. Handle different event types appropriately:"
|
|
295
|
+
puts " - message_start: Initialize UI/state"
|
|
296
|
+
puts " - content_block_delta: Display incremental text"
|
|
297
|
+
puts " - message_stop: Finalize and clean up"
|
|
298
|
+
puts "3. Implement proper error handling for network issues"
|
|
299
|
+
puts "4. Consider buffering small chunks for smoother display"
|
|
300
|
+
puts "5. Show loading indicators before first chunk arrives"
|
|
301
|
+
puts "6. Use streaming for better user experience in chat applications"
|
|
302
|
+
puts "7. Non-streaming is better for short responses or batch processing"
|
|
303
|
+
puts "8. Test streaming behavior with different network conditions"
|
|
304
|
+
puts "9. Implement timeout handling for stalled streams"
|
|
305
|
+
puts "10. Consider token usage - streaming doesn't reduce costs\n"
|
|
306
|
+
|
|
307
|
+
puts "\n" + "=" * 60
|
|
308
|
+
puts "Streaming examples completed!"
|
|
309
|
+
puts "=" * 60
|
|
310
|
+
puts "\nNote: Streaming provides a better user experience by showing"
|
|
311
|
+
puts "responses as they're generated, reducing perceived latency."
|
|
312
|
+
puts "The total time and token usage is similar to non-streaming mode."
|