poml 0.0.6 → 0.0.8

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/docs/tutorial/advanced/performance.md +695 -0
  3. data/docs/tutorial/advanced/tool-registration.md +776 -0
  4. data/docs/tutorial/basic-usage.md +351 -0
  5. data/docs/tutorial/components/chat-components.md +552 -0
  6. data/docs/tutorial/components/formatting.md +623 -0
  7. data/docs/tutorial/components/index.md +366 -0
  8. data/docs/tutorial/components/media-components.md +259 -0
  9. data/docs/tutorial/components/schema-components.md +668 -0
  10. data/docs/tutorial/index.md +185 -0
  11. data/docs/tutorial/output-formats.md +689 -0
  12. data/docs/tutorial/quickstart.md +30 -0
  13. data/docs/tutorial/template-engine.md +540 -0
  14. data/lib/poml/components/base.rb +146 -4
  15. data/lib/poml/components/content.rb +10 -3
  16. data/lib/poml/components/data.rb +539 -19
  17. data/lib/poml/components/examples.rb +235 -1
  18. data/lib/poml/components/formatting.rb +184 -18
  19. data/lib/poml/components/layout.rb +7 -2
  20. data/lib/poml/components/lists.rb +69 -35
  21. data/lib/poml/components/meta.rb +134 -5
  22. data/lib/poml/components/output_schema.rb +19 -1
  23. data/lib/poml/components/template.rb +72 -61
  24. data/lib/poml/components/text.rb +30 -1
  25. data/lib/poml/components/tool.rb +81 -0
  26. data/lib/poml/components/tool_definition.rb +339 -10
  27. data/lib/poml/components/tools.rb +14 -0
  28. data/lib/poml/components/utilities.rb +34 -18
  29. data/lib/poml/components.rb +19 -0
  30. data/lib/poml/context.rb +19 -4
  31. data/lib/poml/parser.rb +88 -63
  32. data/lib/poml/renderer.rb +191 -9
  33. data/lib/poml/template_engine.rb +138 -13
  34. data/lib/poml/version.rb +1 -1
  35. data/lib/poml.rb +16 -1
  36. data/readme.md +157 -30
  37. metadata +87 -4
  38. data/TUTORIAL.md +0 -987
@@ -0,0 +1,689 @@
1
+ # Output Formats
2
+
3
+ POML supports multiple output formats to integrate seamlessly with different AI services, frameworks, and applications.
4
+
5
+ ## Two Types of Format Control
6
+
7
+ POML provides **two distinct levels** of format control:
8
+
9
+ ### 1. Output Structure Formats
10
+
11
+ Control the **overall structure** of the result (API format):
12
+
13
+ - `format: 'raw'` - Plain text with role boundaries
14
+ - `format: 'dict'` - Ruby Hash with metadata (default)
15
+ - `format: 'openai_chat'` - OpenAI message arrays
16
+ - `format: 'openaiResponse'` - Standardized AI response
17
+ - `format: 'langchain'` - LangChain compatible
18
+ - `format: 'pydantic'` - Python interoperability
19
+
20
+ ### 2. Content Rendering Formats
21
+
22
+ Control how **components render** within the content using `<output format="...">content</output>`:
23
+
24
+ - `format="markdown"` - Markdown syntax (default): `# Header`, `**bold**`
25
+ - `format="html"` - HTML tags: `<h1>Header</h1>`, `<b>bold</b>`
26
+ - `format="text"` - Plain text without markup
27
+ - `format="json"` - JSON representation of content
28
+ - `format="xml"` - XML-structured content
29
+
30
+ **Example combining both:**
31
+
32
+ ```ruby
33
+ markup = <<~POML
34
+ <poml>
35
+ <role>Writer</role>
36
+ <output format="html">
37
+ <h1>Article Title</h1>
38
+ <p>Content with <b>emphasis</b></p>
39
+ </output>
40
+ </poml>
41
+ POML
42
+
43
+ # Structure format
44
+ result = Poml.process(markup: markup, format: 'dict')
45
+ puts result['output'] # HTML: <h1>Article Title</h1><p>Content with <b>emphasis</b></p>
46
+ ```
47
+
48
+ ## Available Formats
49
+
50
+ POML provides six main output formats:
51
+
52
+ - **raw** - Plain text with role boundaries
53
+ - **dict** - Ruby Hash with structured metadata
54
+ - **openai_chat** - OpenAI API compatible message arrays
55
+ - **openaiResponse** - Standardized AI response structure
56
+ - **langchain** - LangChain compatible format
57
+ - **pydantic** - Python interoperability with strict schemas
58
+
59
+ ## Raw Format
60
+
61
+ The raw format outputs plain text with role boundaries, perfect for direct use with AI APIs:
62
+
63
+ ```ruby
64
+ require 'poml'
65
+
66
+ markup = <<~POML
67
+ <poml>
68
+ <role>Ruby Expert</role>
69
+ <task>Review code for best practices</task>
70
+ <hint>Focus on performance and readability</hint>
71
+ </poml>
72
+ POML
73
+
74
+ result = Poml.process(markup: markup, format: 'raw')
75
+ puts result
76
+ ```
77
+
78
+ Output:
79
+
80
+ ```
81
+ ===== system =====
82
+
83
+ # Role
84
+
85
+ Ruby Expert
86
+
87
+ # Task
88
+
89
+ Review code for best practices
90
+
91
+ # Hint
92
+
93
+ Focus on performance and readability
94
+ ```
95
+
96
+ ### Raw Format with Chat Components
97
+
98
+ ```ruby
99
+ markup = <<~POML
100
+ <poml>
101
+ <system>You are a helpful programming assistant.</system>
102
+ <human>How do I optimize this Ruby code?</human>
103
+ </poml>
104
+ POML
105
+
106
+ result = Poml.process(markup: markup, format: 'raw')
107
+ ```
108
+
109
+ Output:
110
+
111
+ ```
112
+ ===== system =====
113
+
114
+ You are a helpful programming assistant.
115
+
116
+ ===== human =====
117
+
118
+ How do I optimize this Ruby code?
119
+ ```
120
+
121
+ ## Dictionary Format (Default)
122
+
123
+ The dictionary format returns a Ruby Hash with structured content and metadata:
124
+
125
+ ```ruby
126
+ markup = <<~POML
127
+ <poml>
128
+ <role>Data Analyst</role>
129
+ <task>Analyze sales data</task>
130
+ <meta variables='{"quarter": "Q4", "year": 2024}' />
131
+ </poml>
132
+ POML
133
+
134
+ result = Poml.process(markup: markup, format: 'dict')
135
+ # or simply: result = Poml.process(markup: markup)
136
+
137
+ puts result.class # Hash
138
+ puts result['content'] # The formatted prompt
139
+ puts result['metadata'] # Additional information
140
+ ```
141
+
142
+ Structure:
143
+
144
+ ```ruby
145
+ {
146
+ 'content' => "The formatted prompt text",
147
+ 'metadata' => {
148
+ 'chat' => true,
149
+ 'variables' => {'quarter' => 'Q4', 'year' => 2024},
150
+ 'schemas' => [],
151
+ 'tools' => [],
152
+ 'custom_metadata' => {}
153
+ }
154
+ }
155
+ ```
156
+
157
+ ## OpenAI Chat Format
158
+
159
+ Perfect for integration with OpenAI's Chat Completions API:
160
+
161
+ ```ruby
162
+ markup = <<~POML
163
+ <poml>
164
+ <system>You are a code review assistant specializing in Ruby.</system>
165
+ <human>Please review this method for potential improvements.</human>
166
+ </poml>
167
+ POML
168
+
169
+ messages = Poml.process(markup: markup, format: 'openai_chat')
170
+ puts JSON.pretty_generate(messages)
171
+ ```
172
+
173
+ Output:
174
+
175
+ ```json
176
+ [
177
+ {
178
+ "role": "system",
179
+ "content": "You are a code review assistant specializing in Ruby."
180
+ },
181
+ {
182
+ "role": "user",
183
+ "content": "Please review this method for potential improvements."
184
+ }
185
+ ]
186
+ ```
187
+
188
+ ### With Template Variables
189
+
190
+ ```ruby
191
+ markup = <<~POML
192
+ <poml>
193
+ <system>You are a {{expertise}} expert.</system>
194
+ <human>Help me with {{task_type}}.</human>
195
+ </poml>
196
+ POML
197
+
198
+ context = {
199
+ 'expertise' => 'Ruby on Rails',
200
+ 'task_type' => 'performance optimization'
201
+ }
202
+
203
+ messages = Poml.process(markup: markup, context: context, format: 'openai_chat')
204
+ ```
205
+
206
+ ### Direct OpenAI Integration
207
+
208
+ ```ruby
209
+ require 'net/http'
210
+ require 'json'
211
+
212
+ def send_to_openai(poml_markup, context = {})
213
+ messages = Poml.process(
214
+ markup: poml_markup,
215
+ context: context,
216
+ format: 'openai_chat'
217
+ )
218
+
219
+ payload = {
220
+ model: 'gpt-4',
221
+ messages: messages,
222
+ max_tokens: 1000,
223
+ temperature: 0.7
224
+ }
225
+
226
+ uri = URI('https://api.openai.com/v1/chat/completions')
227
+ http = Net::HTTP.new(uri.host, uri.port)
228
+ http.use_ssl = true
229
+
230
+ request = Net::HTTP::Post.new(uri)
231
+ request['Authorization'] = "Bearer #{ENV['OPENAI_API_KEY']}"
232
+ request['Content-Type'] = 'application/json'
233
+ request.body = JSON.generate(payload)
234
+
235
+ response = http.request(request)
236
+ JSON.parse(response.body)
237
+ end
238
+
239
+ # Usage
240
+ markup = <<~POML
241
+ <poml>
242
+ <system>You are a helpful assistant.</system>
243
+ <human>Explain recursion in programming.</human>
244
+ </poml>
245
+ POML
246
+
247
+ response = send_to_openai(markup)
248
+ puts response['choices'][0]['message']['content']
249
+ ```
250
+
251
+ ## OpenAI Response Format
252
+
253
+ Standardized AI response structure separate from conversational chat format:
254
+
255
+ ```ruby
256
+ markup = <<~POML
257
+ <poml>
258
+ <role>Research Assistant</role>
259
+ <task>Summarize research findings</task>
260
+
261
+ <output-schema name="ResearchSummary">
262
+ {
263
+ "type": "object",
264
+ "properties": {
265
+ "key_findings": {"type": "array"},
266
+ "confidence": {"type": "number"}
267
+ }
268
+ }
269
+ </output-schema>
270
+ </poml>
271
+ POML
272
+
273
+ result = Poml.process(markup: markup, format: 'openaiResponse')
274
+ puts JSON.pretty_generate(result)
275
+ ```
276
+
277
+ Output:
278
+
279
+ ```json
280
+ {
281
+ "content": "Research Assistant\n\nSummarize research findings",
282
+ "type": "prompt",
283
+ "metadata": {
284
+ "variables": {},
285
+ "schemas": [
286
+ {
287
+ "name": "ResearchSummary",
288
+ "content": {"type": "object", "properties": {...}}
289
+ }
290
+ ],
291
+ "tools": [],
292
+ "custom_metadata": {}
293
+ }
294
+ }
295
+ ```
296
+
297
+ ### With Tools and Schemas
298
+
299
+ ```ruby
300
+ markup = <<~POML
301
+ <poml>
302
+ <role>Data Analyst</role>
303
+ <task>Analyze dataset and provide insights</task>
304
+
305
+ <tool-definition name="load_data">
306
+ {
307
+ "description": "Load dataset from file",
308
+ "parameters": {
309
+ "type": "object",
310
+ "properties": {
311
+ "file_path": {"type": "string"},
312
+ "format": {"type": "string"}
313
+ }
314
+ }
315
+ }
316
+ </tool-definition>
317
+
318
+ <output-schema name="Analysis">
319
+ {
320
+ "type": "object",
321
+ "properties": {
322
+ "insights": {"type": "array"},
323
+ "recommendations": {"type": "array"}
324
+ }
325
+ }
326
+ </output-schema>
327
+ </poml>
328
+ POML
329
+
330
+ result = Poml.process(markup: markup, format: 'openaiResponse')
331
+ ```
332
+
333
+ ## LangChain Format
334
+
335
+ Compatible with LangChain framework for Python/Node.js integration:
336
+
337
+ ```ruby
338
+ markup = <<~POML
339
+ <poml>
340
+ <system>You are a helpful assistant.</system>
341
+ <human>Explain machine learning concepts.</human>
342
+ </poml>
343
+ POML
344
+
345
+ result = Poml.process(markup: markup, format: 'langchain')
346
+ puts JSON.pretty_generate(result)
347
+ ```
348
+
349
+ Output:
350
+
351
+ ```json
352
+ {
353
+ "messages": [
354
+ {
355
+ "role": "system",
356
+ "content": "You are a helpful assistant."
357
+ },
358
+ {
359
+ "role": "human",
360
+ "content": "Explain machine learning concepts."
361
+ }
362
+ ],
363
+ "content": "===== system =====\n\nYou are a helpful assistant.\n\n===== human =====\n\nExplain machine learning concepts."
364
+ }
365
+ ```
366
+
367
+ ### LangChain Integration Example
368
+
369
+ ```python
370
+ # Python example using the output
371
+ import json
372
+ from langchain.schema import SystemMessage, HumanMessage
373
+
374
+ def process_poml_for_langchain(poml_output):
375
+ messages = []
376
+ for msg in poml_output['messages']:
377
+ if msg['role'] == 'system':
378
+ messages.append(SystemMessage(content=msg['content']))
379
+ elif msg['role'] == 'human':
380
+ messages.append(HumanMessage(content=msg['content']))
381
+ return messages
382
+ ```
383
+
384
+ ## Pydantic Format
385
+
386
+ Enhanced Python interoperability with strict JSON schemas:
387
+
388
+ ```ruby
389
+ markup = <<~POML
390
+ <poml>
391
+ <role>API Designer</role>
392
+ <task>Design REST API endpoints</task>
393
+
394
+ <meta title="API Design Session" author="Senior Developer" />
395
+
396
+ <output-schema name="APIDesign">
397
+ {
398
+ "type": "object",
399
+ "properties": {
400
+ "endpoints": {"type": "array"},
401
+ "schemas": {"type": "object"}
402
+ }
403
+ }
404
+ </output-schema>
405
+ </poml>
406
+ POML
407
+
408
+ result = Poml.process(markup: markup, format: 'pydantic')
409
+ puts JSON.pretty_generate(result)
410
+ ```
411
+
412
+ Output:
413
+
414
+ ```json
415
+ {
416
+ "content": "API Designer\n\nDesign REST API endpoints",
417
+ "variables": {},
418
+ "chat_enabled": true,
419
+ "schemas": [
420
+ {
421
+ "name": "APIDesign",
422
+ "schema": {"type": "object", "properties": {...}}
423
+ }
424
+ ],
425
+ "custom_metadata": {
426
+ "title": "API Design Session",
427
+ "author": "Senior Developer"
428
+ },
429
+ "metadata": {
430
+ "format": "pydantic",
431
+ "python_compatibility": true,
432
+ "strict_schemas": true
433
+ }
434
+ }
435
+ ```
436
+
437
+ ### Python Integration
438
+
439
+ ```python
440
+ # Python example using pydantic output
441
+ from pydantic import BaseModel, create_model
442
+ import json
443
+
444
+ def create_pydantic_model(poml_output):
445
+ """Create Pydantic model from POML schema output"""
446
+ schemas = poml_output.get('schemas', [])
447
+
448
+ if schemas:
449
+ schema = schemas[0]['schema']
450
+ model_name = schemas[0]['name']
451
+
452
+ # Create Pydantic model from JSON schema
453
+ return create_model(model_name, **schema['properties'])
454
+
455
+ return None
456
+ ```
457
+
458
+ ## Format Comparison
459
+
460
+ ### When to Use Each Format
461
+
462
+ | Format | Best For | Output Type | Metadata |
463
+ |--------|----------|-------------|----------|
464
+ | `raw` | Direct AI API calls, Claude, custom models | String | None |
465
+ | `dict` | Ruby applications, general processing | Hash | Full |
466
+ | `openai_chat` | OpenAI Chat Completions API | Array | None |
467
+ | `openaiResponse` | Structured AI responses with metadata | Hash | Full |
468
+ | `langchain` | LangChain framework integration | Hash | Partial |
469
+ | `pydantic` | Python applications, strict schemas | Hash | Enhanced |
470
+
471
+ ### Performance Characteristics
472
+
473
+ ```ruby
474
+ require 'benchmark'
475
+
476
+ markup = <<~POML
477
+ <poml>
478
+ <role>Performance Tester</role>
479
+ <task>Test processing speed</task>
480
+ </poml>
481
+ POML
482
+
483
+ Benchmark.bm(15) do |x|
484
+ x.report('raw:') { 1000.times { Poml.process(markup: markup, format: 'raw') } }
485
+ x.report('dict:') { 1000.times { Poml.process(markup: markup, format: 'dict') } }
486
+ x.report('openai_chat:') { 1000.times { Poml.process(markup: markup, format: 'openai_chat') } }
487
+ x.report('openaiResponse:') { 1000.times { Poml.process(markup: markup, format: 'openaiResponse') } }
488
+ x.report('langchain:') { 1000.times { Poml.process(markup: markup, format: 'langchain') } }
489
+ x.report('pydantic:') { 1000.times { Poml.process(markup: markup, format: 'pydantic') } }
490
+ end
491
+ ```
492
+
493
+ ## Advanced Format Usage
494
+
495
+ ### Dynamic Format Selection
496
+
497
+ ```ruby
498
+ class PomlFormatter
499
+ FORMATS = {
500
+ openai: 'openai_chat',
501
+ claude: 'raw',
502
+ langchain: 'langchain',
503
+ python: 'pydantic',
504
+ default: 'dict'
505
+ }.freeze
506
+
507
+ def self.process_for_service(markup, service, context = {})
508
+ format = FORMATS[service] || FORMATS[:default]
509
+ Poml.process(markup: markup, context: context, format: format)
510
+ end
511
+ end
512
+
513
+ # Usage
514
+ markup = '<poml><role>Assistant</role><task>Help users</task></poml>'
515
+
516
+ openai_result = PomlFormatter.process_for_service(markup, :openai)
517
+ claude_result = PomlFormatter.process_for_service(markup, :claude)
518
+ python_result = PomlFormatter.process_for_service(markup, :python)
519
+ ```
520
+
521
+ ### Format Validation
522
+
523
+ ```ruby
524
+ def validate_format_output(markup, format)
525
+ result = Poml.process(markup: markup, format: format)
526
+
527
+ case format
528
+ when 'openai_chat'
529
+ return result.is_a?(Array) && result.all? { |msg| msg['role'] && msg['content'] }
530
+ when 'dict', 'openaiResponse', 'langchain', 'pydantic'
531
+ return result.is_a?(Hash) && result['content']
532
+ when 'raw'
533
+ return result.is_a?(String)
534
+ else
535
+ false
536
+ end
537
+ end
538
+
539
+ # Test all formats
540
+ formats = ['raw', 'dict', 'openai_chat', 'openaiResponse', 'langchain', 'pydantic']
541
+ markup = '<poml><role>Tester</role><task>Test formats</task></poml>'
542
+
543
+ formats.each do |format|
544
+ valid = validate_format_output(markup, format)
545
+ puts "#{format}: #{valid ? '✅' : '❌'}"
546
+ end
547
+ ```
548
+
549
+ ### Content Consistency Testing
550
+
551
+ ```ruby
552
+ def test_content_consistency(markup, context = {})
553
+ formats = ['raw', 'dict', 'openai_chat', 'openaiResponse', 'langchain', 'pydantic']
554
+ results = {}
555
+
556
+ formats.each do |format|
557
+ results[format] = Poml.process(markup: markup, context: context, format: format)
558
+ end
559
+
560
+ # Extract content from each format
561
+ contents = {
562
+ 'raw' => results['raw'],
563
+ 'dict' => results['dict']['content'],
564
+ 'openai_chat' => results['openai_chat'].map { |msg| msg['content'] }.join("\n"),
565
+ 'openaiResponse' => results['openaiResponse']['content'],
566
+ 'langchain' => results['langchain']['content'],
567
+ 'pydantic' => results['pydantic']['content']
568
+ }
569
+
570
+ # Check if core content is consistent
571
+ base_content = contents['dict'].gsub(/=+\s*\w+\s*=+/, '').strip
572
+
573
+ contents.each do |format, content|
574
+ processed_content = content.gsub(/=+\s*\w+\s*=+/, '').strip
575
+ consistent = processed_content.include?(base_content.split("\n").first)
576
+ puts "#{format}: #{consistent ? '✅' : '❌'} content consistency"
577
+ end
578
+ end
579
+
580
+ # Test consistency
581
+ markup = <<~POML
582
+ <poml>
583
+ <role>{{role_type}}</role>
584
+ <task>{{task_description}}</task>
585
+ </poml>
586
+ POML
587
+
588
+ context = { 'role_type' => 'Analyst', 'task_description' => 'Analyze data' }
589
+ test_content_consistency(markup, context)
590
+ ```
591
+
592
+ ## Integration Examples
593
+
594
+ ### Multi-Service AI Client
595
+
596
+ ```ruby
597
+ class MultiServiceAIClient
598
+ def initialize
599
+ @services = {
600
+ openai: { format: 'openai_chat', api_class: OpenAIClient },
601
+ claude: { format: 'raw', api_class: ClaudeClient },
602
+ langchain: { format: 'langchain', api_class: LangChainClient }
603
+ }
604
+ end
605
+
606
+ def process_prompt(service, markup, context = {})
607
+ config = @services[service]
608
+ return nil unless config
609
+
610
+ formatted_prompt = Poml.process(
611
+ markup: markup,
612
+ context: context,
613
+ format: config[:format]
614
+ )
615
+
616
+ config[:api_class].new.send_request(formatted_prompt)
617
+ end
618
+ end
619
+
620
+ # Usage
621
+ client = MultiServiceAIClient.new
622
+
623
+ markup = <<~POML
624
+ <poml>
625
+ <system>You are a helpful assistant.</system>
626
+ <human>Explain {{topic}} in simple terms.</human>
627
+ </poml>
628
+ POML
629
+
630
+ context = { 'topic' => 'quantum computing' }
631
+
632
+ openai_response = client.process_prompt(:openai, markup, context)
633
+ claude_response = client.process_prompt(:claude, markup, context)
634
+ ```
635
+
636
+ ### Format-Specific Optimization
637
+
638
+ ```ruby
639
+ class OptimizedPomlProcessor
640
+ def self.process_optimized(markup, target_format, context = {})
641
+ case target_format
642
+ when 'openai_chat'
643
+ # Optimize for OpenAI - ensure proper chat structure
644
+ result = Poml.process(markup: markup, context: context, format: 'openai_chat')
645
+ # Add any OpenAI-specific optimizations
646
+ optimize_for_openai(result)
647
+
648
+ when 'raw'
649
+ # Optimize for raw text - clean formatting
650
+ result = Poml.process(markup: markup, context: context, format: 'raw')
651
+ result.gsub(/\n{3,}/, "\n\n") # Remove excessive newlines
652
+
653
+ when 'pydantic'
654
+ # Optimize for Python - ensure schema compatibility
655
+ result = Poml.process(markup: markup, context: context, format: 'pydantic')
656
+ ensure_python_compatibility(result)
657
+
658
+ else
659
+ Poml.process(markup: markup, context: context, format: target_format)
660
+ end
661
+ end
662
+
663
+ private
664
+
665
+ def self.optimize_for_openai(messages)
666
+ # Ensure messages don't exceed token limits, merge if needed
667
+ messages.map do |msg|
668
+ if msg['content'].length > 4000
669
+ msg['content'] = msg['content'][0..3000] + "... (truncated)"
670
+ end
671
+ msg
672
+ end
673
+ end
674
+
675
+ def self.ensure_python_compatibility(result)
676
+ # Ensure all schemas are valid JSON Schema Draft 7
677
+ result['schemas']&.each do |schema|
678
+ schema['schema']['$schema'] = 'http://json-schema.org/draft-07/schema#'
679
+ end
680
+ result
681
+ end
682
+ end
683
+ ```
684
+
685
+ ## Next Steps
686
+
687
+ - Learn about [Template Engine](template-engine.md) for dynamic content
688
+ - Explore [Schema Components](components/schema-components.md) for structured outputs
689
+ - Check [Integration Examples](integration/rails.md) for real-world usage
@@ -0,0 +1,30 @@
1
+ # Quick Start
2
+
3
+ Here's a very simple POML example. Please put it in a file named `example.poml`. Make sure it resides in the same directory as the `photosynthesis_diagram.png` image file.
4
+
5
+ ```xml
6
+ <poml>
7
+ <role>You are a patient teacher explaining concepts to a 10-year-old.</role>
8
+ <task>Explain the concept of photosynthesis using the provided image as a reference.</task>
9
+
10
+ <img src="photosynthesis_diagram.png" alt="Diagram of photosynthesis" />
11
+
12
+ <output-format>
13
+ Keep the explanation simple, engaging, and under 100 words.
14
+ Start with "Hey there, future scientist!".
15
+ </output-format>
16
+ </poml>
17
+ ```
18
+
19
+ This example defines a role and task for the LLM, includes an image for context, and specifies the desired output format. With the POML toolkit, the prompt can be easily rendered with a flexible format, and tested with a vision LLM.
20
+
21
+ ## YouTube Video
22
+
23
+ We also recommend watching our [YouTube video](https://youtu.be/b9WDcFsKixo) for a quick introduction to POML and how to get started.
24
+
25
+ ## Next Steps
26
+
27
+ - [Learn POML Syntax](basic-usage.md): Understand the structure and syntax of POML with Ruby examples.
28
+ - [Explore Components](components/index.md): Discover the available components and how to use them.
29
+ - [Ruby SDK Guide](../ruby/index.md): Learn how to use POML in your Ruby applications.
30
+ - [Template Engine](template-engine.md): Learn about variables, conditionals, and loops in POML.