poml 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/poml/components/base.rb +45 -7
- data/lib/poml/components/data.rb +259 -0
- data/lib/poml/components/examples.rb +159 -13
- data/lib/poml/components/formatting.rb +148 -0
- data/lib/poml/components/media.rb +34 -0
- data/lib/poml/components/meta.rb +248 -0
- data/lib/poml/components/template.rb +334 -0
- data/lib/poml/components/utilities.rb +508 -0
- data/lib/poml/components.rb +91 -2
- data/lib/poml/context.rb +41 -2
- data/lib/poml/parser.rb +128 -15
- data/lib/poml/renderer.rb +26 -7
- data/lib/poml/template_engine.rb +101 -4
- data/lib/poml/version.rb +1 -1
- data/lib/poml.rb +67 -1
- data/{README.md → readme.md} +9 -1
- metadata +8 -4
- data/examples/_generate_expects.py +0 -35
@@ -0,0 +1,508 @@
|
|
1
|
+
module Poml
|
2
|
+
# AI Message component for wrapping AI responses
|
3
|
+
class AiMessageComponent < Component
|
4
|
+
def render
|
5
|
+
apply_stylesheet
|
6
|
+
|
7
|
+
content = @element.content.empty? ? render_children : @element.content
|
8
|
+
|
9
|
+
# Add to structured chat messages if context supports it
|
10
|
+
if @context.respond_to?(:chat_messages)
|
11
|
+
@context.chat_messages << {
|
12
|
+
'role' => 'assistant',
|
13
|
+
'content' => content
|
14
|
+
}
|
15
|
+
# Return empty for raw format to avoid duplication
|
16
|
+
return ''
|
17
|
+
end
|
18
|
+
|
19
|
+
if xml_mode?
|
20
|
+
render_as_xml('ai-msg', content, { speaker: 'ai' })
|
21
|
+
else
|
22
|
+
content
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Human Message component for wrapping user messages
|
28
|
+
class HumanMessageComponent < Component
|
29
|
+
def render
|
30
|
+
apply_stylesheet
|
31
|
+
|
32
|
+
content = @element.content.empty? ? render_children : @element.content
|
33
|
+
|
34
|
+
# Add to structured chat messages if context supports it
|
35
|
+
if @context.respond_to?(:chat_messages)
|
36
|
+
@context.chat_messages << {
|
37
|
+
'role' => 'user',
|
38
|
+
'content' => content
|
39
|
+
}
|
40
|
+
# Return empty for raw format to avoid duplication
|
41
|
+
return ''
|
42
|
+
end
|
43
|
+
|
44
|
+
if xml_mode?
|
45
|
+
render_as_xml('user-msg', content, { speaker: 'human' })
|
46
|
+
else
|
47
|
+
content
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# System Message component for wrapping system messages
|
53
|
+
class SystemMessageComponent < Component
|
54
|
+
def render
|
55
|
+
apply_stylesheet
|
56
|
+
|
57
|
+
content = @element.content.empty? ? render_children : @element.content
|
58
|
+
|
59
|
+
# Add to structured chat messages if context supports it
|
60
|
+
if @context.respond_to?(:chat_messages)
|
61
|
+
@context.chat_messages << {
|
62
|
+
'role' => 'system',
|
63
|
+
'content' => content
|
64
|
+
}
|
65
|
+
# Return empty for raw format to avoid duplication
|
66
|
+
return ''
|
67
|
+
end
|
68
|
+
|
69
|
+
if xml_mode?
|
70
|
+
render_as_xml('system-msg', content, { speaker: 'system' })
|
71
|
+
else
|
72
|
+
content
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Message Content component for displaying message content
|
78
|
+
class MessageContentComponent < Component
|
79
|
+
def render
|
80
|
+
apply_stylesheet
|
81
|
+
|
82
|
+
content_attr = get_attribute('content')
|
83
|
+
|
84
|
+
if content_attr.is_a?(Array)
|
85
|
+
# Handle array of content items
|
86
|
+
content_attr.map { |item|
|
87
|
+
item.is_a?(String) ? item : item.to_s
|
88
|
+
}.join('')
|
89
|
+
elsif content_attr.is_a?(String)
|
90
|
+
content_attr
|
91
|
+
else
|
92
|
+
content_attr.to_s
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Conversation component for displaying chat conversations
|
98
|
+
class ConversationComponent < Component
|
99
|
+
def render
|
100
|
+
apply_stylesheet
|
101
|
+
|
102
|
+
messages_attr = get_attribute('messages')
|
103
|
+
messages = if messages_attr.is_a?(String)
|
104
|
+
begin
|
105
|
+
require 'json'
|
106
|
+
JSON.parse(messages_attr)
|
107
|
+
rescue JSON::ParserError
|
108
|
+
[]
|
109
|
+
end
|
110
|
+
else
|
111
|
+
messages_attr || []
|
112
|
+
end
|
113
|
+
|
114
|
+
selected_messages = get_attribute('selectedMessages')
|
115
|
+
|
116
|
+
# Apply message selection if specified
|
117
|
+
if selected_messages
|
118
|
+
messages = apply_message_selection(messages, selected_messages)
|
119
|
+
end
|
120
|
+
|
121
|
+
if xml_mode?
|
122
|
+
render_conversation_xml(messages)
|
123
|
+
else
|
124
|
+
render_conversation_markdown(messages)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def apply_message_selection(messages, selection)
|
131
|
+
return messages unless selection
|
132
|
+
|
133
|
+
if selection.include?(':')
|
134
|
+
# Handle slice notation like "2:4" or "-6:"
|
135
|
+
start_idx, end_idx = parse_slice(selection, messages.length)
|
136
|
+
messages[start_idx...end_idx] || []
|
137
|
+
elsif selection.is_a?(Integer)
|
138
|
+
# Single message index
|
139
|
+
[messages[selection]].compact
|
140
|
+
else
|
141
|
+
messages
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_slice(slice_str, total_length)
|
146
|
+
if slice_str.start_with?('-') && slice_str.end_with?(':')
|
147
|
+
# Handle "-6:" (last 6 messages)
|
148
|
+
count = slice_str[1..-2].to_i
|
149
|
+
[total_length - count, total_length]
|
150
|
+
elsif slice_str.include?(':')
|
151
|
+
parts = slice_str.split(':')
|
152
|
+
start_idx = parts[0].empty? ? 0 : parts[0].to_i
|
153
|
+
end_idx = parts[1].empty? ? total_length : parts[1].to_i
|
154
|
+
[start_idx, end_idx]
|
155
|
+
else
|
156
|
+
index = slice_str.to_i
|
157
|
+
[index, index + 1]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def render_conversation_xml(messages)
|
162
|
+
result = ['<conversation>']
|
163
|
+
messages.each do |msg|
|
164
|
+
speaker = msg['speaker'] || 'human'
|
165
|
+
content = msg['content'] || ''
|
166
|
+
result << " <msg speaker=\"#{speaker}\">#{escape_xml(content)}</msg>"
|
167
|
+
end
|
168
|
+
result << '</conversation>'
|
169
|
+
result.join("\n")
|
170
|
+
end
|
171
|
+
|
172
|
+
def render_conversation_markdown(messages)
|
173
|
+
result = []
|
174
|
+
messages.each do |msg|
|
175
|
+
speaker = msg['speaker'] || 'human'
|
176
|
+
content = msg['content'] || ''
|
177
|
+
|
178
|
+
case speaker.downcase
|
179
|
+
when 'human', 'user'
|
180
|
+
result << "**Human:** #{content}"
|
181
|
+
when 'ai', 'assistant'
|
182
|
+
result << "**Assistant:** #{content}"
|
183
|
+
when 'system'
|
184
|
+
result << "**System:** #{content}"
|
185
|
+
else
|
186
|
+
result << "**#{speaker.capitalize}:** #{content}"
|
187
|
+
end
|
188
|
+
result << ""
|
189
|
+
end
|
190
|
+
result.join("\n")
|
191
|
+
end
|
192
|
+
|
193
|
+
def escape_xml(text)
|
194
|
+
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Folder component for displaying directory structures
|
199
|
+
class FolderComponent < Component
|
200
|
+
require 'find'
|
201
|
+
|
202
|
+
def render
|
203
|
+
apply_stylesheet
|
204
|
+
|
205
|
+
src = get_attribute('src')
|
206
|
+
filter = get_attribute('filter')
|
207
|
+
max_depth = get_attribute('maxDepth', 3).to_i
|
208
|
+
show_content = get_attribute('showContent', false)
|
209
|
+
syntax = get_attribute('syntax', 'text')
|
210
|
+
|
211
|
+
return '[Folder: no src specified]' unless src
|
212
|
+
return '[Folder: directory not found]' unless Dir.exist?(src)
|
213
|
+
|
214
|
+
tree_data = build_tree_structure(src, filter, max_depth, show_content)
|
215
|
+
|
216
|
+
if xml_mode?
|
217
|
+
render_folder_xml(tree_data)
|
218
|
+
else
|
219
|
+
case syntax
|
220
|
+
when 'markdown'
|
221
|
+
render_folder_markdown(tree_data)
|
222
|
+
when 'json'
|
223
|
+
require 'json'
|
224
|
+
JSON.pretty_generate(tree_data)
|
225
|
+
else
|
226
|
+
render_folder_text(tree_data)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def build_tree_structure(path, filter, max_depth, show_content, current_depth = 0)
|
234
|
+
return nil if current_depth >= max_depth
|
235
|
+
|
236
|
+
items = []
|
237
|
+
|
238
|
+
begin
|
239
|
+
Dir.entries(path).sort.each do |entry|
|
240
|
+
next if entry.start_with?('.')
|
241
|
+
|
242
|
+
full_path = File.join(path, entry)
|
243
|
+
|
244
|
+
if File.directory?(full_path)
|
245
|
+
# Check if directory should be included
|
246
|
+
if !filter || entry.match?(Regexp.new(filter))
|
247
|
+
sub_items = build_tree_structure(full_path, filter, max_depth, show_content, current_depth + 1)
|
248
|
+
if sub_items && !sub_items.empty?
|
249
|
+
items << {
|
250
|
+
name: "#{entry}/",
|
251
|
+
type: 'directory',
|
252
|
+
children: sub_items
|
253
|
+
}
|
254
|
+
end
|
255
|
+
end
|
256
|
+
else
|
257
|
+
# Check if file should be included
|
258
|
+
if !filter || entry.match?(Regexp.new(filter))
|
259
|
+
item = {
|
260
|
+
name: entry,
|
261
|
+
type: 'file'
|
262
|
+
}
|
263
|
+
|
264
|
+
if show_content
|
265
|
+
begin
|
266
|
+
content = File.read(full_path, encoding: 'utf-8')
|
267
|
+
item[:content] = content
|
268
|
+
rescue
|
269
|
+
item[:content] = '[Binary file or read error]'
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
items << item
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
rescue => e
|
278
|
+
return [{ name: "[Error: #{e.message}]", type: 'error' }]
|
279
|
+
end
|
280
|
+
|
281
|
+
items
|
282
|
+
end
|
283
|
+
|
284
|
+
def render_folder_text(items, indent = 0)
|
285
|
+
result = []
|
286
|
+
prefix = ' ' * indent
|
287
|
+
|
288
|
+
items.each do |item|
|
289
|
+
result << "#{prefix}#{item[:name]}"
|
290
|
+
|
291
|
+
if item[:content]
|
292
|
+
content_lines = item[:content].split("\n")
|
293
|
+
content_lines.each do |line|
|
294
|
+
result << "#{prefix} #{line}"
|
295
|
+
end
|
296
|
+
result << ""
|
297
|
+
end
|
298
|
+
|
299
|
+
if item[:children]
|
300
|
+
result << render_folder_text(item[:children], indent + 1)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
result.join("\n")
|
305
|
+
end
|
306
|
+
|
307
|
+
def render_folder_markdown(items, indent = 0)
|
308
|
+
result = []
|
309
|
+
prefix = ' ' * indent
|
310
|
+
|
311
|
+
items.each do |item|
|
312
|
+
if item[:type] == 'directory'
|
313
|
+
result << "#{prefix}- **#{item[:name]}**"
|
314
|
+
else
|
315
|
+
result << "#{prefix}- #{item[:name]}"
|
316
|
+
end
|
317
|
+
|
318
|
+
if item[:content]
|
319
|
+
result << "#{prefix} ```"
|
320
|
+
item[:content].split("\n").each do |line|
|
321
|
+
result << "#{prefix} #{line}"
|
322
|
+
end
|
323
|
+
result << "#{prefix} ```"
|
324
|
+
result << ""
|
325
|
+
end
|
326
|
+
|
327
|
+
if item[:children]
|
328
|
+
result << render_folder_markdown(item[:children], indent + 1)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
result.join("\n")
|
333
|
+
end
|
334
|
+
|
335
|
+
def render_folder_xml(items)
|
336
|
+
result = ['<folder>']
|
337
|
+
items.each do |item|
|
338
|
+
if item[:type] == 'directory'
|
339
|
+
result << " <directory name=\"#{escape_xml(item[:name])}\">"
|
340
|
+
if item[:children]
|
341
|
+
render_folder_xml_items(item[:children], result, 2)
|
342
|
+
end
|
343
|
+
result << " </directory>"
|
344
|
+
else
|
345
|
+
if item[:content]
|
346
|
+
result << " <file name=\"#{escape_xml(item[:name])}\">"
|
347
|
+
result << " <content>#{escape_xml(item[:content])}</content>"
|
348
|
+
result << " </file>"
|
349
|
+
else
|
350
|
+
result << " <file name=\"#{escape_xml(item[:name])}\"/>"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
result << '</folder>'
|
355
|
+
result.join("\n")
|
356
|
+
end
|
357
|
+
|
358
|
+
def render_folder_xml_items(items, result, indent_level)
|
359
|
+
indent = ' ' * indent_level
|
360
|
+
items.each do |item|
|
361
|
+
if item[:type] == 'directory'
|
362
|
+
result << "#{indent}<directory name=\"#{escape_xml(item[:name])}\">"
|
363
|
+
if item[:children]
|
364
|
+
render_folder_xml_items(item[:children], result, indent_level + 1)
|
365
|
+
end
|
366
|
+
result << "#{indent}</directory>"
|
367
|
+
else
|
368
|
+
if item[:content]
|
369
|
+
result << "#{indent}<file name=\"#{escape_xml(item[:name])}\">"
|
370
|
+
result << "#{indent} <content>#{escape_xml(item[:content])}</content>"
|
371
|
+
result << "#{indent}</file>"
|
372
|
+
else
|
373
|
+
result << "#{indent}<file name=\"#{escape_xml(item[:name])}\"/>"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def escape_xml(text)
|
380
|
+
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Tree component for rendering tree structures
|
385
|
+
class TreeComponent < Component
|
386
|
+
def render
|
387
|
+
apply_stylesheet
|
388
|
+
|
389
|
+
items_attr = get_attribute('items')
|
390
|
+
items = if items_attr.is_a?(String)
|
391
|
+
begin
|
392
|
+
require 'json'
|
393
|
+
JSON.parse(items_attr)
|
394
|
+
rescue JSON::ParserError
|
395
|
+
[]
|
396
|
+
end
|
397
|
+
else
|
398
|
+
items_attr || []
|
399
|
+
end
|
400
|
+
|
401
|
+
show_content = get_attribute('showContent', false)
|
402
|
+
syntax = get_attribute('syntax', 'text')
|
403
|
+
|
404
|
+
if xml_mode?
|
405
|
+
render_tree_xml(items, show_content)
|
406
|
+
else
|
407
|
+
case syntax
|
408
|
+
when 'markdown'
|
409
|
+
render_tree_markdown(items, show_content)
|
410
|
+
when 'json'
|
411
|
+
require 'json'
|
412
|
+
JSON.pretty_generate(items)
|
413
|
+
else
|
414
|
+
render_tree_text(items, show_content)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
420
|
+
|
421
|
+
def render_tree_text(items, show_content, indent = 0)
|
422
|
+
result = []
|
423
|
+
prefix = ' ' * indent
|
424
|
+
|
425
|
+
items.each do |item|
|
426
|
+
result << "#{prefix}#{item['name'] || item[:name]}"
|
427
|
+
|
428
|
+
if show_content && (item['content'] || item[:content])
|
429
|
+
content = item['content'] || item[:content]
|
430
|
+
content.to_s.split("\n").each do |line|
|
431
|
+
result << "#{prefix} #{line}"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
children = item['children'] || item[:children]
|
436
|
+
if children && !children.empty?
|
437
|
+
result << render_tree_text(children, show_content, indent + 1)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
result.join("\n")
|
442
|
+
end
|
443
|
+
|
444
|
+
def render_tree_markdown(items, show_content, indent = 0)
|
445
|
+
result = []
|
446
|
+
prefix = ' ' * indent
|
447
|
+
|
448
|
+
items.each do |item|
|
449
|
+
name = item['name'] || item[:name]
|
450
|
+
result << "#{prefix}- #{name}"
|
451
|
+
|
452
|
+
if show_content && (item['content'] || item[:content])
|
453
|
+
content = item['content'] || item[:content]
|
454
|
+
result << "#{prefix} ```"
|
455
|
+
content.to_s.split("\n").each do |line|
|
456
|
+
result << "#{prefix} #{line}"
|
457
|
+
end
|
458
|
+
result << "#{prefix} ```"
|
459
|
+
end
|
460
|
+
|
461
|
+
children = item['children'] || item[:children]
|
462
|
+
if children && !children.empty?
|
463
|
+
result << render_tree_markdown(children, show_content, indent + 1)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
result.join("\n")
|
468
|
+
end
|
469
|
+
|
470
|
+
def render_tree_xml(items, show_content, indent_level = 1)
|
471
|
+
result = ['<tree>']
|
472
|
+
render_tree_xml_items(items, result, show_content, indent_level)
|
473
|
+
result << '</tree>'
|
474
|
+
result.join("\n")
|
475
|
+
end
|
476
|
+
|
477
|
+
def render_tree_xml_items(items, result, show_content, indent_level)
|
478
|
+
indent = ' ' * indent_level
|
479
|
+
|
480
|
+
items.each do |item|
|
481
|
+
name = item['name'] || item[:name]
|
482
|
+
children = item['children'] || item[:children]
|
483
|
+
content = item['content'] || item[:content] if show_content
|
484
|
+
|
485
|
+
if children && !children.empty?
|
486
|
+
result << "#{indent}<item name=\"#{escape_xml(name)}\">"
|
487
|
+
if content
|
488
|
+
result << "#{indent} <content>#{escape_xml(content)}</content>"
|
489
|
+
end
|
490
|
+
render_tree_xml_items(children, result, show_content, indent_level + 1)
|
491
|
+
result << "#{indent}</item>"
|
492
|
+
else
|
493
|
+
if content
|
494
|
+
result << "#{indent}<item name=\"#{escape_xml(name)}\">"
|
495
|
+
result << "#{indent} <content>#{escape_xml(content)}</content>"
|
496
|
+
result << "#{indent}</item>"
|
497
|
+
else
|
498
|
+
result << "#{indent}<item name=\"#{escape_xml(name)}\"/>"
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def escape_xml(text)
|
505
|
+
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
data/lib/poml/components.rb
CHANGED
@@ -9,39 +9,128 @@ require_relative 'components/lists'
|
|
9
9
|
require_relative 'components/layout'
|
10
10
|
require_relative 'components/workflow'
|
11
11
|
require_relative 'components/styling'
|
12
|
+
require_relative 'components/formatting'
|
13
|
+
require_relative 'components/media'
|
14
|
+
require_relative 'components/utilities'
|
15
|
+
require_relative 'components/meta'
|
16
|
+
require_relative 'components/template'
|
12
17
|
|
13
18
|
module Poml
|
14
19
|
# Update the component mapping after all components are loaded
|
15
20
|
Components::COMPONENT_MAPPING.merge!({
|
21
|
+
# Basic components
|
16
22
|
text: TextComponent,
|
23
|
+
poml: TextComponent,
|
24
|
+
p: ParagraphComponent,
|
25
|
+
|
26
|
+
# Formatting components
|
27
|
+
b: BoldComponent,
|
28
|
+
bold: BoldComponent,
|
29
|
+
i: ItalicComponent,
|
30
|
+
italic: ItalicComponent,
|
31
|
+
u: UnderlineComponent,
|
32
|
+
underline: UnderlineComponent,
|
33
|
+
s: StrikethroughComponent,
|
34
|
+
strike: StrikethroughComponent,
|
35
|
+
strikethrough: StrikethroughComponent,
|
36
|
+
span: InlineComponent,
|
37
|
+
inline: InlineComponent,
|
38
|
+
h: HeaderComponent,
|
39
|
+
header: HeaderComponent,
|
40
|
+
br: NewlineComponent,
|
41
|
+
newline: NewlineComponent,
|
42
|
+
code: CodeComponent,
|
43
|
+
section: SubContentComponent,
|
44
|
+
subcontent: SubContentComponent,
|
45
|
+
|
46
|
+
# Media components
|
47
|
+
audio: AudioComponent,
|
48
|
+
|
49
|
+
# Instruction components
|
17
50
|
role: RoleComponent,
|
18
51
|
task: TaskComponent,
|
19
52
|
hint: HintComponent,
|
53
|
+
|
54
|
+
# Content components
|
20
55
|
document: DocumentComponent,
|
21
56
|
Document: DocumentComponent, # Capitalized version
|
57
|
+
|
58
|
+
# Data components
|
22
59
|
table: TableComponent,
|
23
60
|
Table: TableComponent, # Capitalized version
|
24
61
|
img: ImageComponent,
|
25
|
-
|
62
|
+
obj: ObjectComponent,
|
63
|
+
object: ObjectComponent,
|
64
|
+
dataobj: ObjectComponent,
|
65
|
+
'data-obj': ObjectComponent,
|
66
|
+
webpage: WebpageComponent,
|
67
|
+
|
68
|
+
# Example components
|
26
69
|
example: ExampleComponent,
|
27
70
|
input: InputComponent,
|
28
71
|
output: OutputComponent,
|
29
72
|
'output-format': OutputFormatComponent,
|
30
73
|
'outputformat': OutputFormatComponent,
|
74
|
+
examples: ExampleSetComponent,
|
75
|
+
'example-set': ExampleSetComponent,
|
76
|
+
introducer: IntroducerComponent,
|
77
|
+
|
78
|
+
# List components
|
31
79
|
list: ListComponent,
|
32
80
|
item: ItemComponent,
|
81
|
+
|
82
|
+
# Layout components
|
33
83
|
cp: CPComponent,
|
84
|
+
'captioned-paragraph': CPComponent,
|
85
|
+
|
86
|
+
# Workflow components
|
34
87
|
'stepwise-instructions': StepwiseInstructionsComponent,
|
35
88
|
'stepwiseinstructions': StepwiseInstructionsComponent,
|
36
89
|
StepwiseInstructions: StepwiseInstructionsComponent,
|
37
90
|
'human-message': HumanMessageComponent,
|
38
91
|
'humanmessage': HumanMessageComponent,
|
39
92
|
HumanMessage: HumanMessageComponent,
|
93
|
+
'user-msg': HumanMessageComponent,
|
40
94
|
qa: QAComponent,
|
41
95
|
QA: QAComponent,
|
96
|
+
question: QAComponent,
|
97
|
+
|
98
|
+
# Utility components
|
99
|
+
'ai-msg': AiMessageComponent,
|
100
|
+
'aimessage': AiMessageComponent,
|
101
|
+
ai: AiMessageComponent,
|
102
|
+
Ai: AiMessageComponent,
|
103
|
+
human: HumanMessageComponent,
|
104
|
+
Human: HumanMessageComponent,
|
105
|
+
system: SystemMessageComponent,
|
106
|
+
System: SystemMessageComponent,
|
107
|
+
'system-msg': SystemMessageComponent,
|
108
|
+
'systemmessage': SystemMessageComponent,
|
109
|
+
'msg-content': MessageContentComponent,
|
110
|
+
'message-content': MessageContentComponent,
|
111
|
+
conversation: ConversationComponent,
|
112
|
+
Conversation: ConversationComponent,
|
113
|
+
folder: FolderComponent,
|
114
|
+
Folder: FolderComponent,
|
115
|
+
tree: TreeComponent,
|
116
|
+
Tree: TreeComponent,
|
117
|
+
|
118
|
+
# Styling components
|
42
119
|
let: LetComponent,
|
43
120
|
Let: LetComponent,
|
44
121
|
stylesheet: StylesheetComponent,
|
45
|
-
Stylesheet: StylesheetComponent
|
122
|
+
Stylesheet: StylesheetComponent,
|
123
|
+
|
124
|
+
# Meta components
|
125
|
+
meta: MetaComponent,
|
126
|
+
Meta: MetaComponent,
|
127
|
+
|
128
|
+
# Template components
|
129
|
+
include: IncludeComponent,
|
130
|
+
Include: IncludeComponent,
|
131
|
+
if: IfComponent,
|
132
|
+
If: IfComponent,
|
133
|
+
for: ForComponent,
|
134
|
+
For: ForComponent
|
46
135
|
})
|
47
136
|
end
|
data/lib/poml/context.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
module Poml
|
4
5
|
# Context object that holds variables, stylesheets, and processing state
|
5
6
|
class Context
|
6
7
|
attr_accessor :variables, :stylesheet, :chat, :texts, :source_path, :syntax, :header_level
|
8
|
+
attr_accessor :response_schema, :tools, :runtime_parameters, :disabled_components
|
9
|
+
attr_accessor :template_engine, :chat_messages, :custom_metadata
|
7
10
|
|
8
11
|
def initialize(variables: {}, stylesheet: nil, chat: true, syntax: nil)
|
9
12
|
@variables = variables || {}
|
@@ -13,6 +16,13 @@ module Poml
|
|
13
16
|
@source_path = nil
|
14
17
|
@syntax = syntax
|
15
18
|
@header_level = 1 # Track current header nesting level
|
19
|
+
@response_schema = nil
|
20
|
+
@tools = []
|
21
|
+
@runtime_parameters = {}
|
22
|
+
@disabled_components = Set.new
|
23
|
+
@template_engine = TemplateEngine.new(self)
|
24
|
+
@chat_messages = [] # Track structured chat messages
|
25
|
+
@custom_metadata = {} # Track general metadata like title, description etc.
|
16
26
|
end
|
17
27
|
|
18
28
|
def xml_mode?
|
@@ -28,9 +38,38 @@ module Poml
|
|
28
38
|
def with_increased_header_level
|
29
39
|
old_level = @header_level
|
30
40
|
@header_level += 1
|
31
|
-
yield
|
32
|
-
ensure
|
41
|
+
result = yield
|
33
42
|
@header_level = old_level
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_chat_context(chat_enabled)
|
47
|
+
old_chat = @chat
|
48
|
+
@chat = chat_enabled
|
49
|
+
result = yield
|
50
|
+
@chat = old_chat
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_child_context
|
55
|
+
child = Context.new(
|
56
|
+
variables: @variables.dup,
|
57
|
+
stylesheet: @stylesheet.dup,
|
58
|
+
chat: @chat,
|
59
|
+
syntax: @syntax
|
60
|
+
)
|
61
|
+
child.header_level = @header_level
|
62
|
+
child.response_schema = @response_schema
|
63
|
+
child.tools = @tools.dup
|
64
|
+
child.runtime_parameters = @runtime_parameters.dup
|
65
|
+
child.disabled_components = @disabled_components.dup
|
66
|
+
child.chat_messages = @chat_messages # Share the same array reference
|
67
|
+
child.custom_metadata = @custom_metadata # Share the same hash reference
|
68
|
+
child
|
69
|
+
end
|
70
|
+
|
71
|
+
def component_enabled?(component_name)
|
72
|
+
!@disabled_components.include?(component_name.to_s)
|
34
73
|
end
|
35
74
|
|
36
75
|
private
|