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.
- checksums.yaml +7 -0
- data/.env.example +1 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +130 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +41 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +384 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +138 -0
- data/LICENSE.txt +21 -0
- data/MIGRATION.md +556 -0
- data/README.md +1660 -0
- data/Rakefile +334 -0
- data/SECURITY.md +150 -0
- data/VCR_CONFIGURATION.md +80 -0
- data/docs/model_selection.md +637 -0
- data/docs/observability.md +430 -0
- data/docs/prompt_templates.md +422 -0
- data/docs/streaming.md +467 -0
- data/docs/structured_outputs.md +466 -0
- data/docs/tools.md +1016 -0
- data/examples/basic_completion.rb +122 -0
- data/examples/model_selection_example.rb +141 -0
- data/examples/observability_example.rb +199 -0
- data/examples/prompt_template_example.rb +184 -0
- data/examples/smart_completion_example.rb +89 -0
- data/examples/streaming_example.rb +176 -0
- data/examples/structured_outputs_example.rb +191 -0
- data/examples/tool_calling_example.rb +149 -0
- data/lib/open_router/client.rb +552 -0
- data/lib/open_router/http.rb +118 -0
- data/lib/open_router/json_healer.rb +263 -0
- data/lib/open_router/model_registry.rb +378 -0
- data/lib/open_router/model_selector.rb +462 -0
- data/lib/open_router/prompt_template.rb +290 -0
- data/lib/open_router/response.rb +371 -0
- data/lib/open_router/schema.rb +288 -0
- data/lib/open_router/streaming_client.rb +210 -0
- data/lib/open_router/tool.rb +221 -0
- data/lib/open_router/tool_call.rb +180 -0
- data/lib/open_router/usage_tracker.rb +277 -0
- data/lib/open_router/version.rb +5 -0
- data/lib/open_router.rb +123 -0
- data/sig/open_router.rbs +20 -0
- 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.
|