open_router_enhanced 1.0.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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +1 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +13 -0
  5. data/.rubocop_todo.yml +130 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +41 -0
  8. data/CODE_OF_CONDUCT.md +84 -0
  9. data/CONTRIBUTING.md +384 -0
  10. data/Gemfile +22 -0
  11. data/Gemfile.lock +138 -0
  12. data/LICENSE.txt +21 -0
  13. data/MIGRATION.md +556 -0
  14. data/README.md +1660 -0
  15. data/Rakefile +334 -0
  16. data/SECURITY.md +150 -0
  17. data/VCR_CONFIGURATION.md +80 -0
  18. data/docs/model_selection.md +637 -0
  19. data/docs/observability.md +430 -0
  20. data/docs/prompt_templates.md +422 -0
  21. data/docs/streaming.md +467 -0
  22. data/docs/structured_outputs.md +466 -0
  23. data/docs/tools.md +1016 -0
  24. data/examples/basic_completion.rb +122 -0
  25. data/examples/model_selection_example.rb +141 -0
  26. data/examples/observability_example.rb +199 -0
  27. data/examples/prompt_template_example.rb +184 -0
  28. data/examples/smart_completion_example.rb +89 -0
  29. data/examples/streaming_example.rb +176 -0
  30. data/examples/structured_outputs_example.rb +191 -0
  31. data/examples/tool_calling_example.rb +149 -0
  32. data/lib/open_router/client.rb +552 -0
  33. data/lib/open_router/http.rb +118 -0
  34. data/lib/open_router/json_healer.rb +263 -0
  35. data/lib/open_router/model_registry.rb +378 -0
  36. data/lib/open_router/model_selector.rb +462 -0
  37. data/lib/open_router/prompt_template.rb +290 -0
  38. data/lib/open_router/response.rb +371 -0
  39. data/lib/open_router/schema.rb +288 -0
  40. data/lib/open_router/streaming_client.rb +210 -0
  41. data/lib/open_router/tool.rb +221 -0
  42. data/lib/open_router/tool_call.rb +180 -0
  43. data/lib/open_router/usage_tracker.rb +277 -0
  44. data/lib/open_router/version.rb +5 -0
  45. data/lib/open_router.rb +123 -0
  46. data/sig/open_router.rbs +20 -0
  47. metadata +186 -0
@@ -0,0 +1,422 @@
1
+ # Prompt Templates
2
+
3
+ The OpenRouter gem provides a flexible prompt template system inspired by LangChain, allowing you to create reusable, parameterized prompts with support for few-shot learning, chat formatting, and variable interpolation.
4
+
5
+ ## Quick Start
6
+
7
+ ```ruby
8
+ # Basic template
9
+ template = OpenRouter::PromptTemplate.new(
10
+ template: "Translate '{text}' from {source} to {target}",
11
+ input_variables: [:text, :source, :target]
12
+ )
13
+
14
+ prompt = template.format(
15
+ text: "Hello world",
16
+ source: "English",
17
+ target: "French"
18
+ )
19
+ # => "Translate 'Hello world' from English to French"
20
+
21
+ # Use with OpenRouter client
22
+ client = OpenRouter::Client.new
23
+ response = client.complete(
24
+ template.to_messages(text: "Hello", source: "English", target: "Spanish"),
25
+ model: "openai/gpt-4o-mini"
26
+ )
27
+ ```
28
+
29
+ ## Core Features
30
+
31
+ ### 1. Variable Interpolation
32
+
33
+ Templates support `{variable}` placeholders that get replaced with provided values:
34
+
35
+ ```ruby
36
+ template = OpenRouter::PromptTemplate.new(
37
+ template: "Hello {name}, welcome to {place}!",
38
+ input_variables: [:name, :place]
39
+ )
40
+
41
+ template.format(name: "Alice", place: "Wonderland")
42
+ # => "Hello Alice, welcome to Wonderland!"
43
+ ```
44
+
45
+ ### 2. Few-Shot Learning
46
+
47
+ Create templates with examples for consistent output formatting:
48
+
49
+ ```ruby
50
+ few_shot = OpenRouter::PromptTemplate.new(
51
+ prefix: "Classify sentiment as positive, negative, or neutral:",
52
+ examples: [
53
+ { text: "I love this!", sentiment: "positive" },
54
+ { text: "This is terrible", sentiment: "negative" },
55
+ { text: "It's okay", sentiment: "neutral" }
56
+ ],
57
+ example_template: "Text: {text}\nSentiment: {sentiment}",
58
+ suffix: "Text: {input}\nSentiment:",
59
+ input_variables: [:input]
60
+ )
61
+
62
+ prompt = few_shot.format(input: "This product is amazing!")
63
+ ```
64
+
65
+ Output:
66
+ ```
67
+ Classify sentiment as positive, negative, or neutral:
68
+
69
+ Text: I love this!
70
+ Sentiment: positive
71
+
72
+ Text: This is terrible
73
+ Sentiment: negative
74
+
75
+ Text: It's okay
76
+ Sentiment: neutral
77
+
78
+ Text: This product is amazing!
79
+ Sentiment:
80
+ ```
81
+
82
+ ### 3. Partial Templates
83
+
84
+ Pre-fill some variables for reuse:
85
+
86
+ ```ruby
87
+ base_template = OpenRouter::PromptTemplate.new(
88
+ template: "As a {role} expert, answer this {difficulty} question: {question}",
89
+ input_variables: [:role, :difficulty, :question]
90
+ )
91
+
92
+ # Create a partial with role pre-filled
93
+ python_expert = base_template.partial(role: "Python", difficulty: "beginner")
94
+
95
+ # Now only need to provide the question
96
+ python_expert.format(question: "What is a list comprehension?")
97
+ # => "As a Python expert, answer this beginner question: What is a list comprehension?"
98
+ ```
99
+
100
+ ### 4. Chat Message Formatting
101
+
102
+ Convert templates directly to OpenRouter message format:
103
+
104
+ ```ruby
105
+ chat_template = OpenRouter::PromptTemplate.new(
106
+ template: "System: You are a {role}.\nUser: {user_message}",
107
+ input_variables: [:role, :user_message]
108
+ )
109
+
110
+ messages = chat_template.to_messages(
111
+ role: "helpful assistant",
112
+ user_message: "Explain quantum computing"
113
+ )
114
+ # => [
115
+ # { role: "system", content: "You are a helpful assistant." },
116
+ # { role: "user", content: "Explain quantum computing" }
117
+ # ]
118
+
119
+ # Use directly with client
120
+ response = client.complete(messages, model: "anthropic/claude-3-haiku")
121
+ ```
122
+
123
+ ### 5. DSL for Template Creation
124
+
125
+ Use the builder DSL for more readable template definitions:
126
+
127
+ ```ruby
128
+ template = OpenRouter::PromptTemplate.build do
129
+ prefix "You are an expert code reviewer."
130
+
131
+ examples [
132
+ { code: "def add(a,b); a+b end", review: "Missing spaces after commas" },
133
+ { code: "def foo; 42; end", review: "Method name not descriptive" }
134
+ ]
135
+
136
+ example_template "Code: {code}\nReview: {review}"
137
+
138
+ suffix "Code: {input_code}\nReview:"
139
+
140
+ variables :input_code
141
+ end
142
+ ```
143
+
144
+ ## Factory Methods
145
+
146
+ The gem provides convenient factory methods for common patterns:
147
+
148
+ ```ruby
149
+ # Simple template
150
+ template = OpenRouter::Prompt.template(
151
+ "Summarize in {count} words: {text}",
152
+ variables: [:count, :text]
153
+ )
154
+
155
+ # Few-shot template
156
+ sentiment = OpenRouter::Prompt.few_shot(
157
+ prefix: "Classify sentiment:",
158
+ examples: [...],
159
+ example_template: "{text} -> {label}",
160
+ suffix: "{input} ->",
161
+ variables: [:input]
162
+ )
163
+
164
+ # Chat template with DSL
165
+ chat = OpenRouter::Prompt.chat do
166
+ template "System: {system}\nUser: {user}"
167
+ variables :system, :user
168
+ end
169
+ ```
170
+
171
+ ## Advanced Usage
172
+
173
+ ### Combining with Tools and Structured Outputs
174
+
175
+ Prompt templates work seamlessly with other OpenRouter features:
176
+
177
+ ```ruby
178
+ # Define a tool
179
+ weather_tool = OpenRouter::Tool.define do
180
+ name "get_weather"
181
+ parameters do
182
+ string :location, required: true
183
+ end
184
+ end
185
+
186
+ # Define output schema
187
+ response_schema = OpenRouter::Schema.define("analysis") do
188
+ string :summary, required: true
189
+ array :key_points, items: { type: "string" }
190
+ end
191
+
192
+ # Create template
193
+ analysis_template = OpenRouter::PromptTemplate.new(
194
+ template: "Analyze the weather data for {city} and provide insights",
195
+ input_variables: [:city]
196
+ )
197
+
198
+ # Use together
199
+ response = client.complete(
200
+ analysis_template.to_messages(city: "Tokyo"),
201
+ model: "openai/gpt-4o",
202
+ tools: [weather_tool],
203
+ response_format: response_schema
204
+ )
205
+ ```
206
+
207
+ ### Multi-Stage Prompting
208
+
209
+ Chain templates for complex workflows:
210
+
211
+ ```ruby
212
+ # Stage 1: Research
213
+ research_template = OpenRouter::PromptTemplate.new(
214
+ template: "Research {topic} and list 5 key facts",
215
+ input_variables: [:topic]
216
+ )
217
+
218
+ # Stage 2: Analysis
219
+ analysis_template = OpenRouter::PromptTemplate.new(
220
+ template: "Given these facts:\n{facts}\n\nProvide analysis on {aspect}",
221
+ input_variables: [:facts, :aspect]
222
+ )
223
+
224
+ # Stage 3: Summary
225
+ summary_template = OpenRouter::PromptTemplate.new(
226
+ template: "Summarize this analysis for a {audience} audience:\n{analysis}",
227
+ input_variables: [:analysis, :audience]
228
+ )
229
+
230
+ # Execute workflow
231
+ topic = "renewable energy"
232
+ facts_response = client.complete(
233
+ research_template.to_messages(topic: topic),
234
+ model: "openai/gpt-4o-mini"
235
+ )
236
+
237
+ analysis_response = client.complete(
238
+ analysis_template.to_messages(
239
+ facts: facts_response.content,
240
+ aspect: "economic impact"
241
+ ),
242
+ model: "openai/gpt-4o-mini"
243
+ )
244
+
245
+ summary_response = client.complete(
246
+ summary_template.to_messages(
247
+ analysis: analysis_response.content,
248
+ audience: "general public"
249
+ ),
250
+ model: "openai/gpt-4o-mini"
251
+ )
252
+ ```
253
+
254
+ ## Template Best Practices
255
+
256
+ ### 1. Clear Variable Names
257
+ Use descriptive variable names that indicate expected content:
258
+ ```ruby
259
+ # Good
260
+ template = "Translate {source_text} from {source_language} to {target_language}"
261
+
262
+ # Less clear
263
+ template = "Translate {text} from {lang1} to {lang2}"
264
+ ```
265
+
266
+ ### 2. Consistent Example Formatting
267
+ Keep examples consistent in structure and style:
268
+ ```ruby
269
+ examples = [
270
+ { input: "cat", output: "cats" }, # Consistent
271
+ { input: "dog", output: "dogs" }, # structure
272
+ { input: "fish", output: "fish" } # throughout
273
+ ]
274
+ ```
275
+
276
+ ### 3. Validate Required Variables
277
+ Always specify `input_variables` to catch missing values early:
278
+ ```ruby
279
+ template = OpenRouter::PromptTemplate.new(
280
+ template: "Hello {name}",
281
+ input_variables: [:name] # Enforces name is provided
282
+ )
283
+ ```
284
+
285
+ ### 4. Use Partials for Reusability
286
+ Create base templates and specialize them:
287
+ ```ruby
288
+ base = OpenRouter::PromptTemplate.new(
289
+ template: "As a {expertise} expert in {domain}, {task}",
290
+ input_variables: [:expertise, :domain, :task]
291
+ )
292
+
293
+ ruby_expert = base.partial(expertise: "Ruby", domain: "web development")
294
+ python_expert = base.partial(expertise: "Python", domain: "data science")
295
+ ```
296
+
297
+ ### 5. Role Markers for Chat
298
+ Use clear role markers for multi-turn conversations:
299
+ ```ruby
300
+ template = <<~TEMPLATE
301
+ System: You are a {character} with {trait}.
302
+
303
+ User: {user_question}
304
+
305
+ Assistant: I'll respond as a {character} would.
306
+ TEMPLATE
307
+ ```
308
+
309
+ ## Migration from LangChain
310
+
311
+ If you're familiar with LangChain's prompt templates, here's a comparison:
312
+
313
+ ### LangChain (Python/Ruby)
314
+ ```ruby
315
+ # LangChain style
316
+ template = Langchain::Prompt::FewShotPromptTemplate.new(
317
+ prefix: "Translate words:",
318
+ suffix: "Input: {word}\nOutput:",
319
+ example_prompt: Langchain::Prompt::PromptTemplate.new(
320
+ input_variables: ["input", "output"],
321
+ template: "Input: {input}\nOutput: {output}"
322
+ ),
323
+ examples: [
324
+ { "input" => "happy", "output" => "glad" }
325
+ ],
326
+ input_variables: ["word"]
327
+ )
328
+ ```
329
+
330
+ ### OpenRouter Enhanced
331
+ ```ruby
332
+ # OpenRouter style (simpler, more Ruby-idiomatic)
333
+ template = OpenRouter::PromptTemplate.new(
334
+ prefix: "Translate words:",
335
+ suffix: "Input: {word}\nOutput:",
336
+ example_template: "Input: {input}\nOutput: {output}",
337
+ examples: [
338
+ { input: "happy", output: "glad" }
339
+ ],
340
+ input_variables: [:word]
341
+ )
342
+ ```
343
+
344
+ Key differences:
345
+ - Simpler API with fewer nested objects
346
+ - Symbol keys instead of strings
347
+ - Built-in chat message formatting
348
+ - Direct integration with OpenRouter client
349
+ - No separate prompt classes for different patterns
350
+
351
+ ## API Reference
352
+
353
+ ### Class: `OpenRouter::PromptTemplate`
354
+
355
+ #### Constructor Options
356
+ - `template`: Main template string with `{variable}` placeholders
357
+ - `input_variables`: Array of required variable symbols
358
+ - `prefix`: Optional prefix text (for few-shot)
359
+ - `suffix`: Optional suffix text (for few-shot)
360
+ - `examples`: Array of example hashes
361
+ - `example_template`: Template string or PromptTemplate for formatting examples
362
+ - `partial_variables`: Hash of pre-filled variables
363
+
364
+ #### Methods
365
+ - `format(variables)`: Format template with variables
366
+ - `to_messages(variables)`: Convert to OpenRouter messages array
367
+ - `partial(variables)`: Create new template with partial variables
368
+ - `few_shot_template?`: Check if template uses examples
369
+
370
+ ### Module: `OpenRouter::Prompt`
371
+
372
+ Factory methods for template creation:
373
+ - `template(text, variables:)`: Create simple template
374
+ - `few_shot(prefix:, suffix:, examples:, example_template:, variables:)`: Create few-shot template
375
+ - `chat(&block)`: Create template using DSL
376
+
377
+ ## Complete Example
378
+
379
+ ```ruby
380
+ require "open_router"
381
+
382
+ # Configure client
383
+ client = OpenRouter::Client.new(access_token: ENV["OPENROUTER_API_KEY"])
384
+
385
+ # Create a sophisticated prompt template
386
+ code_review_template = OpenRouter::PromptTemplate.new(
387
+ prefix: "You are an expert code reviewer. Review code for quality, bugs, and improvements.",
388
+ examples: [
389
+ {
390
+ code: "def calculate_sum(arr)\n total = 0\n arr.each { |n| total += n }\n total\nend",
391
+ review: "Consider using arr.sum for better readability and performance."
392
+ },
393
+ {
394
+ code: "def get_user_name(id)\n User.find(id).name\nend",
395
+ review: "Add error handling for when user is not found. Consider: User.find(id)&.name"
396
+ }
397
+ ],
398
+ example_template: "Code:\n```ruby\n{code}\n```\nReview: {review}",
399
+ suffix: "Code:\n```ruby\n{input_code}\n```\nReview:",
400
+ input_variables: [:input_code]
401
+ )
402
+
403
+ # Format the prompt
404
+ code_to_review = <<~CODE
405
+ def process_payment(amount, card)
406
+ charge = card.charge(amount)
407
+ charge.success
408
+ end
409
+ CODE
410
+
411
+ prompt = code_review_template.format(input_code: code_to_review)
412
+
413
+ # Send to OpenRouter
414
+ response = client.complete(
415
+ [{ role: "user", content: prompt }],
416
+ model: "anthropic/claude-3.5-sonnet"
417
+ )
418
+
419
+ puts response.content
420
+ ```
421
+
422
+ This prompt template system provides a powerful, flexible way to manage prompts in your OpenRouter applications while maintaining consistency and reusability.