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,858 @@
1
+ # Prompt API Reference
2
+
3
+ The `Tsikol::Prompt` class provides the foundation for creating reusable AI assistant configurations with dynamic arguments.
4
+
5
+ ## Class: Tsikol::Prompt
6
+
7
+ ### Class Methods
8
+
9
+ #### `.name(value = nil)`
10
+
11
+ Set or get the prompt name.
12
+
13
+ ```ruby
14
+ class CodeReviewer < Tsikol::Prompt
15
+ name "code_reviewer"
16
+ end
17
+ ```
18
+
19
+ #### `.description(text = nil)`
20
+
21
+ Set or get the prompt description.
22
+
23
+ ```ruby
24
+ class CodeReviewer < Tsikol::Prompt
25
+ name "code_reviewer"
26
+ description "AI code review assistant for various programming languages"
27
+ end
28
+ ```
29
+
30
+ #### `.argument(name, &block)`
31
+
32
+ Define a prompt argument with validation and completion.
33
+
34
+ ```ruby
35
+ class TranslationPrompt < Tsikol::Prompt
36
+ argument :text do
37
+ type :string
38
+ required
39
+ description "Text to translate"
40
+ end
41
+
42
+ argument :target_language do
43
+ type :string
44
+ required
45
+ description "Target language for translation"
46
+
47
+ complete do |partial|
48
+ languages = ["spanish", "french", "german", "italian", "portuguese"]
49
+ languages.select { |lang| lang.start_with?(partial.downcase) }
50
+ end
51
+ end
52
+
53
+ argument :style do
54
+ type :string
55
+ optional
56
+ default "formal"
57
+ enum ["formal", "casual", "technical"]
58
+ description "Translation style"
59
+ end
60
+ end
61
+ ```
62
+
63
+ ### Argument DSL
64
+
65
+ Arguments use the same DSL as tool parameters:
66
+
67
+ #### `type(symbol)`
68
+
69
+ Set the argument type.
70
+
71
+ ```ruby
72
+ argument :count do
73
+ type :integer # :string, :number, :integer, :boolean, :array, :object
74
+ end
75
+ ```
76
+
77
+ #### `required` / `optional`
78
+
79
+ Specify if argument is required.
80
+
81
+ ```ruby
82
+ argument :topic do
83
+ type :string
84
+ required # or optional
85
+ end
86
+ ```
87
+
88
+ #### `default(value)`
89
+
90
+ Set default value for optional arguments.
91
+
92
+ ```ruby
93
+ argument :temperature do
94
+ type :number
95
+ optional
96
+ default 0.7
97
+ end
98
+ ```
99
+
100
+ #### `description(text)`
101
+
102
+ Describe the argument's purpose.
103
+
104
+ ```ruby
105
+ argument :context do
106
+ type :string
107
+ required
108
+ description "Background context for the AI"
109
+ end
110
+ ```
111
+
112
+ #### `enum(values)`
113
+
114
+ Restrict argument to specific values.
115
+
116
+ ```ruby
117
+ argument :language do
118
+ type :string
119
+ required
120
+ enum ["ruby", "python", "javascript", "go"]
121
+ description "Programming language"
122
+ end
123
+ ```
124
+
125
+ #### `complete(&block)`
126
+
127
+ Define completion logic.
128
+
129
+ ```ruby
130
+ argument :file_path do
131
+ type :string
132
+ required
133
+
134
+ complete do |partial|
135
+ Dir.glob("#{partial}*").select { |f| File.file?(f) }
136
+ end
137
+ end
138
+ ```
139
+
140
+ ### Instance Methods
141
+
142
+ #### `#get_messages(**args)`
143
+
144
+ Main method to implement prompt logic. Must return an array of message objects.
145
+
146
+ ```ruby
147
+ class SimplePrompt < Tsikol::Prompt
148
+ name "simple_prompt"
149
+
150
+ argument :task do
151
+ type :string
152
+ required
153
+ end
154
+
155
+ def get_messages(task:)
156
+ [
157
+ {
158
+ role: "system",
159
+ content: {
160
+ type: "text",
161
+ text: "You are a helpful assistant."
162
+ }
163
+ },
164
+ {
165
+ role: "user",
166
+ content: {
167
+ type: "text",
168
+ text: task
169
+ }
170
+ }
171
+ ]
172
+ end
173
+ end
174
+ ```
175
+
176
+ #### `#name`
177
+
178
+ Get the prompt name (instance method).
179
+
180
+ ```ruby
181
+ prompt = CodeReviewer.new
182
+ prompt.name # => "code_reviewer"
183
+ ```
184
+
185
+ #### `#set_server(server)`
186
+
187
+ Called when prompt is registered with a server. Override to access server features.
188
+
189
+ ```ruby
190
+ def set_server(server)
191
+ @server = server
192
+
193
+ # Access server resources if needed
194
+ if @server.resources["config"]
195
+ @config = JSON.parse(@server.resources["config"].read)
196
+ end
197
+ end
198
+ ```
199
+
200
+ ### Prompt Registration
201
+
202
+ Prompts can be registered in several ways:
203
+
204
+ #### Class Registration
205
+
206
+ ```ruby
207
+ server.register_prompt_class(CodeReviewer)
208
+ # or
209
+ server.prompt CodeReviewer
210
+ ```
211
+
212
+ #### Instance Registration
213
+
214
+ ```ruby
215
+ prompt = CodeReviewer.new
216
+ server.register_prompt_instance(prompt)
217
+ ```
218
+
219
+ #### Inline Registration
220
+
221
+ ```ruby
222
+ server.register_prompt("simple") do |message:|
223
+ [
224
+ {
225
+ role: "user",
226
+ content: { type: "text", text: message }
227
+ }
228
+ ]
229
+ end
230
+ # or
231
+ server.prompt "simple" do |message:|
232
+ [
233
+ {
234
+ role: "user",
235
+ content: { type: "text", text: message }
236
+ }
237
+ ]
238
+ end
239
+ ```
240
+
241
+ ### Message Format
242
+
243
+ Messages follow the OpenAI/Anthropic format:
244
+
245
+ #### Basic Text Message
246
+
247
+ ```ruby
248
+ {
249
+ role: "system", # or "user", "assistant"
250
+ content: {
251
+ type: "text",
252
+ text: "The message content"
253
+ }
254
+ }
255
+ ```
256
+
257
+ #### Multiple Content Types
258
+
259
+ ```ruby
260
+ {
261
+ role: "user",
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: "What's in this image?"
266
+ },
267
+ {
268
+ type: "image",
269
+ data: Base64.encode64(image_data),
270
+ mimeType: "image/png"
271
+ }
272
+ ]
273
+ }
274
+ ```
275
+
276
+ ### Complex Prompt Examples
277
+
278
+ #### Multi-Turn Conversation
279
+
280
+ ```ruby
281
+ class ConversationalPrompt < Tsikol::Prompt
282
+ name "conversation"
283
+ description "Multi-turn conversational AI"
284
+
285
+ argument :topic do
286
+ type :string
287
+ required
288
+ description "Conversation topic"
289
+ end
290
+
291
+ argument :history do
292
+ type :array
293
+ optional
294
+ default []
295
+ description "Previous conversation messages"
296
+ end
297
+
298
+ argument :personality do
299
+ type :string
300
+ optional
301
+ default "helpful"
302
+ enum ["helpful", "professional", "friendly", "concise"]
303
+ end
304
+
305
+ def get_messages(topic:, history: [], personality: "helpful")
306
+ messages = []
307
+
308
+ # System message with personality
309
+ messages << {
310
+ role: "system",
311
+ content: {
312
+ type: "text",
313
+ text: build_system_prompt(personality)
314
+ }
315
+ }
316
+
317
+ # Include conversation history
318
+ history.each do |msg|
319
+ messages << format_history_message(msg)
320
+ end
321
+
322
+ # Current user message
323
+ messages << {
324
+ role: "user",
325
+ content: {
326
+ type: "text",
327
+ text: topic
328
+ }
329
+ }
330
+
331
+ messages
332
+ end
333
+
334
+ private
335
+
336
+ def build_system_prompt(personality)
337
+ base = "You are a conversational AI assistant."
338
+
339
+ case personality
340
+ when "professional"
341
+ "#{base} Maintain a professional, business-appropriate tone."
342
+ when "friendly"
343
+ "#{base} Be warm, friendly, and conversational."
344
+ when "concise"
345
+ "#{base} Provide brief, direct responses."
346
+ else
347
+ "#{base} Be helpful and thorough in your responses."
348
+ end
349
+ end
350
+
351
+ def format_history_message(msg)
352
+ {
353
+ role: msg["role"] || "user",
354
+ content: {
355
+ type: "text",
356
+ text: msg["content"] || msg["text"]
357
+ }
358
+ }
359
+ end
360
+ end
361
+ ```
362
+
363
+ #### Dynamic Context Loading
364
+
365
+ ```ruby
366
+ class ContextAwarePrompt < Tsikol::Prompt
367
+ name "context_aware"
368
+ description "AI with dynamic context loading"
369
+
370
+ argument :query do
371
+ type :string
372
+ required
373
+ description "User query"
374
+ end
375
+
376
+ argument :context_sources do
377
+ type :array
378
+ optional
379
+ default ["docs", "code"]
380
+ description "Sources to load context from"
381
+ end
382
+
383
+ argument :max_context_length do
384
+ type :integer
385
+ optional
386
+ default 2000
387
+ description "Maximum context length in tokens"
388
+ end
389
+
390
+ def get_messages(query:, context_sources: ["docs", "code"], max_context_length: 2000)
391
+ messages = []
392
+
393
+ # System message
394
+ messages << {
395
+ role: "system",
396
+ content: {
397
+ type: "text",
398
+ text: "You are an AI assistant with access to project documentation and code."
399
+ }
400
+ }
401
+
402
+ # Load and add context
403
+ context_sources.each do |source|
404
+ context = load_context(source, query)
405
+ next if context.empty?
406
+
407
+ messages << {
408
+ role: "system",
409
+ content: {
410
+ type: "text",
411
+ text: "Context from #{source}:\n#{truncate(context, max_context_length)}"
412
+ }
413
+ }
414
+ end
415
+
416
+ # User query
417
+ messages << {
418
+ role: "user",
419
+ content: {
420
+ type: "text",
421
+ text: query
422
+ }
423
+ }
424
+
425
+ messages
426
+ end
427
+
428
+ private
429
+
430
+ def load_context(source, query)
431
+ case source
432
+ when "docs"
433
+ load_documentation_context(query)
434
+ when "code"
435
+ load_code_context(query)
436
+ when "data"
437
+ load_data_context(query)
438
+ else
439
+ ""
440
+ end
441
+ end
442
+
443
+ def load_documentation_context(query)
444
+ # Search documentation for relevant content
445
+ relevant_docs = search_docs(query)
446
+ relevant_docs.map { |doc| "#{doc[:title]}:\n#{doc[:content]}" }.join("\n\n")
447
+ end
448
+
449
+ def load_code_context(query)
450
+ # Search codebase for relevant code
451
+ relevant_files = search_code(query)
452
+ relevant_files.map { |file| "File: #{file[:path]}\n```\n#{file[:content]}\n```" }.join("\n\n")
453
+ end
454
+
455
+ def truncate(text, max_length)
456
+ return text if text.length <= max_length
457
+ text[0...max_length] + "... (truncated)"
458
+ end
459
+ end
460
+ ```
461
+
462
+ #### Template-Based Prompts
463
+
464
+ ```ruby
465
+ class TemplatePrompt < Tsikol::Prompt
466
+ name "template"
467
+ description "Template-based prompt generation"
468
+
469
+ argument :template_name do
470
+ type :string
471
+ required
472
+ enum ["bug_report", "feature_request", "code_review", "documentation"]
473
+ description "Template to use"
474
+ end
475
+
476
+ argument :variables do
477
+ type :object
478
+ required
479
+ description "Variables to fill in the template"
480
+ end
481
+
482
+ def get_messages(template_name:, variables:)
483
+ template = load_template(template_name)
484
+ filled_template = fill_template(template, variables)
485
+
486
+ [
487
+ {
488
+ role: "system",
489
+ content: {
490
+ type: "text",
491
+ text: template[:system_prompt]
492
+ }
493
+ },
494
+ {
495
+ role: "user",
496
+ content: {
497
+ type: "text",
498
+ text: filled_template
499
+ }
500
+ }
501
+ ]
502
+ end
503
+
504
+ private
505
+
506
+ def load_template(name)
507
+ case name
508
+ when "bug_report"
509
+ {
510
+ system_prompt: "You are a software engineer analyzing bug reports. Provide detailed analysis and potential solutions.",
511
+ template: <<~TEMPLATE
512
+ Bug Report Analysis Request
513
+
514
+ Title: {{title}}
515
+ Severity: {{severity}}
516
+ Component: {{component}}
517
+
518
+ Description:
519
+ {{description}}
520
+
521
+ Steps to Reproduce:
522
+ {{steps}}
523
+
524
+ Expected Behavior:
525
+ {{expected}}
526
+
527
+ Actual Behavior:
528
+ {{actual}}
529
+
530
+ Environment:
531
+ {{environment}}
532
+ TEMPLATE
533
+ }
534
+ when "code_review"
535
+ {
536
+ system_prompt: "You are an experienced code reviewer. Provide constructive feedback on code quality, best practices, and potential improvements.",
537
+ template: <<~TEMPLATE
538
+ Code Review Request
539
+
540
+ Language: {{language}}
541
+ Type: {{change_type}}
542
+
543
+ Description of Changes:
544
+ {{description}}
545
+
546
+ Code:
547
+ ```{{language}}
548
+ {{code}}
549
+ ```
550
+
551
+ Specific Concerns:
552
+ {{concerns}}
553
+ TEMPLATE
554
+ }
555
+ # ... other templates
556
+ end
557
+ end
558
+
559
+ def fill_template(template, variables)
560
+ result = template[:template].dup
561
+
562
+ variables.each do |key, value|
563
+ result.gsub!("{{#{key}}}", value.to_s)
564
+ end
565
+
566
+ # Check for missing variables
567
+ missing = result.scan(/\{\{(\w+)\}\}/).flatten
568
+ unless missing.empty?
569
+ raise Tsikol::ValidationError, "Missing template variables: #{missing.join(', ')}"
570
+ end
571
+
572
+ result
573
+ end
574
+ end
575
+ ```
576
+
577
+ ### Prompt Composition
578
+
579
+ Combine multiple prompts or behaviors:
580
+
581
+ ```ruby
582
+ class CompositePrompt < Tsikol::Prompt
583
+ name "composite"
584
+ description "Combines multiple AI behaviors"
585
+
586
+ argument :task do
587
+ type :string
588
+ required
589
+ end
590
+
591
+ argument :modes do
592
+ type :array
593
+ required
594
+ description "AI modes to activate"
595
+ end
596
+
597
+ argument :context do
598
+ type :object
599
+ optional
600
+ default {}
601
+ end
602
+
603
+ def get_messages(task:, modes:, context: {})
604
+ messages = []
605
+
606
+ # Base system message
607
+ messages << {
608
+ role: "system",
609
+ content: {
610
+ type: "text",
611
+ text: "You are a multi-skilled AI assistant."
612
+ }
613
+ }
614
+
615
+ # Add mode-specific instructions
616
+ modes.each do |mode|
617
+ mode_messages = get_mode_messages(mode, context)
618
+ messages.concat(mode_messages)
619
+ end
620
+
621
+ # Add the task
622
+ messages << {
623
+ role: "user",
624
+ content: {
625
+ type: "text",
626
+ text: task
627
+ }
628
+ }
629
+
630
+ messages
631
+ end
632
+
633
+ private
634
+
635
+ def get_mode_messages(mode, context)
636
+ case mode
637
+ when "researcher"
638
+ researcher_messages(context)
639
+ when "coder"
640
+ coder_messages(context)
641
+ when "analyst"
642
+ analyst_messages(context)
643
+ else
644
+ []
645
+ end
646
+ end
647
+
648
+ def researcher_messages(context)
649
+ [{
650
+ role: "system",
651
+ content: {
652
+ type: "text",
653
+ text: <<~PROMPT
654
+ As a researcher:
655
+ - Gather information from multiple sources
656
+ - Verify facts and cite sources
657
+ - Present balanced viewpoints
658
+ - Highlight key findings
659
+ PROMPT
660
+ }
661
+ }]
662
+ end
663
+
664
+ def coder_messages(context)
665
+ lang = context["language"] || "multiple languages"
666
+ [{
667
+ role: "system",
668
+ content: {
669
+ type: "text",
670
+ text: <<~PROMPT
671
+ As a coder working with #{lang}:
672
+ - Write clean, efficient code
673
+ - Follow best practices and conventions
674
+ - Include error handling
675
+ - Add helpful comments
676
+ PROMPT
677
+ }
678
+ }]
679
+ end
680
+ end
681
+ ```
682
+
683
+ ## Testing Prompts
684
+
685
+ ```ruby
686
+ require 'minitest/autorun'
687
+ require 'tsikol/test_helpers'
688
+
689
+ class PromptTest < Minitest::Test
690
+ include Tsikol::TestHelpers::Assertions
691
+
692
+ def setup
693
+ @server = Tsikol::Server.new(name: "test")
694
+ @server.register_prompt_instance(CodeReviewer.new)
695
+ @client = Tsikol::TestHelpers::TestClient.new(@server)
696
+ @client.initialize_connection
697
+ end
698
+
699
+ def test_basic_prompt
700
+ response = @client.get_prompt("code_reviewer", {
701
+ "code" => "def hello\n puts 'Hello'\nend",
702
+ "language" => "ruby"
703
+ })
704
+
705
+ assert_successful_response(response)
706
+
707
+ messages = response.dig(:result, :messages)
708
+ assert messages.is_a?(Array)
709
+ assert messages.length >= 2
710
+
711
+ # Verify system message
712
+ system_msg = messages.find { |m| m[:role] == "system" }
713
+ assert system_msg
714
+ assert_match /code review/i, system_msg[:content][:text]
715
+
716
+ # Verify user message contains code
717
+ user_msg = messages.find { |m| m[:role] == "user" }
718
+ assert user_msg
719
+ assert_match /def hello/, user_msg[:content][:text]
720
+ end
721
+
722
+ def test_argument_validation
723
+ # Missing required argument
724
+ response = @client.get_prompt("code_reviewer", {
725
+ "language" => "ruby"
726
+ # Missing "code" argument
727
+ })
728
+
729
+ assert_error_response(response, -32602)
730
+ assert_match /code.*required/i, response[:error][:message]
731
+ end
732
+
733
+ def test_enum_validation
734
+ response = @client.get_prompt("code_reviewer", {
735
+ "code" => "print('Hello')",
736
+ "language" => "cobol" # Not in enum
737
+ })
738
+
739
+ assert_error_response(response, -32602)
740
+ assert_match /must be one of/i, response[:error][:message]
741
+ end
742
+
743
+ def test_optional_arguments_with_defaults
744
+ response = @client.get_prompt("code_reviewer", {
745
+ "code" => "console.log('Hello');",
746
+ "language" => "javascript"
747
+ # "style" will use default value
748
+ })
749
+
750
+ assert_successful_response(response)
751
+
752
+ # Should include default style in prompt
753
+ messages = response.dig(:result, :messages)
754
+ text = messages.map { |m| m[:content][:text] }.join(" ")
755
+ assert_match /standard/i, text # Default style
756
+ end
757
+
758
+ def test_completion
759
+ response = @client.complete(
760
+ { type: "ref/prompt", name: "code_reviewer" },
761
+ { name: "language", value: "ja" }
762
+ )
763
+
764
+ assert_successful_response(response)
765
+ values = response.dig(:result, :completion, :values)
766
+ assert values.include?("javascript")
767
+ assert values.include?("java")
768
+ end
769
+ end
770
+ ```
771
+
772
+ ## Best Practices
773
+
774
+ ### Clear System Prompts
775
+
776
+ ```ruby
777
+ def get_messages(task:)
778
+ [
779
+ {
780
+ role: "system",
781
+ content: {
782
+ type: "text",
783
+ text: <<~PROMPT
784
+ You are an AI assistant specialized in #{specialty}.
785
+
786
+ Your responsibilities:
787
+ 1. Provide accurate information
788
+ 2. Follow best practices
789
+ 3. Explain your reasoning
790
+
791
+ Constraints:
792
+ - Stay within your area of expertise
793
+ - Acknowledge when you're unsure
794
+ - Provide sources when possible
795
+ PROMPT
796
+ }
797
+ },
798
+ {
799
+ role: "user",
800
+ content: { type: "text", text: task }
801
+ }
802
+ ]
803
+ end
804
+ ```
805
+
806
+ ### Dynamic Context
807
+
808
+ ```ruby
809
+ def get_messages(query:, include_context: true)
810
+ messages = [base_system_message]
811
+
812
+ if include_context
813
+ # Add relevant context
814
+ messages << context_message(query)
815
+ end
816
+
817
+ # Add examples if helpful
818
+ if would_benefit_from_examples?(query)
819
+ messages.concat(example_messages)
820
+ end
821
+
822
+ messages << user_message(query)
823
+ messages
824
+ end
825
+ ```
826
+
827
+ ### Error Handling
828
+
829
+ ```ruby
830
+ def get_messages(**args)
831
+ validate_arguments!(args)
832
+
833
+ messages = build_messages(args)
834
+
835
+ # Ensure valid message format
836
+ validate_messages!(messages)
837
+
838
+ messages
839
+ rescue => e
840
+ # Return minimal valid prompt on error
841
+ [
842
+ {
843
+ role: "user",
844
+ content: {
845
+ type: "text",
846
+ text: "Error building prompt: #{e.message}"
847
+ }
848
+ }
849
+ ]
850
+ end
851
+ ```
852
+
853
+ ## See Also
854
+
855
+ - [Prompts Guide](../guides/prompts.md)
856
+ - [Server API](server.md)
857
+ - [Testing Guide](../guides/testing.md)
858
+ - [Sampling Guide](../guides/sampling.md)