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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/CONTRIBUTING.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +579 -0
  6. data/Rakefile +12 -0
  7. data/docs/README.md +69 -0
  8. data/docs/api/middleware.md +721 -0
  9. data/docs/api/prompt.md +858 -0
  10. data/docs/api/resource.md +651 -0
  11. data/docs/api/server.md +509 -0
  12. data/docs/api/test-helpers.md +591 -0
  13. data/docs/api/tool.md +527 -0
  14. data/docs/cookbook/authentication.md +651 -0
  15. data/docs/cookbook/caching.md +877 -0
  16. data/docs/cookbook/dynamic-tools.md +970 -0
  17. data/docs/cookbook/error-handling.md +887 -0
  18. data/docs/cookbook/logging.md +1044 -0
  19. data/docs/cookbook/rate-limiting.md +717 -0
  20. data/docs/examples/code-assistant.md +922 -0
  21. data/docs/examples/complete-server.md +726 -0
  22. data/docs/examples/database-manager.md +1198 -0
  23. data/docs/examples/devops-tools.md +1382 -0
  24. data/docs/examples/echo-server.md +501 -0
  25. data/docs/examples/weather-service.md +822 -0
  26. data/docs/guides/completion.md +472 -0
  27. data/docs/guides/getting-started.md +462 -0
  28. data/docs/guides/middleware.md +823 -0
  29. data/docs/guides/project-structure.md +434 -0
  30. data/docs/guides/prompts.md +920 -0
  31. data/docs/guides/resources.md +720 -0
  32. data/docs/guides/sampling.md +804 -0
  33. data/docs/guides/testing.md +863 -0
  34. data/docs/guides/tools.md +627 -0
  35. data/examples/README.md +92 -0
  36. data/examples/advanced_features.rb +129 -0
  37. data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
  38. data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
  39. data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
  40. data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
  41. data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
  42. data/examples/basic-migrated/server.rb +25 -0
  43. data/examples/basic.rb +73 -0
  44. data/examples/full_featured.rb +175 -0
  45. data/examples/middleware_example.rb +112 -0
  46. data/examples/sampling_example.rb +104 -0
  47. data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
  48. data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
  49. data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
  50. data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
  51. data/examples/weather-service/server.rb +28 -0
  52. data/exe/tsikol +6 -0
  53. data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
  54. data/lib/tsikol/cli/templates/README.md.erb +38 -0
  55. data/lib/tsikol/cli/templates/gitignore.erb +49 -0
  56. data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
  57. data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
  58. data/lib/tsikol/cli/templates/server.rb.erb +24 -0
  59. data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
  60. data/lib/tsikol/cli.rb +203 -0
  61. data/lib/tsikol/error_handler.rb +141 -0
  62. data/lib/tsikol/health.rb +198 -0
  63. data/lib/tsikol/http_transport.rb +72 -0
  64. data/lib/tsikol/lifecycle.rb +149 -0
  65. data/lib/tsikol/middleware.rb +168 -0
  66. data/lib/tsikol/prompt.rb +101 -0
  67. data/lib/tsikol/resource.rb +53 -0
  68. data/lib/tsikol/router.rb +190 -0
  69. data/lib/tsikol/server.rb +660 -0
  70. data/lib/tsikol/stdio_transport.rb +108 -0
  71. data/lib/tsikol/test_helpers.rb +261 -0
  72. data/lib/tsikol/tool.rb +111 -0
  73. data/lib/tsikol/version.rb +5 -0
  74. data/lib/tsikol.rb +72 -0
  75. 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)