tsikol 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 +22 -0
- data/CONTRIBUTING.md +84 -0
- data/LICENSE +21 -0
- data/README.md +579 -0
- data/Rakefile +12 -0
- data/docs/README.md +69 -0
- data/docs/api/middleware.md +721 -0
- data/docs/api/prompt.md +858 -0
- data/docs/api/resource.md +651 -0
- data/docs/api/server.md +509 -0
- data/docs/api/test-helpers.md +591 -0
- data/docs/api/tool.md +527 -0
- data/docs/cookbook/authentication.md +651 -0
- data/docs/cookbook/caching.md +877 -0
- data/docs/cookbook/dynamic-tools.md +970 -0
- data/docs/cookbook/error-handling.md +887 -0
- data/docs/cookbook/logging.md +1044 -0
- data/docs/cookbook/rate-limiting.md +717 -0
- data/docs/examples/code-assistant.md +922 -0
- data/docs/examples/complete-server.md +726 -0
- data/docs/examples/database-manager.md +1198 -0
- data/docs/examples/devops-tools.md +1382 -0
- data/docs/examples/echo-server.md +501 -0
- data/docs/examples/weather-service.md +822 -0
- data/docs/guides/completion.md +472 -0
- data/docs/guides/getting-started.md +462 -0
- data/docs/guides/middleware.md +823 -0
- data/docs/guides/project-structure.md +434 -0
- data/docs/guides/prompts.md +920 -0
- data/docs/guides/resources.md +720 -0
- data/docs/guides/sampling.md +804 -0
- data/docs/guides/testing.md +863 -0
- data/docs/guides/tools.md +627 -0
- data/examples/README.md +92 -0
- data/examples/advanced_features.rb +129 -0
- data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
- data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
- data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
- data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
- data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
- data/examples/basic-migrated/server.rb +25 -0
- data/examples/basic.rb +73 -0
- data/examples/full_featured.rb +175 -0
- data/examples/middleware_example.rb +112 -0
- data/examples/sampling_example.rb +104 -0
- data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
- data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
- data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
- data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
- data/examples/weather-service/server.rb +28 -0
- data/exe/tsikol +6 -0
- data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
- data/lib/tsikol/cli/templates/README.md.erb +38 -0
- data/lib/tsikol/cli/templates/gitignore.erb +49 -0
- data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
- data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
- data/lib/tsikol/cli/templates/server.rb.erb +24 -0
- data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
- data/lib/tsikol/cli.rb +203 -0
- data/lib/tsikol/error_handler.rb +141 -0
- data/lib/tsikol/health.rb +198 -0
- data/lib/tsikol/http_transport.rb +72 -0
- data/lib/tsikol/lifecycle.rb +149 -0
- data/lib/tsikol/middleware.rb +168 -0
- data/lib/tsikol/prompt.rb +101 -0
- data/lib/tsikol/resource.rb +53 -0
- data/lib/tsikol/router.rb +190 -0
- data/lib/tsikol/server.rb +660 -0
- data/lib/tsikol/stdio_transport.rb +108 -0
- data/lib/tsikol/test_helpers.rb +261 -0
- data/lib/tsikol/tool.rb +111 -0
- data/lib/tsikol/version.rb +5 -0
- data/lib/tsikol.rb +72 -0
- metadata +219 -0
@@ -0,0 +1,920 @@
|
|
1
|
+
# Prompts Guide
|
2
|
+
|
3
|
+
Prompts enable MCP servers to provide reusable AI assistant configurations with dynamic arguments. They're perfect for creating specialized AI behaviors that clients can invoke.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
1. [What are Prompts?](#what-are-prompts)
|
8
|
+
2. [Creating Prompts](#creating-prompts)
|
9
|
+
3. [Arguments](#arguments)
|
10
|
+
4. [Message Formats](#message-formats)
|
11
|
+
5. [Dynamic Content](#dynamic-content)
|
12
|
+
6. [Advanced Patterns](#advanced-patterns)
|
13
|
+
7. [Testing Prompts](#testing-prompts)
|
14
|
+
|
15
|
+
## What are Prompts?
|
16
|
+
|
17
|
+
Prompts are pre-configured message templates that:
|
18
|
+
- Define AI assistant behaviors
|
19
|
+
- Accept dynamic arguments
|
20
|
+
- Return structured messages
|
21
|
+
- Can include system prompts, examples, and context
|
22
|
+
|
23
|
+
Think of prompts as reusable AI configurations.
|
24
|
+
|
25
|
+
## Creating Prompts
|
26
|
+
|
27
|
+
### Inline Prompts
|
28
|
+
|
29
|
+
Simple prompts in server.rb:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Tsikol.server "my-server" do
|
33
|
+
prompt "translator" do |language:, text:|
|
34
|
+
[
|
35
|
+
{
|
36
|
+
role: "system",
|
37
|
+
content: {
|
38
|
+
type: "text",
|
39
|
+
text: "You are a professional translator. Translate the text to #{language}."
|
40
|
+
}
|
41
|
+
},
|
42
|
+
{
|
43
|
+
role: "user",
|
44
|
+
content: {
|
45
|
+
type: "text",
|
46
|
+
text: text
|
47
|
+
}
|
48
|
+
}
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
prompt "code_reviewer" do |code:, language: "auto"|
|
53
|
+
[
|
54
|
+
{
|
55
|
+
role: "system",
|
56
|
+
content: {
|
57
|
+
type: "text",
|
58
|
+
text: "You are an expert code reviewer. Review the #{language} code for best practices, potential bugs, and improvements."
|
59
|
+
}
|
60
|
+
},
|
61
|
+
{
|
62
|
+
role: "user",
|
63
|
+
content: {
|
64
|
+
type: "text",
|
65
|
+
text: "Review this code:\n\n```#{language}\n#{code}\n```"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
### Class-Based Prompts
|
74
|
+
|
75
|
+
For complex prompts:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# app/prompts/sql_assistant.rb
|
79
|
+
class SqlAssistant < Tsikol::Prompt
|
80
|
+
name "sql_assistant"
|
81
|
+
description "Expert SQL query assistant with database schema awareness"
|
82
|
+
|
83
|
+
argument :query_description do
|
84
|
+
type :string
|
85
|
+
required
|
86
|
+
description "Natural language description of the desired query"
|
87
|
+
end
|
88
|
+
|
89
|
+
argument :database_type do
|
90
|
+
type :string
|
91
|
+
optional
|
92
|
+
default "postgresql"
|
93
|
+
enum ["postgresql", "mysql", "sqlite", "oracle"]
|
94
|
+
description "Target database system"
|
95
|
+
end
|
96
|
+
|
97
|
+
argument :include_schema do
|
98
|
+
type :boolean
|
99
|
+
optional
|
100
|
+
default true
|
101
|
+
description "Include database schema in context"
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_messages(query_description:, database_type: "postgresql", include_schema: true)
|
105
|
+
messages = []
|
106
|
+
|
107
|
+
# System prompt
|
108
|
+
messages << {
|
109
|
+
role: "system",
|
110
|
+
content: {
|
111
|
+
type: "text",
|
112
|
+
text: build_system_prompt(database_type)
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
# Include schema if requested
|
117
|
+
if include_schema
|
118
|
+
messages << {
|
119
|
+
role: "system",
|
120
|
+
content: {
|
121
|
+
type: "text",
|
122
|
+
text: "Database schema:\n#{fetch_schema}"
|
123
|
+
}
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Add examples
|
128
|
+
messages.concat(example_messages(database_type))
|
129
|
+
|
130
|
+
# User query
|
131
|
+
messages << {
|
132
|
+
role: "user",
|
133
|
+
content: {
|
134
|
+
type: "text",
|
135
|
+
text: query_description
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
messages
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def build_system_prompt(database_type)
|
145
|
+
<<~PROMPT
|
146
|
+
You are an expert SQL developer specializing in #{database_type}.
|
147
|
+
|
148
|
+
Your responsibilities:
|
149
|
+
1. Write efficient, optimized SQL queries
|
150
|
+
2. Follow #{database_type} best practices
|
151
|
+
3. Include helpful comments
|
152
|
+
4. Explain complex queries
|
153
|
+
5. Suggest indexes when appropriate
|
154
|
+
|
155
|
+
Always consider:
|
156
|
+
- Query performance
|
157
|
+
- Data integrity
|
158
|
+
- SQL injection prevention
|
159
|
+
- Database-specific features and syntax
|
160
|
+
PROMPT
|
161
|
+
end
|
162
|
+
|
163
|
+
def fetch_schema
|
164
|
+
# In real implementation, fetch from database
|
165
|
+
<<~SCHEMA
|
166
|
+
-- Users table
|
167
|
+
CREATE TABLE users (
|
168
|
+
id SERIAL PRIMARY KEY,
|
169
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
170
|
+
name VARCHAR(255),
|
171
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
172
|
+
);
|
173
|
+
|
174
|
+
-- Posts table
|
175
|
+
CREATE TABLE posts (
|
176
|
+
id SERIAL PRIMARY KEY,
|
177
|
+
user_id INTEGER REFERENCES users(id),
|
178
|
+
title VARCHAR(255) NOT NULL,
|
179
|
+
content TEXT,
|
180
|
+
published_at TIMESTAMP,
|
181
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
182
|
+
);
|
183
|
+
|
184
|
+
-- Comments table
|
185
|
+
CREATE TABLE comments (
|
186
|
+
id SERIAL PRIMARY KEY,
|
187
|
+
post_id INTEGER REFERENCES posts(id),
|
188
|
+
user_id INTEGER REFERENCES users(id),
|
189
|
+
content TEXT NOT NULL,
|
190
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
191
|
+
);
|
192
|
+
SCHEMA
|
193
|
+
end
|
194
|
+
|
195
|
+
def example_messages(database_type)
|
196
|
+
[
|
197
|
+
{
|
198
|
+
role: "user",
|
199
|
+
content: {
|
200
|
+
type: "text",
|
201
|
+
text: "Find all users who have posted in the last 30 days"
|
202
|
+
}
|
203
|
+
},
|
204
|
+
{
|
205
|
+
role: "assistant",
|
206
|
+
content: {
|
207
|
+
type: "text",
|
208
|
+
text: example_query_for(database_type)
|
209
|
+
}
|
210
|
+
}
|
211
|
+
]
|
212
|
+
end
|
213
|
+
|
214
|
+
def example_query_for(database_type)
|
215
|
+
case database_type
|
216
|
+
when "postgresql"
|
217
|
+
<<~SQL
|
218
|
+
-- Find users who have posted in the last 30 days
|
219
|
+
SELECT DISTINCT u.id, u.email, u.name
|
220
|
+
FROM users u
|
221
|
+
INNER JOIN posts p ON u.id = p.user_id
|
222
|
+
WHERE p.created_at >= CURRENT_DATE - INTERVAL '30 days'
|
223
|
+
ORDER BY u.name;
|
224
|
+
SQL
|
225
|
+
when "mysql"
|
226
|
+
<<~SQL
|
227
|
+
-- Find users who have posted in the last 30 days
|
228
|
+
SELECT DISTINCT u.id, u.email, u.name
|
229
|
+
FROM users u
|
230
|
+
INNER JOIN posts p ON u.id = p.user_id
|
231
|
+
WHERE p.created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
232
|
+
ORDER BY u.name;
|
233
|
+
SQL
|
234
|
+
else
|
235
|
+
"-- Example query for #{database_type}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
## Arguments
|
242
|
+
|
243
|
+
### Basic Arguments
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
argument :topic do
|
247
|
+
type :string
|
248
|
+
required
|
249
|
+
description "The topic to discuss"
|
250
|
+
end
|
251
|
+
|
252
|
+
argument :style do
|
253
|
+
type :string
|
254
|
+
optional
|
255
|
+
default "casual"
|
256
|
+
enum ["casual", "formal", "technical"]
|
257
|
+
description "Writing style"
|
258
|
+
end
|
259
|
+
```
|
260
|
+
|
261
|
+
### Argument Types
|
262
|
+
|
263
|
+
All supported argument types:
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
# String arguments
|
267
|
+
argument :name do
|
268
|
+
type :string
|
269
|
+
required
|
270
|
+
end
|
271
|
+
|
272
|
+
# Number arguments
|
273
|
+
argument :temperature do
|
274
|
+
type :number
|
275
|
+
optional
|
276
|
+
default 0.7
|
277
|
+
end
|
278
|
+
|
279
|
+
# Boolean arguments
|
280
|
+
argument :verbose do
|
281
|
+
type :boolean
|
282
|
+
optional
|
283
|
+
default false
|
284
|
+
end
|
285
|
+
|
286
|
+
# Array arguments
|
287
|
+
argument :topics do
|
288
|
+
type :array
|
289
|
+
optional
|
290
|
+
default []
|
291
|
+
end
|
292
|
+
|
293
|
+
# Object arguments
|
294
|
+
argument :config do
|
295
|
+
type :object
|
296
|
+
optional
|
297
|
+
default {}
|
298
|
+
end
|
299
|
+
```
|
300
|
+
|
301
|
+
### Argument Completion
|
302
|
+
|
303
|
+
Add autocomplete support:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
argument :programming_language do
|
307
|
+
type :string
|
308
|
+
required
|
309
|
+
|
310
|
+
complete do |partial|
|
311
|
+
languages = ["ruby", "python", "javascript", "go", "rust", "java"]
|
312
|
+
languages.select { |lang| lang.start_with?(partial.downcase) }
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
argument :file_path do
|
317
|
+
type :string
|
318
|
+
required
|
319
|
+
|
320
|
+
complete do |partial|
|
321
|
+
Dir.glob("#{partial}*").select { |f| File.file?(f) }
|
322
|
+
end
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
326
|
+
## Message Formats
|
327
|
+
|
328
|
+
### Basic Text Messages
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
def get_messages(topic:)
|
332
|
+
[
|
333
|
+
{
|
334
|
+
role: "system",
|
335
|
+
content: {
|
336
|
+
type: "text",
|
337
|
+
text: "You are a helpful assistant."
|
338
|
+
}
|
339
|
+
},
|
340
|
+
{
|
341
|
+
role: "user",
|
342
|
+
content: {
|
343
|
+
type: "text",
|
344
|
+
text: "Tell me about #{topic}"
|
345
|
+
}
|
346
|
+
}
|
347
|
+
]
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
### Multi-Modal Messages
|
352
|
+
|
353
|
+
Including images or other content:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
def get_messages(image_path:, question:)
|
357
|
+
[
|
358
|
+
{
|
359
|
+
role: "user",
|
360
|
+
content: [
|
361
|
+
{
|
362
|
+
type: "text",
|
363
|
+
text: question
|
364
|
+
},
|
365
|
+
{
|
366
|
+
type: "image",
|
367
|
+
data: Base64.encode64(File.read(image_path, mode: "rb")),
|
368
|
+
mimeType: "image/png"
|
369
|
+
}
|
370
|
+
]
|
371
|
+
}
|
372
|
+
]
|
373
|
+
end
|
374
|
+
```
|
375
|
+
|
376
|
+
### Conversation History
|
377
|
+
|
378
|
+
Include previous exchanges:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
def get_messages(topic:, history: [])
|
382
|
+
messages = [
|
383
|
+
{
|
384
|
+
role: "system",
|
385
|
+
content: {
|
386
|
+
type: "text",
|
387
|
+
text: "You are a helpful tutor. Build on previous conversations."
|
388
|
+
}
|
389
|
+
}
|
390
|
+
]
|
391
|
+
|
392
|
+
# Add history
|
393
|
+
messages.concat(history)
|
394
|
+
|
395
|
+
# Add new query
|
396
|
+
messages << {
|
397
|
+
role: "user",
|
398
|
+
content: {
|
399
|
+
type: "text",
|
400
|
+
text: "Continue explaining #{topic}"
|
401
|
+
}
|
402
|
+
}
|
403
|
+
|
404
|
+
messages
|
405
|
+
end
|
406
|
+
```
|
407
|
+
|
408
|
+
## Dynamic Content
|
409
|
+
|
410
|
+
### Resource Integration
|
411
|
+
|
412
|
+
Include data from resources:
|
413
|
+
|
414
|
+
```ruby
|
415
|
+
class DataAnalyst < Tsikol::Prompt
|
416
|
+
name "data_analyst"
|
417
|
+
description "Analyze data with current context"
|
418
|
+
|
419
|
+
argument :query do
|
420
|
+
type :string
|
421
|
+
required
|
422
|
+
end
|
423
|
+
|
424
|
+
def get_messages(query:)
|
425
|
+
# Fetch current data
|
426
|
+
current_metrics = fetch_current_metrics
|
427
|
+
|
428
|
+
[
|
429
|
+
{
|
430
|
+
role: "system",
|
431
|
+
content: {
|
432
|
+
type: "text",
|
433
|
+
text: "You are a data analyst. Analyze the provided metrics."
|
434
|
+
}
|
435
|
+
},
|
436
|
+
{
|
437
|
+
role: "system",
|
438
|
+
content: {
|
439
|
+
type: "text",
|
440
|
+
text: "Current metrics:\n#{current_metrics.to_json}"
|
441
|
+
}
|
442
|
+
},
|
443
|
+
{
|
444
|
+
role: "user",
|
445
|
+
content: {
|
446
|
+
type: "text",
|
447
|
+
text: query
|
448
|
+
}
|
449
|
+
}
|
450
|
+
]
|
451
|
+
end
|
452
|
+
|
453
|
+
private
|
454
|
+
|
455
|
+
def fetch_current_metrics
|
456
|
+
# Fetch from resources or database
|
457
|
+
{
|
458
|
+
users_total: 1523,
|
459
|
+
active_today: 234,
|
460
|
+
revenue_mtd: 45_000,
|
461
|
+
conversion_rate: 2.3
|
462
|
+
}
|
463
|
+
end
|
464
|
+
end
|
465
|
+
```
|
466
|
+
|
467
|
+
### Template Expansion
|
468
|
+
|
469
|
+
Use templates for complex prompts:
|
470
|
+
|
471
|
+
```ruby
|
472
|
+
class DocumentGenerator < Tsikol::Prompt
|
473
|
+
name "document_generator"
|
474
|
+
description "Generate documents from templates"
|
475
|
+
|
476
|
+
argument :document_type do
|
477
|
+
type :string
|
478
|
+
required
|
479
|
+
enum ["report", "proposal", "email", "article"]
|
480
|
+
end
|
481
|
+
|
482
|
+
argument :data do
|
483
|
+
type :object
|
484
|
+
required
|
485
|
+
description "Data to fill the template"
|
486
|
+
end
|
487
|
+
|
488
|
+
def get_messages(document_type:, data:)
|
489
|
+
template = load_template(document_type)
|
490
|
+
expanded = expand_template(template, data)
|
491
|
+
|
492
|
+
[
|
493
|
+
{
|
494
|
+
role: "system",
|
495
|
+
content: {
|
496
|
+
type: "text",
|
497
|
+
text: "You are a professional document writer. Generate a #{document_type} based on the template."
|
498
|
+
}
|
499
|
+
},
|
500
|
+
{
|
501
|
+
role: "user",
|
502
|
+
content: {
|
503
|
+
type: "text",
|
504
|
+
text: expanded
|
505
|
+
}
|
506
|
+
}
|
507
|
+
]
|
508
|
+
end
|
509
|
+
|
510
|
+
private
|
511
|
+
|
512
|
+
def load_template(type)
|
513
|
+
# Load from file or database
|
514
|
+
case type
|
515
|
+
when "report"
|
516
|
+
<<~TEMPLATE
|
517
|
+
Generate a report with:
|
518
|
+
- Title: {{title}}
|
519
|
+
- Period: {{start_date}} to {{end_date}}
|
520
|
+
- Key metrics: {{metrics}}
|
521
|
+
- Recommendations based on the data
|
522
|
+
TEMPLATE
|
523
|
+
when "email"
|
524
|
+
<<~TEMPLATE
|
525
|
+
Write a professional email:
|
526
|
+
- To: {{recipient}}
|
527
|
+
- Subject: {{subject}}
|
528
|
+
- Main points: {{points}}
|
529
|
+
- Tone: {{tone}}
|
530
|
+
TEMPLATE
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def expand_template(template, data)
|
535
|
+
result = template.dup
|
536
|
+
data.each do |key, value|
|
537
|
+
result.gsub!("{{#{key}}}", value.to_s)
|
538
|
+
end
|
539
|
+
result
|
540
|
+
end
|
541
|
+
end
|
542
|
+
```
|
543
|
+
|
544
|
+
## Advanced Patterns
|
545
|
+
|
546
|
+
### Chained Prompts
|
547
|
+
|
548
|
+
Build complex interactions:
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
class InterviewAssistant < Tsikol::Prompt
|
552
|
+
name "interview_assistant"
|
553
|
+
description "Conduct structured interviews"
|
554
|
+
|
555
|
+
argument :role do
|
556
|
+
type :string
|
557
|
+
required
|
558
|
+
description "Role being interviewed for"
|
559
|
+
end
|
560
|
+
|
561
|
+
argument :stage do
|
562
|
+
type :string
|
563
|
+
required
|
564
|
+
enum ["intro", "technical", "behavioral", "closing"]
|
565
|
+
end
|
566
|
+
|
567
|
+
argument :previous_answers do
|
568
|
+
type :array
|
569
|
+
optional
|
570
|
+
default []
|
571
|
+
end
|
572
|
+
|
573
|
+
def get_messages(role:, stage:, previous_answers: [])
|
574
|
+
messages = base_messages(role)
|
575
|
+
|
576
|
+
# Add context from previous stages
|
577
|
+
if previous_answers.any?
|
578
|
+
messages << {
|
579
|
+
role: "system",
|
580
|
+
content: {
|
581
|
+
type: "text",
|
582
|
+
text: "Previous answers:\n#{format_previous_answers(previous_answers)}"
|
583
|
+
}
|
584
|
+
}
|
585
|
+
end
|
586
|
+
|
587
|
+
# Add stage-specific prompt
|
588
|
+
messages << stage_message(stage, role)
|
589
|
+
|
590
|
+
messages
|
591
|
+
end
|
592
|
+
|
593
|
+
private
|
594
|
+
|
595
|
+
def base_messages(role)
|
596
|
+
[
|
597
|
+
{
|
598
|
+
role: "system",
|
599
|
+
content: {
|
600
|
+
type: "text",
|
601
|
+
text: <<~PROMPT
|
602
|
+
You are an experienced technical interviewer.
|
603
|
+
You're conducting an interview for a #{role} position.
|
604
|
+
Ask thoughtful questions and provide constructive feedback.
|
605
|
+
Maintain a professional but friendly tone.
|
606
|
+
PROMPT
|
607
|
+
}
|
608
|
+
}
|
609
|
+
]
|
610
|
+
end
|
611
|
+
|
612
|
+
def stage_message(stage, role)
|
613
|
+
content = case stage
|
614
|
+
when "intro"
|
615
|
+
"Start the interview. Introduce yourself and ask the candidate to tell you about themselves."
|
616
|
+
when "technical"
|
617
|
+
"Ask technical questions relevant to a #{role} position. Focus on practical problem-solving."
|
618
|
+
when "behavioral"
|
619
|
+
"Ask behavioral questions to assess culture fit and soft skills."
|
620
|
+
when "closing"
|
621
|
+
"Wrap up the interview. Ask if they have questions and explain next steps."
|
622
|
+
end
|
623
|
+
|
624
|
+
{
|
625
|
+
role: "user",
|
626
|
+
content: {
|
627
|
+
type: "text",
|
628
|
+
text: content
|
629
|
+
}
|
630
|
+
}
|
631
|
+
end
|
632
|
+
|
633
|
+
def format_previous_answers(answers)
|
634
|
+
answers.map.with_index do |answer, i|
|
635
|
+
"Q#{i + 1}: #{answer[:question]}\nA: #{answer[:answer]}"
|
636
|
+
end.join("\n\n")
|
637
|
+
end
|
638
|
+
end
|
639
|
+
```
|
640
|
+
|
641
|
+
### Adaptive Prompts
|
642
|
+
|
643
|
+
Adjust behavior based on context:
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
class AdaptiveAssistant < Tsikol::Prompt
|
647
|
+
name "adaptive_assistant"
|
648
|
+
description "Adapts communication style based on user"
|
649
|
+
|
650
|
+
argument :message do
|
651
|
+
type :string
|
652
|
+
required
|
653
|
+
end
|
654
|
+
|
655
|
+
argument :user_profile do
|
656
|
+
type :object
|
657
|
+
optional
|
658
|
+
default {}
|
659
|
+
end
|
660
|
+
|
661
|
+
def get_messages(message:, user_profile: {})
|
662
|
+
style = determine_style(user_profile)
|
663
|
+
expertise = user_profile["expertise_level"] || "intermediate"
|
664
|
+
|
665
|
+
[
|
666
|
+
{
|
667
|
+
role: "system",
|
668
|
+
content: {
|
669
|
+
type: "text",
|
670
|
+
text: build_adaptive_prompt(style, expertise)
|
671
|
+
}
|
672
|
+
},
|
673
|
+
{
|
674
|
+
role: "user",
|
675
|
+
content: {
|
676
|
+
type: "text",
|
677
|
+
text: message
|
678
|
+
}
|
679
|
+
}
|
680
|
+
]
|
681
|
+
end
|
682
|
+
|
683
|
+
private
|
684
|
+
|
685
|
+
def determine_style(profile)
|
686
|
+
return profile["preferred_style"] if profile["preferred_style"]
|
687
|
+
|
688
|
+
# Infer from other attributes
|
689
|
+
if profile["role"] == "executive"
|
690
|
+
"concise"
|
691
|
+
elsif profile["technical_background"]
|
692
|
+
"detailed"
|
693
|
+
else
|
694
|
+
"balanced"
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
def build_adaptive_prompt(style, expertise)
|
699
|
+
base = "You are an adaptive AI assistant."
|
700
|
+
|
701
|
+
style_guide = case style
|
702
|
+
when "concise"
|
703
|
+
"Be very brief and to the point. Use bullet points."
|
704
|
+
when "detailed"
|
705
|
+
"Provide comprehensive explanations with examples."
|
706
|
+
when "balanced"
|
707
|
+
"Strike a balance between clarity and detail."
|
708
|
+
end
|
709
|
+
|
710
|
+
expertise_guide = case expertise
|
711
|
+
when "beginner"
|
712
|
+
"Explain concepts simply, avoid jargon."
|
713
|
+
when "expert"
|
714
|
+
"Use technical terms freely, focus on advanced concepts."
|
715
|
+
else
|
716
|
+
"Adjust explanations to an intermediate level."
|
717
|
+
end
|
718
|
+
|
719
|
+
"#{base}\n#{style_guide}\n#{expertise_guide}"
|
720
|
+
end
|
721
|
+
end
|
722
|
+
```
|
723
|
+
|
724
|
+
### Prompt Composition
|
725
|
+
|
726
|
+
Combine multiple prompts:
|
727
|
+
|
728
|
+
```ruby
|
729
|
+
class CompositePrompt < Tsikol::Prompt
|
730
|
+
name "composite_prompt"
|
731
|
+
description "Combines multiple specialized prompts"
|
732
|
+
|
733
|
+
argument :task do
|
734
|
+
type :string
|
735
|
+
required
|
736
|
+
end
|
737
|
+
|
738
|
+
argument :components do
|
739
|
+
type :array
|
740
|
+
required
|
741
|
+
description "Which components to include"
|
742
|
+
end
|
743
|
+
|
744
|
+
def get_messages(task:, components:)
|
745
|
+
messages = []
|
746
|
+
|
747
|
+
# Add base system message
|
748
|
+
messages << {
|
749
|
+
role: "system",
|
750
|
+
content: {
|
751
|
+
type: "text",
|
752
|
+
text: "You are a multi-skilled AI assistant."
|
753
|
+
}
|
754
|
+
}
|
755
|
+
|
756
|
+
# Add component-specific instructions
|
757
|
+
components.each do |component|
|
758
|
+
messages.concat(component_messages(component))
|
759
|
+
end
|
760
|
+
|
761
|
+
# Add user task
|
762
|
+
messages << {
|
763
|
+
role: "user",
|
764
|
+
content: {
|
765
|
+
type: "text",
|
766
|
+
text: task
|
767
|
+
}
|
768
|
+
}
|
769
|
+
|
770
|
+
messages
|
771
|
+
end
|
772
|
+
|
773
|
+
private
|
774
|
+
|
775
|
+
def component_messages(component)
|
776
|
+
case component
|
777
|
+
when "coder"
|
778
|
+
coder_prompt_messages
|
779
|
+
when "writer"
|
780
|
+
writer_prompt_messages
|
781
|
+
when "analyst"
|
782
|
+
analyst_prompt_messages
|
783
|
+
else
|
784
|
+
[]
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
def coder_prompt_messages
|
789
|
+
[
|
790
|
+
{
|
791
|
+
role: "system",
|
792
|
+
content: {
|
793
|
+
type: "text",
|
794
|
+
text: "For coding tasks: Write clean, efficient, well-documented code."
|
795
|
+
}
|
796
|
+
}
|
797
|
+
]
|
798
|
+
end
|
799
|
+
|
800
|
+
# Similar for other components...
|
801
|
+
end
|
802
|
+
```
|
803
|
+
|
804
|
+
## Testing Prompts
|
805
|
+
|
806
|
+
### Basic Prompt Test
|
807
|
+
|
808
|
+
```ruby
|
809
|
+
require 'minitest/autorun'
|
810
|
+
require 'tsikol/test_helpers'
|
811
|
+
|
812
|
+
class SqlAssistantTest < Minitest::Test
|
813
|
+
include Tsikol::TestHelpers::Assertions
|
814
|
+
|
815
|
+
def setup
|
816
|
+
@server = Tsikol::Server.new(name: "test")
|
817
|
+
@server.register_prompt_instance(SqlAssistant.new)
|
818
|
+
@client = Tsikol::TestHelpers::TestClient.new(@server)
|
819
|
+
@client.initialize_connection
|
820
|
+
end
|
821
|
+
|
822
|
+
def test_basic_query
|
823
|
+
response = @client.get_prompt("sql_assistant", {
|
824
|
+
"query_description" => "Find all users created this week"
|
825
|
+
})
|
826
|
+
|
827
|
+
assert_successful_response(response)
|
828
|
+
|
829
|
+
messages = response.dig(:result, :messages)
|
830
|
+
assert messages.is_a?(Array)
|
831
|
+
assert messages.length >= 2
|
832
|
+
|
833
|
+
# Check system message
|
834
|
+
system_message = messages.find { |m| m[:role] == "system" }
|
835
|
+
assert system_message
|
836
|
+
assert_match /SQL developer/i, system_message[:content][:text]
|
837
|
+
end
|
838
|
+
|
839
|
+
def test_database_type_variation
|
840
|
+
["postgresql", "mysql", "sqlite"].each do |db_type|
|
841
|
+
response = @client.get_prompt("sql_assistant", {
|
842
|
+
"query_description" => "Create an index",
|
843
|
+
"database_type" => db_type
|
844
|
+
})
|
845
|
+
|
846
|
+
assert_successful_response(response)
|
847
|
+
|
848
|
+
# Verify database-specific content
|
849
|
+
content = response.dig(:result, :messages).map { |m|
|
850
|
+
m[:content][:text]
|
851
|
+
}.join(" ")
|
852
|
+
|
853
|
+
assert_match db_type, content
|
854
|
+
end
|
855
|
+
end
|
856
|
+
end
|
857
|
+
```
|
858
|
+
|
859
|
+
### Testing Dynamic Content
|
860
|
+
|
861
|
+
```ruby
|
862
|
+
class DataAnalystTest < Minitest::Test
|
863
|
+
def test_includes_current_metrics
|
864
|
+
# Mock metrics fetching
|
865
|
+
prompt = DataAnalyst.new
|
866
|
+
prompt.define_singleton_method(:fetch_current_metrics) do
|
867
|
+
{ test_metric: 123 }
|
868
|
+
end
|
869
|
+
|
870
|
+
messages = prompt.get_messages(query: "Analyze trends")
|
871
|
+
|
872
|
+
# Find the metrics message
|
873
|
+
metrics_message = messages.find { |m|
|
874
|
+
m[:content][:text].include?("test_metric")
|
875
|
+
}
|
876
|
+
|
877
|
+
assert metrics_message
|
878
|
+
assert_match /"test_metric":123/, metrics_message[:content][:text]
|
879
|
+
end
|
880
|
+
end
|
881
|
+
```
|
882
|
+
|
883
|
+
### Testing Argument Validation
|
884
|
+
|
885
|
+
```ruby
|
886
|
+
def test_required_arguments
|
887
|
+
# Missing required argument
|
888
|
+
response = @client.get_prompt("sql_assistant", {})
|
889
|
+
|
890
|
+
assert_error_response(response)
|
891
|
+
assert_match /query_description.*required/i, response[:error][:message]
|
892
|
+
end
|
893
|
+
|
894
|
+
def test_enum_validation
|
895
|
+
response = @client.get_prompt("sql_assistant", {
|
896
|
+
"query_description" => "Test",
|
897
|
+
"database_type" => "invalid_db"
|
898
|
+
})
|
899
|
+
|
900
|
+
assert_error_response(response)
|
901
|
+
assert_match /must be one of/i, response[:error][:message]
|
902
|
+
end
|
903
|
+
```
|
904
|
+
|
905
|
+
## Best Practices
|
906
|
+
|
907
|
+
1. **Clear Descriptions**: Help users understand what the prompt does
|
908
|
+
2. **Validate Arguments**: Use enums and types to prevent errors
|
909
|
+
3. **Provide Context**: Include relevant information in system messages
|
910
|
+
4. **Use Examples**: Show the AI what you expect
|
911
|
+
5. **Test Variations**: Ensure prompts work with different arguments
|
912
|
+
6. **Keep Focused**: Each prompt should have a clear purpose
|
913
|
+
7. **Document Behavior**: Explain what the prompt will generate
|
914
|
+
|
915
|
+
## Next Steps
|
916
|
+
|
917
|
+
- Learn about [Completion](completion.md)
|
918
|
+
- Explore [Sampling](sampling.md)
|
919
|
+
- Add [Middleware](middleware.md)
|
920
|
+
- Set up [Testing](testing.md)
|