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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -10
  3. data/README.cn.md +307 -64
  4. data/README.md +311 -64
  5. data/Rakefile +10 -1
  6. data/config/anthropic_config.yml +151 -0
  7. data/config/image_generation_config.yml +22 -0
  8. data/config/multimodal_config.yml +85 -0
  9. data/config/sensenova_config.yml +63 -0
  10. data/config/zhipu_config.yml +73 -0
  11. data/examples/anthropic_basic_chat.rb +143 -0
  12. data/examples/anthropic_example.rb +232 -0
  13. data/examples/anthropic_multimodal.rb +212 -0
  14. data/examples/anthropic_streaming.rb +312 -0
  15. data/examples/anthropic_tool_calling.rb +393 -0
  16. data/examples/automatic_cleanup_example.rb +109 -0
  17. data/examples/history_management_examples.rb +522 -0
  18. data/examples/image_generation_example.rb +130 -0
  19. data/examples/monitoring_example.rb +121 -0
  20. data/examples/multimodal_example.rb +63 -0
  21. data/examples/relevance_based_strategy_example.rb +87 -0
  22. data/examples/sensenova_example.rb +129 -0
  23. data/examples/stt_example.rb +287 -0
  24. data/examples/tts_example.rb +244 -0
  25. data/examples/video_generation_example.rb +189 -0
  26. data/examples/zhipu_example.rb +151 -0
  27. data/lib/smart_prompt/anthropic_adapter.rb +363 -281
  28. data/lib/smart_prompt/compression_engine.rb +201 -0
  29. data/lib/smart_prompt/context_strategy.rb +22 -0
  30. data/lib/smart_prompt/conversation.rb +81 -191
  31. data/lib/smart_prompt/engine.rb +36 -19
  32. data/lib/smart_prompt/history_manager.rb +596 -0
  33. data/lib/smart_prompt/hybrid_strategy.rb +222 -0
  34. data/lib/smart_prompt/image_generation_adapter.rb +297 -0
  35. data/lib/smart_prompt/lru_cache.rb +133 -0
  36. data/lib/smart_prompt/message.rb +57 -0
  37. data/lib/smart_prompt/multimodal_adapter.rb +277 -0
  38. data/lib/smart_prompt/openai_adapter.rb +1 -25
  39. data/lib/smart_prompt/persistence_layer.rb +197 -0
  40. data/lib/smart_prompt/relevance_based_strategy.rb +221 -0
  41. data/lib/smart_prompt/sensenova_adapter.rb +410 -0
  42. data/lib/smart_prompt/session.rb +140 -0
  43. data/lib/smart_prompt/sliding_window_strategy.rb +100 -0
  44. data/lib/smart_prompt/stt_adapter.rb +381 -0
  45. data/lib/smart_prompt/summary_based_strategy.rb +152 -0
  46. data/lib/smart_prompt/token_counter.rb +74 -0
  47. data/lib/smart_prompt/tts_adapter.rb +403 -0
  48. data/lib/smart_prompt/version.rb +1 -1
  49. data/lib/smart_prompt/video_generation_adapter.rb +330 -0
  50. data/lib/smart_prompt/worker.rb +25 -3
  51. data/lib/smart_prompt/zhipu_adapter.rb +616 -0
  52. data/lib/smart_prompt.rb +22 -2
  53. data/workers/history_management_examples.rb +407 -0
  54. data/workers/image_generation_workers.rb +119 -0
  55. data/workers/multimodal_workers.rb +110 -0
  56. data/workers/sensenova_workers.rb +62 -0
  57. data/workers/stt_workers.rb +195 -0
  58. data/workers/tts_workers.rb +388 -0
  59. data/workers/video_generation_workers.rb +264 -0
  60. data/workers/zhipu_workers.rb +113 -0
  61. 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."