poml 0.0.1
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +239 -0
- data/TUTORIAL.md +987 -0
- data/bin/poml +80 -0
- data/examples/101_explain_character.poml +30 -0
- data/examples/102_render_xml.poml +40 -0
- data/examples/103_word_todos.poml +27 -0
- data/examples/104_financial_analysis.poml +33 -0
- data/examples/105_write_blog_post.poml +48 -0
- data/examples/106_research.poml +36 -0
- data/examples/107_read_report_pdf.poml +4 -0
- data/examples/201_orders_qa.poml +50 -0
- data/examples/202_arc_agi.poml +36 -0
- data/examples/301_generate_poml.poml +46 -0
- data/examples/README.md +50 -0
- data/examples/_generate_expects.py +35 -0
- data/examples/assets/101_jerry_mouse.jpg +0 -0
- data/examples/assets/101_tom_and_jerry.docx +0 -0
- data/examples/assets/101_tom_cat.jpg +0 -0
- data/examples/assets/101_tom_introduction.txt +9 -0
- data/examples/assets/103_prompt_wizard.docx +0 -0
- data/examples/assets/104_chart_normalized_price.png +0 -0
- data/examples/assets/104_chart_price.png +0 -0
- data/examples/assets/104_mag7.xlsx +0 -0
- data/examples/assets/107_usenix_paper.pdf +0 -0
- data/examples/assets/201_order_instructions.json +7 -0
- data/examples/assets/201_orderlines.csv +2 -0
- data/examples/assets/201_orders.csv +3 -0
- data/examples/assets/202_arc_agi_data.json +1 -0
- data/examples/expects/101_explain_character.txt +117 -0
- data/examples/expects/102_render_xml.txt +28 -0
- data/examples/expects/103_word_todos.txt +121 -0
- data/examples/expects/104_financial_analysis.txt +86 -0
- data/examples/expects/105_write_blog_post.txt +41 -0
- data/examples/expects/106_research.txt +29 -0
- data/examples/expects/107_read_report_pdf.txt +151 -0
- data/examples/expects/201_orders_qa.txt +44 -0
- data/examples/expects/202_arc_agi.txt +64 -0
- data/examples/expects/301_generate_poml.txt +153 -0
- data/examples/ruby_expects/101_explain_character.txt +17 -0
- data/examples/ruby_expects/102_render_xml.txt +28 -0
- data/examples/ruby_expects/103_word_todos.txt +14 -0
- data/examples/ruby_expects/104_financial_analysis.txt +0 -0
- data/examples/ruby_expects/105_write_blog_post.txt +57 -0
- data/examples/ruby_expects/106_research.txt +5 -0
- data/examples/ruby_expects/107_read_report_pdf.txt +403 -0
- data/examples/ruby_expects/201_orders_qa.txt +41 -0
- data/examples/ruby_expects/202_arc_agi.txt +17 -0
- data/examples/ruby_expects/301_generate_poml.txt +17 -0
- data/lib/poml/components/base.rb +132 -0
- data/lib/poml/components/content.rb +156 -0
- data/lib/poml/components/data.rb +346 -0
- data/lib/poml/components/examples.rb +55 -0
- data/lib/poml/components/instructions.rb +93 -0
- data/lib/poml/components/layout.rb +50 -0
- data/lib/poml/components/lists.rb +82 -0
- data/lib/poml/components/styling.rb +36 -0
- data/lib/poml/components/text.rb +8 -0
- data/lib/poml/components/workflow.rb +63 -0
- data/lib/poml/components.rb +47 -0
- data/lib/poml/components_new.rb +297 -0
- data/lib/poml/components_old.rb +1096 -0
- data/lib/poml/context.rb +53 -0
- data/lib/poml/parser.rb +153 -0
- data/lib/poml/renderer.rb +147 -0
- data/lib/poml/template_engine.rb +66 -0
- data/lib/poml/version.rb +5 -0
- data/lib/poml.rb +53 -0
- data/media/logo-16-purple.png +0 -0
- data/media/logo-64-white.png +0 -0
- metadata +149 -0
data/lib/poml/context.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Poml
|
4
|
+
# Context object that holds variables, stylesheets, and processing state
|
5
|
+
class Context
|
6
|
+
attr_accessor :variables, :stylesheet, :chat, :texts, :source_path, :syntax, :header_level
|
7
|
+
|
8
|
+
def initialize(variables: {}, stylesheet: nil, chat: true, syntax: nil)
|
9
|
+
@variables = variables || {}
|
10
|
+
@stylesheet = parse_stylesheet(stylesheet)
|
11
|
+
@chat = chat
|
12
|
+
@texts = {}
|
13
|
+
@source_path = nil
|
14
|
+
@syntax = syntax
|
15
|
+
@header_level = 1 # Track current header nesting level
|
16
|
+
end
|
17
|
+
|
18
|
+
def xml_mode?
|
19
|
+
@syntax == 'xml'
|
20
|
+
end
|
21
|
+
|
22
|
+
def determine_syntax(element)
|
23
|
+
# Check if element or parent has syntax specified
|
24
|
+
element_syntax = element.attributes['syntax'] if element.respond_to?(:attributes)
|
25
|
+
element_syntax || @syntax || 'markdown'
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_increased_header_level
|
29
|
+
old_level = @header_level
|
30
|
+
@header_level += 1
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
@header_level = old_level
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def parse_stylesheet(stylesheet)
|
39
|
+
case stylesheet
|
40
|
+
when Hash
|
41
|
+
stylesheet
|
42
|
+
when String
|
43
|
+
JSON.parse(stylesheet)
|
44
|
+
when nil
|
45
|
+
{}
|
46
|
+
else
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
rescue JSON::ParserError
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/poml/parser.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module Poml
|
4
|
+
# Element represents a parsed POML element
|
5
|
+
class Element
|
6
|
+
attr_accessor :tag_name, :attributes, :content, :children
|
7
|
+
|
8
|
+
def initialize(tag_name:, attributes: {}, content: '', children: [])
|
9
|
+
@tag_name = tag_name
|
10
|
+
@attributes = attributes || {}
|
11
|
+
@content = content || ''
|
12
|
+
@children = children || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def text?
|
16
|
+
@tag_name == :text
|
17
|
+
end
|
18
|
+
|
19
|
+
def component?
|
20
|
+
!text?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parser for POML markup
|
25
|
+
class Parser
|
26
|
+
def initialize(context)
|
27
|
+
@context = context
|
28
|
+
@template_engine = TemplateEngine.new(context)
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse(content)
|
32
|
+
# Handle escape characters
|
33
|
+
content = unescape_poml(content)
|
34
|
+
|
35
|
+
# Remove XML comments but preserve surrounding whitespace
|
36
|
+
content = content.gsub(/(\s*)<!--.*?-->(\s*)/m) do |match|
|
37
|
+
before_space = $1
|
38
|
+
after_space = $2
|
39
|
+
# Preserve the original spacing pattern, but normalize to a single space if there was whitespace
|
40
|
+
if before_space.length > 0 || after_space.length > 0
|
41
|
+
' '
|
42
|
+
else
|
43
|
+
''
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Apply template substitutions
|
48
|
+
content = @template_engine.substitute(content)
|
49
|
+
|
50
|
+
# Check if content is wrapped in <poml> tags and extract syntax
|
51
|
+
content = content.strip
|
52
|
+
if content =~ /\A<poml\b([^>]*)?>(.*)<\/poml>\s*\z/m
|
53
|
+
poml_attributes = $1
|
54
|
+
content = $2
|
55
|
+
|
56
|
+
# Extract syntax attribute if present
|
57
|
+
if poml_attributes && poml_attributes =~ /syntax\s*=\s*["']([^"']+)["']/
|
58
|
+
@context.syntax = $1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Pre-process stylesheets before parsing other components
|
63
|
+
preprocess_stylesheets(content)
|
64
|
+
|
65
|
+
# Parse as XML with whitespace preservation
|
66
|
+
begin
|
67
|
+
doc = REXML::Document.new("<root>#{content}</root>")
|
68
|
+
# Preserve whitespace in text nodes
|
69
|
+
doc.context[:ignore_whitespace_nodes] = :none
|
70
|
+
parse_element(doc.root)
|
71
|
+
rescue => e
|
72
|
+
# If XML parsing fails, treat as plain text
|
73
|
+
puts "XML parsing failed: #{e.message}" if defined?(DEBUG)
|
74
|
+
[Element.new(tag_name: :text, content: content)]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def parse_element(xml_element)
|
81
|
+
elements = []
|
82
|
+
|
83
|
+
xml_element.children.each do |child|
|
84
|
+
case child
|
85
|
+
when REXML::Text
|
86
|
+
text_content = child.to_s
|
87
|
+
next if text_content.strip.empty? # Only skip if completely empty when stripped
|
88
|
+
|
89
|
+
# Check if the content (after removing newlines) ends with a space
|
90
|
+
content_no_newlines = text_content.gsub(/\n/, ' ')
|
91
|
+
preserves_trailing_space = content_no_newlines.rstrip != content_no_newlines
|
92
|
+
|
93
|
+
# Normalize the text: strip leading/trailing whitespace
|
94
|
+
normalized = text_content.strip
|
95
|
+
|
96
|
+
# Add back trailing space if it was significant (i.e., there was a space before newlines)
|
97
|
+
normalized += ' ' if preserves_trailing_space
|
98
|
+
|
99
|
+
next if normalized.empty?
|
100
|
+
elements << Element.new(tag_name: :text, content: normalized)
|
101
|
+
when REXML::Element
|
102
|
+
# Convert REXML attributes to string hash
|
103
|
+
attrs = {}
|
104
|
+
child.attributes.each do |name, value|
|
105
|
+
attrs[name.downcase] = value.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
elements << Element.new(
|
109
|
+
tag_name: child.name.downcase.to_sym,
|
110
|
+
attributes: attrs,
|
111
|
+
content: extract_text_content(child),
|
112
|
+
children: parse_element(child)
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
elements
|
118
|
+
end
|
119
|
+
|
120
|
+
def extract_text_content(xml_element)
|
121
|
+
# Extract direct text content (not from child elements)
|
122
|
+
text_nodes = xml_element.children.select { |child| child.is_a?(REXML::Text) }
|
123
|
+
text_nodes.map(&:to_s).join('').strip
|
124
|
+
end
|
125
|
+
|
126
|
+
def unescape_poml(text)
|
127
|
+
# Handle POML escape characters
|
128
|
+
text.gsub(/#quot;/, '"')
|
129
|
+
.gsub(/#apos;/, "'")
|
130
|
+
.gsub(/#amp;/, '&')
|
131
|
+
.gsub(/#lt;/, '<')
|
132
|
+
.gsub(/#gt;/, '>')
|
133
|
+
.gsub(/#hash;/, '#')
|
134
|
+
.gsub(/#lbrace;/, '{')
|
135
|
+
.gsub(/#rbrace;/, '}')
|
136
|
+
end
|
137
|
+
|
138
|
+
def preprocess_stylesheets(content)
|
139
|
+
# Extract and process stylesheet elements before main parsing
|
140
|
+
content.scan(/<stylesheet\b[^>]*>(.*?)<\/stylesheet>/m) do |stylesheet_content|
|
141
|
+
begin
|
142
|
+
stylesheet_text = stylesheet_content[0].strip
|
143
|
+
if stylesheet_text.start_with?('{') && stylesheet_text.end_with?('}')
|
144
|
+
stylesheet = JSON.parse(stylesheet_text)
|
145
|
+
@context.stylesheet.merge!(stylesheet) if stylesheet.is_a?(Hash)
|
146
|
+
end
|
147
|
+
rescue => e
|
148
|
+
# Silently fail JSON parsing errors
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Poml
|
4
|
+
# Renderer converts parsed POML elements to various output formats
|
5
|
+
class Renderer
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
end
|
9
|
+
|
10
|
+
def render(elements, format = 'dict')
|
11
|
+
case format
|
12
|
+
when 'raw'
|
13
|
+
render_raw(elements)
|
14
|
+
when 'dict'
|
15
|
+
render_dict(elements)
|
16
|
+
when 'openai_chat'
|
17
|
+
render_openai_chat(elements)
|
18
|
+
when 'langchain'
|
19
|
+
render_langchain(elements)
|
20
|
+
when 'pydantic'
|
21
|
+
render_pydantic(elements)
|
22
|
+
else
|
23
|
+
raise Error, "Unknown format: #{format}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def render_raw(elements)
|
30
|
+
content = elements.map { |element| Components.render_element(element, @context) }.join('')
|
31
|
+
|
32
|
+
# For raw format, wrap in message boundaries like Python package does
|
33
|
+
if @context.chat && !content.strip.empty?
|
34
|
+
# Determine if this should be system or human message
|
35
|
+
# If it contains role/task/instructions, it's typically a system message
|
36
|
+
# Otherwise, it's a human message
|
37
|
+
message_type = determine_message_type(elements)
|
38
|
+
"===== #{message_type} =====\n\n#{content.strip}\n"
|
39
|
+
else
|
40
|
+
content
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_dict(elements)
|
45
|
+
{
|
46
|
+
'content' => render_raw(elements),
|
47
|
+
'metadata' => {
|
48
|
+
'chat' => @context.chat,
|
49
|
+
'stylesheet' => @context.stylesheet,
|
50
|
+
'variables' => @context.variables
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def render_openai_chat(elements)
|
56
|
+
content = render_raw(elements)
|
57
|
+
if @context.chat
|
58
|
+
parse_chat_messages(content)
|
59
|
+
else
|
60
|
+
[{ 'role' => 'user', 'content' => content }]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def render_langchain(elements)
|
65
|
+
content = render_raw(elements)
|
66
|
+
{
|
67
|
+
'messages' => render_openai_chat(elements),
|
68
|
+
'content' => content
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def render_pydantic(elements)
|
73
|
+
# Simplified pydantic-like structure
|
74
|
+
{
|
75
|
+
'prompt' => render_raw(elements),
|
76
|
+
'variables' => @context.variables,
|
77
|
+
'chat_enabled' => @context.chat
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_chat_messages(content)
|
82
|
+
# Simplified chat message parsing
|
83
|
+
# In a full implementation, this would be more sophisticated
|
84
|
+
messages = []
|
85
|
+
current_role = 'user'
|
86
|
+
current_content = ''
|
87
|
+
|
88
|
+
content.split(/\n\n+/).each do |section|
|
89
|
+
section = section.strip
|
90
|
+
next if section.empty?
|
91
|
+
|
92
|
+
# Simple heuristic: if section starts with certain patterns, it might be assistant content
|
93
|
+
if section.start_with?('**Output:**', '**Assistant:**', 'Response:')
|
94
|
+
if !current_content.empty?
|
95
|
+
messages << { 'role' => current_role, 'content' => current_content.strip }
|
96
|
+
end
|
97
|
+
current_role = 'assistant'
|
98
|
+
current_content = section.gsub(/^\*\*(Output|Assistant):\*\*\s*/, '').gsub(/^Response:\s*/, '')
|
99
|
+
else
|
100
|
+
current_content += (current_content.empty? ? '' : "\n\n") + section
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if !current_content.empty?
|
105
|
+
messages << { 'role' => current_role, 'content' => current_content.strip }
|
106
|
+
end
|
107
|
+
|
108
|
+
messages.empty? ? [{ 'role' => 'user', 'content' => content }] : messages
|
109
|
+
end
|
110
|
+
|
111
|
+
def determine_message_type(elements)
|
112
|
+
# Extract tag names from elements
|
113
|
+
tag_names = elements.map(&:tag_name).compact
|
114
|
+
|
115
|
+
# Check for system-oriented components
|
116
|
+
has_role = tag_names.include?(:role)
|
117
|
+
has_task = tag_names.include?(:task)
|
118
|
+
has_hint = tag_names.include?(:hint)
|
119
|
+
|
120
|
+
# Check for human-oriented components or content that suggests user interaction
|
121
|
+
has_document = tag_names.include?(:document) || tag_names.include?('Document')
|
122
|
+
has_question_content = elements.any? { |el|
|
123
|
+
el.respond_to?(:content) && el.content&.match?(/\b(what|how|why|when|where|please|can you)\b/i)
|
124
|
+
}
|
125
|
+
|
126
|
+
# System message detection rules based on original TypeScript implementation:
|
127
|
+
# 1. If it's only role/task/hint components → system message
|
128
|
+
# 2. If it has role AND task → system message (instruction setup)
|
129
|
+
# 3. If it has role but also user interaction content → human message
|
130
|
+
# 4. If it has document import or question-like content → human message
|
131
|
+
|
132
|
+
if has_role && has_task
|
133
|
+
# Role + Task = system instruction setup
|
134
|
+
'system'
|
135
|
+
elsif has_role && !has_document && !has_question_content && tag_names.all? { |tag| [:role, :task, :hint, :text, :p].include?(tag) }
|
136
|
+
# Pure role/task/hint setup without user content
|
137
|
+
'system'
|
138
|
+
elsif has_document || has_question_content
|
139
|
+
# Document import or question-like content = human message
|
140
|
+
'human'
|
141
|
+
else
|
142
|
+
# Default to human for mixed or ambiguous content
|
143
|
+
'human'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Poml
|
2
|
+
# Template engine for handling {{variable}} substitutions
|
3
|
+
class TemplateEngine
|
4
|
+
def initialize(context)
|
5
|
+
@context = context
|
6
|
+
end
|
7
|
+
|
8
|
+
def substitute(text)
|
9
|
+
return text unless text.is_a?(String)
|
10
|
+
|
11
|
+
# Handle {{variable}} substitutions
|
12
|
+
text.gsub(/\{\{(.+?)\}\}/) do |match|
|
13
|
+
expression = $1.strip
|
14
|
+
evaluate_expression(expression)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def evaluate_expression(expression)
|
21
|
+
# Handle dot notation and arithmetic expressions
|
22
|
+
|
23
|
+
# Simple arithmetic operations like loop.index+1
|
24
|
+
if expression =~ /^(\w+(?:\.\w+)*)\s*\+\s*(\d+)$/
|
25
|
+
variable_path = $1
|
26
|
+
increment = $2.to_i
|
27
|
+
|
28
|
+
value = get_nested_variable(variable_path)
|
29
|
+
if value.is_a?(Numeric)
|
30
|
+
(value + increment).to_s
|
31
|
+
else
|
32
|
+
"{{#{expression}}}"
|
33
|
+
end
|
34
|
+
elsif expression =~ /^(\w+(?:\.\w+)*)$/
|
35
|
+
# Simple variable or dot notation lookup
|
36
|
+
variable_path = $1
|
37
|
+
value = get_nested_variable(variable_path)
|
38
|
+
value ? value.to_s : "{{#{expression}}}"
|
39
|
+
elsif @context.variables.key?(expression)
|
40
|
+
# Direct variable lookup (backward compatibility)
|
41
|
+
@context.variables[expression].to_s
|
42
|
+
else
|
43
|
+
# Return original expression if not found
|
44
|
+
"{{#{expression}}}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_nested_variable(path)
|
49
|
+
# Handle dot notation like "loop.index"
|
50
|
+
parts = path.split('.')
|
51
|
+
value = @context.variables
|
52
|
+
|
53
|
+
parts.each do |part|
|
54
|
+
if value.is_a?(Hash) && value.key?(part)
|
55
|
+
value = value[part]
|
56
|
+
elsif value.is_a?(Hash) && value.key?(part.to_sym)
|
57
|
+
value = value[part.to_sym]
|
58
|
+
else
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/poml/version.rb
ADDED
data/lib/poml.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "poml/version"
|
4
|
+
require_relative 'poml/context'
|
5
|
+
require_relative 'poml/parser'
|
6
|
+
require_relative 'poml/renderer'
|
7
|
+
require_relative 'poml/components'
|
8
|
+
require_relative 'poml/template_engine'
|
9
|
+
|
10
|
+
module Poml
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
# Main entry point for processing POML files
|
14
|
+
# Parameters:
|
15
|
+
# - markup: POML file path or string content
|
16
|
+
# - context: Hash of variables for template substitution
|
17
|
+
# - stylesheet: Optional stylesheet as string or hash
|
18
|
+
# - chat: Boolean indicating chat format (default: true)
|
19
|
+
# - output_file: Path to save output (optional)
|
20
|
+
# - format: 'raw', 'dict', 'openai_chat', 'langchain', 'pydantic' (default: 'dict')
|
21
|
+
# - extra_args: Array of extra CLI args (ignored in pure Ruby implementation)
|
22
|
+
def self.process(markup:, context: nil, stylesheet: nil, chat: true, output_file: nil, format: 'dict', extra_args: [])
|
23
|
+
# Create POML context
|
24
|
+
poml_context = Context.new(
|
25
|
+
variables: context || {},
|
26
|
+
stylesheet: stylesheet,
|
27
|
+
chat: chat
|
28
|
+
)
|
29
|
+
|
30
|
+
# Read markup content and set source path
|
31
|
+
content = if File.exist?(markup.to_s)
|
32
|
+
poml_context.source_path = File.expand_path(markup.to_s)
|
33
|
+
File.read(markup)
|
34
|
+
else
|
35
|
+
markup.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
# Parse POML content
|
39
|
+
parser = Parser.new(poml_context)
|
40
|
+
parsed_elements = parser.parse(content)
|
41
|
+
|
42
|
+
# Render to the desired format
|
43
|
+
renderer = Renderer.new(poml_context)
|
44
|
+
result = renderer.render(parsed_elements, format)
|
45
|
+
|
46
|
+
# Save to file if specified
|
47
|
+
if output_file
|
48
|
+
File.write(output_file, result)
|
49
|
+
end
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
Binary file
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: poml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ghennadii Mirosnicenco
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-08-17 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rexml
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '3.2'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '3.2'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rubyzip
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.3'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.3'
|
40
|
+
description: "POML is a Ruby gem that implements POML (Prompt Oriented Markup Language),
|
41
|
+
\na markup language for structured prompt engineering. This is a Ruby port of \nthe
|
42
|
+
original Microsoft POML library, providing comprehensive tools for creating, \nprocessing,
|
43
|
+
and rendering structured prompts with support for multiple output \nformats including
|
44
|
+
OpenAI Chat, LangChain, and Pydantic.\n"
|
45
|
+
email:
|
46
|
+
- linkator7@gmail.com
|
47
|
+
executables:
|
48
|
+
- poml
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- TUTORIAL.md
|
55
|
+
- bin/poml
|
56
|
+
- examples/101_explain_character.poml
|
57
|
+
- examples/102_render_xml.poml
|
58
|
+
- examples/103_word_todos.poml
|
59
|
+
- examples/104_financial_analysis.poml
|
60
|
+
- examples/105_write_blog_post.poml
|
61
|
+
- examples/106_research.poml
|
62
|
+
- examples/107_read_report_pdf.poml
|
63
|
+
- examples/201_orders_qa.poml
|
64
|
+
- examples/202_arc_agi.poml
|
65
|
+
- examples/301_generate_poml.poml
|
66
|
+
- examples/README.md
|
67
|
+
- examples/_generate_expects.py
|
68
|
+
- examples/assets/101_jerry_mouse.jpg
|
69
|
+
- examples/assets/101_tom_and_jerry.docx
|
70
|
+
- examples/assets/101_tom_cat.jpg
|
71
|
+
- examples/assets/101_tom_introduction.txt
|
72
|
+
- examples/assets/103_prompt_wizard.docx
|
73
|
+
- examples/assets/104_chart_normalized_price.png
|
74
|
+
- examples/assets/104_chart_price.png
|
75
|
+
- examples/assets/104_mag7.xlsx
|
76
|
+
- examples/assets/107_usenix_paper.pdf
|
77
|
+
- examples/assets/201_order_instructions.json
|
78
|
+
- examples/assets/201_orderlines.csv
|
79
|
+
- examples/assets/201_orders.csv
|
80
|
+
- examples/assets/202_arc_agi_data.json
|
81
|
+
- examples/expects/101_explain_character.txt
|
82
|
+
- examples/expects/102_render_xml.txt
|
83
|
+
- examples/expects/103_word_todos.txt
|
84
|
+
- examples/expects/104_financial_analysis.txt
|
85
|
+
- examples/expects/105_write_blog_post.txt
|
86
|
+
- examples/expects/106_research.txt
|
87
|
+
- examples/expects/107_read_report_pdf.txt
|
88
|
+
- examples/expects/201_orders_qa.txt
|
89
|
+
- examples/expects/202_arc_agi.txt
|
90
|
+
- examples/expects/301_generate_poml.txt
|
91
|
+
- examples/ruby_expects/101_explain_character.txt
|
92
|
+
- examples/ruby_expects/102_render_xml.txt
|
93
|
+
- examples/ruby_expects/103_word_todos.txt
|
94
|
+
- examples/ruby_expects/104_financial_analysis.txt
|
95
|
+
- examples/ruby_expects/105_write_blog_post.txt
|
96
|
+
- examples/ruby_expects/106_research.txt
|
97
|
+
- examples/ruby_expects/107_read_report_pdf.txt
|
98
|
+
- examples/ruby_expects/201_orders_qa.txt
|
99
|
+
- examples/ruby_expects/202_arc_agi.txt
|
100
|
+
- examples/ruby_expects/301_generate_poml.txt
|
101
|
+
- lib/poml.rb
|
102
|
+
- lib/poml/components.rb
|
103
|
+
- lib/poml/components/base.rb
|
104
|
+
- lib/poml/components/content.rb
|
105
|
+
- lib/poml/components/data.rb
|
106
|
+
- lib/poml/components/examples.rb
|
107
|
+
- lib/poml/components/instructions.rb
|
108
|
+
- lib/poml/components/layout.rb
|
109
|
+
- lib/poml/components/lists.rb
|
110
|
+
- lib/poml/components/styling.rb
|
111
|
+
- lib/poml/components/text.rb
|
112
|
+
- lib/poml/components/workflow.rb
|
113
|
+
- lib/poml/components_new.rb
|
114
|
+
- lib/poml/components_old.rb
|
115
|
+
- lib/poml/context.rb
|
116
|
+
- lib/poml/parser.rb
|
117
|
+
- lib/poml/renderer.rb
|
118
|
+
- lib/poml/template_engine.rb
|
119
|
+
- lib/poml/version.rb
|
120
|
+
- media/logo-16-purple.png
|
121
|
+
- media/logo-64-white.png
|
122
|
+
homepage: https://github.com/GhennadiiMir/poml
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata:
|
126
|
+
homepage_uri: https://github.com/GhennadiiMir/poml
|
127
|
+
source_code_uri: https://github.com/GhennadiiMir/poml
|
128
|
+
documentation_uri: https://github.com/GhennadiiMir/poml/blob/main/TUTORIAL.md
|
129
|
+
bug_tracker_uri: https://github.com/GhennadiiMir/poml/issues
|
130
|
+
changelog_uri: https://github.com/GhennadiiMir/poml/releases
|
131
|
+
rubygems_mfa_required: 'true'
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 2.7.0
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubygems_version: 3.6.5
|
147
|
+
specification_version: 4
|
148
|
+
summary: Ruby implementation of POML (Prompt Oriented Markup Language)
|
149
|
+
test_files: []
|