poml 0.0.6 → 0.0.7

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