poml 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5ba78c3aed937b9021b7d60fa5615fce4e75067ba607226fcb1c98674e720cf
4
- data.tar.gz: 366238332f24e1f4309c64ff8da6fe52822747727e411d3925e92902b4b0ab2c
3
+ metadata.gz: '08ad238aa1b84997e3120a4379473c190293c3e6cb4a9fc08fe373457e3d10b7'
4
+ data.tar.gz: '028e54bb00290ffd9317902101435a2024a15972d6987e4acf9773b00ccba679'
5
5
  SHA512:
6
- metadata.gz: f54b42cd18875cb6200eda77ea3f5be6ef21bbb5a8adcc35e3d349c4b7b6c8e8194f9fccc7c618bfe04c44163bcd8363816f04658f8b5ad279958aa6b9a21e64
7
- data.tar.gz: caab1eb1555492e0bcc7342baa2b7c5a48c1a5606a172dee3d4983b617572940ce04d5cfda2b1d966da073cec0bc35b71c060435a5354126d265b3e43148fc5f
6
+ metadata.gz: db5eb9e927864afd49fc424be4d97f0e99f206e967605a878da19e8ae150a1ea13b25dab5a4a93773653765933c1e191e4da2c19496d43b6708a37a4aec7e758
7
+ data.tar.gz: be6a7c9fc5570bc8bc88483f2d7afcf757cf8d8153128b3f3fb6aa90bf54faaaff5b8a2236d68be7ee03692b40b6b5c97d36494f4c1ccb622fea5d96e408514e
@@ -0,0 +1,45 @@
1
+ <poml>
2
+ <!-- New syntax using standalone components -->
3
+ <output-schema parser="json">
4
+ {
5
+ "type": "object",
6
+ "properties": {
7
+ "analysis": {
8
+ "type": "object",
9
+ "properties": {
10
+ "sentiment": { "type": "string", "enum": ["positive", "negative", "neutral"] },
11
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
12
+ "key_themes": { "type": "array", "items": { "type": "string" } }
13
+ },
14
+ "required": ["sentiment", "confidence", "key_themes"]
15
+ },
16
+ "summary": { "type": "string" }
17
+ },
18
+ "required": ["analysis", "summary"]
19
+ }
20
+ </output-schema>
21
+
22
+ <tool-definition name="search_web" description="Search the web for information" parser="json">
23
+ {
24
+ "type": "object",
25
+ "properties": {
26
+ "query": { "type": "string", "description": "Search query" },
27
+ "max_results": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5 }
28
+ },
29
+ "required": ["query"]
30
+ }
31
+ </tool-definition>
32
+
33
+ <role>Expert Text Analyst</role>
34
+
35
+ <task>
36
+ Analyze the provided text and return structured insights including sentiment analysis,
37
+ key themes, and a summary. Use the search_web tool if you need additional context.
38
+ </task>
39
+
40
+ <p>Text to analyze: "The new product launch exceeded all expectations with overwhelmingly positive customer feedback and record-breaking sales numbers."</p>
41
+
42
+ <output-format>
43
+ Return your analysis in the structured format defined by the output schema.
44
+ </output-format>
45
+ </poml>
@@ -162,7 +162,12 @@ module Poml
162
162
  COMPONENT_MAPPING = {}
163
163
 
164
164
  def self.render_element(element, context)
165
- component_class = COMPONENT_MAPPING[element.tag_name] || TextComponent
165
+ # Try to find component using multiple key formats for compatibility
166
+ tag_name = element.tag_name
167
+ component_class = COMPONENT_MAPPING[tag_name] ||
168
+ COMPONENT_MAPPING[tag_name.to_s] ||
169
+ COMPONENT_MAPPING[tag_name.to_sym] ||
170
+ TextComponent
166
171
  component = component_class.new(element, context)
167
172
  component.render
168
173
  end
@@ -49,6 +49,12 @@ module Poml
49
49
  handle_variables(variables_attr)
50
50
  end
51
51
 
52
+ # Handle tool registration via tool attribute
53
+ tool_attr = get_attribute('tool')
54
+ if tool_attr
55
+ handle_tool_registration_with_name(tool_attr)
56
+ end
57
+
52
58
  # Handle general metadata attributes
53
59
  %w[title description author keywords].each do |attr|
54
60
  value = get_attribute(attr)
@@ -86,7 +92,7 @@ module Poml
86
92
 
87
93
  # Auto-detect format if parser_attr is auto
88
94
  if parser_attr == 'auto'
89
- parser_attr = content.start_with?('{') ? 'json' : 'expr'
95
+ parser_attr = content.start_with?('{') ? 'json' : 'eval'
90
96
  end
91
97
 
92
98
  # Handle new 'eval' parser type as alias for 'expr'
@@ -102,6 +108,11 @@ module Poml
102
108
  end
103
109
 
104
110
  if schema
111
+ # Check if there's already a response schema defined
112
+ if @context.response_schema
113
+ raise Poml::Error, "Multiple response schemas are not allowed. Only one response schema per document is supported."
114
+ end
115
+
105
116
  # Store the schema directly for simplicity
106
117
  @context.response_schema = schema
107
118
  end
@@ -113,13 +124,58 @@ module Poml
113
124
  # Support both old 'lang' and new 'parser' attributes for compatibility
114
125
  parser_attr = get_attribute('parser') || get_attribute('lang', 'auto')
115
126
 
116
- return unless name
127
+ content = @element.content.strip
128
+
129
+ # Auto-detect format if parser_attr is auto
130
+ if parser_attr == 'auto'
131
+ parser_attr = content.start_with?('{') ? 'json' : 'eval'
132
+ end
133
+
134
+ # Handle new 'eval' parser type as alias for 'expr'
135
+ parser_attr = 'expr' if parser_attr == 'eval'
136
+
137
+ schema = case parser_attr.downcase
138
+ when 'json'
139
+ parse_json_schema(content)
140
+ when 'expr'
141
+ evaluate_expression_schema(content)
142
+ else
143
+ nil
144
+ end
145
+
146
+ if schema
147
+ @context.tools ||= []
148
+
149
+ # If name and description are provided as attributes, use them
150
+ if name
151
+ tool_def = {
152
+ 'name' => name,
153
+ 'description' => description
154
+ }
155
+ # Merge in the parsed schema (should include parameters, etc.)
156
+ tool_def.merge!(schema) if schema.is_a?(Hash)
157
+ elsif schema.is_a?(Hash) && schema['name']
158
+ # If the schema contains the full tool definition, use it directly
159
+ tool_def = schema
160
+ else
161
+ # No valid tool definition found
162
+ return
163
+ end
164
+
165
+ @context.tools << tool_def
166
+ end
167
+ end
168
+
169
+ def handle_tool_registration_with_name(tool_name)
170
+ description = get_attribute('description')
171
+ # Support both old 'lang' and new 'parser' attributes for compatibility
172
+ parser_attr = get_attribute('parser') || get_attribute('lang', 'auto')
117
173
 
118
174
  content = @element.content.strip
119
175
 
120
176
  # Auto-detect format if parser_attr is auto
121
177
  if parser_attr == 'auto'
122
- parser_attr = content.start_with?('{') ? 'json' : 'expr'
178
+ parser_attr = content.start_with?('{') ? 'json' : 'eval'
123
179
  end
124
180
 
125
181
  # Handle new 'eval' parser type as alias for 'expr'
@@ -138,7 +194,7 @@ module Poml
138
194
  @context.tools ||= []
139
195
  # Store tool with string keys for JSON compatibility
140
196
  tool_def = {
141
- 'name' => name,
197
+ 'name' => tool_name,
142
198
  'description' => description
143
199
  }
144
200
  # Merge in the parsed schema (should include parameters, etc.)
@@ -0,0 +1,85 @@
1
+ module Poml
2
+ # OutputSchema component for defining AI response schemas
3
+ class OutputSchemaComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ # Support both old 'lang' and new 'parser' attributes for compatibility
8
+ parser_attr = get_attribute('parser') || get_attribute('lang', 'auto')
9
+ _name = get_attribute('name') # May be used for schema naming in future
10
+ _description = get_attribute('description') # May be used for schema documentation in future
11
+
12
+ content = @element.content.strip
13
+
14
+ # Auto-detect format if parser_attr is auto
15
+ if parser_attr == 'auto'
16
+ parser_attr = content.start_with?('{') ? 'json' : 'eval'
17
+ end
18
+
19
+ # Handle new 'eval' parser type as alias for 'expr'
20
+ parser_attr = 'expr' if parser_attr == 'eval'
21
+
22
+ schema = case parser_attr.downcase
23
+ when 'json'
24
+ parse_json_schema(content)
25
+ when 'expr'
26
+ evaluate_expression_schema(content)
27
+ else
28
+ nil
29
+ end
30
+
31
+ if schema
32
+ # Check if there's already a response schema defined
33
+ if @context.response_schema
34
+ raise Poml::Error, "Multiple output-schema elements are not allowed. Only one response schema per document is supported."
35
+ end
36
+
37
+ # Store the schema directly for simplicity
38
+ @context.response_schema = schema
39
+ end
40
+
41
+ # Meta-like components don't produce output
42
+ ''
43
+ end
44
+
45
+ private
46
+
47
+ def parse_json_schema(content)
48
+ # Apply template substitution first
49
+ substituted_content = @context.template_engine.substitute(content)
50
+
51
+ begin
52
+ JSON.parse(substituted_content)
53
+ rescue JSON::ParserError => e
54
+ raise Poml::Error, "Invalid JSON schema: #{e.message}"
55
+ end
56
+ end
57
+
58
+ def evaluate_expression_schema(content)
59
+ # Apply template substitution first
60
+ substituted_content = @context.template_engine.substitute(content)
61
+
62
+ begin
63
+ # In a real implementation, this would evaluate JavaScript expressions
64
+ # For now, we'll try to parse as JSON if it looks like JSON,
65
+ # otherwise treat it as a placeholder
66
+ if substituted_content.strip.start_with?('{', '[')
67
+ JSON.parse(substituted_content)
68
+ else
69
+ # This would need a JavaScript engine in a real implementation
70
+ # For now, return a placeholder that indicates expression evaluation
71
+ {
72
+ "_expression" => substituted_content,
73
+ "_note" => "Expression evaluation not implemented in Ruby gem"
74
+ }
75
+ end
76
+ rescue JSON::ParserError
77
+ # Return expression as-is if it can't be parsed as JSON
78
+ {
79
+ "_expression" => substituted_content,
80
+ "_note" => "Expression evaluation not implemented in Ruby gem"
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,98 @@
1
+ module Poml
2
+ # ToolDefinition component for registering AI tools
3
+ class ToolDefinitionComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ name = get_attribute('name')
8
+ description = get_attribute('description')
9
+ # Support both old 'lang' and new 'parser' attributes for compatibility
10
+ parser_attr = get_attribute('parser') || get_attribute('lang', 'auto')
11
+
12
+ return '' unless name # Name is required for tools
13
+
14
+ content = @element.content.strip
15
+
16
+ # Auto-detect format if parser_attr is auto
17
+ if parser_attr == 'auto'
18
+ parser_attr = content.start_with?('{') ? 'json' : 'eval'
19
+ end
20
+
21
+ # Handle new 'eval' parser type as alias for 'expr'
22
+ parser_attr = 'expr' if parser_attr == 'eval'
23
+
24
+ schema = case parser_attr.downcase
25
+ when 'json'
26
+ parse_json_schema(content)
27
+ when 'expr'
28
+ evaluate_expression_schema(content)
29
+ else
30
+ nil
31
+ end
32
+
33
+ if schema
34
+ @context.tools ||= []
35
+ # Store tool with string keys for JSON compatibility
36
+ tool_def = {
37
+ 'name' => name,
38
+ 'description' => description,
39
+ 'schema' => schema.is_a?(String) ? schema : JSON.generate(schema)
40
+ }
41
+
42
+ # For compatibility with expected structure, also store as parameters
43
+ if schema.is_a?(Hash)
44
+ tool_def['parameters'] = schema
45
+ end
46
+
47
+ @context.tools << tool_def
48
+ end
49
+
50
+ # Meta-like components don't produce output
51
+ ''
52
+ end
53
+
54
+ private
55
+
56
+ def parse_json_schema(content)
57
+ # Apply template substitution first
58
+ substituted_content = @context.template_engine.substitute(content)
59
+
60
+ begin
61
+ parsed = JSON.parse(substituted_content)
62
+ # Return the parsed schema directly - don't wrap it
63
+ parsed
64
+ rescue JSON::ParserError => e
65
+ raise Poml::Error, "Invalid JSON schema for tool '#{get_attribute('name')}': #{e.message}"
66
+ end
67
+ end
68
+
69
+ def evaluate_expression_schema(content)
70
+ # Apply template substitution first
71
+ substituted_content = @context.template_engine.substitute(content)
72
+
73
+ begin
74
+ # In a real implementation, this would evaluate JavaScript expressions
75
+ # For now, we'll try to parse as JSON if it looks like JSON,
76
+ # otherwise treat it as a placeholder
77
+ if substituted_content.strip.start_with?('{', '[')
78
+ parsed = JSON.parse(substituted_content)
79
+ # Return the parsed schema directly - don't wrap it
80
+ parsed
81
+ else
82
+ # This would need a JavaScript engine in a real implementation
83
+ # For now, return a placeholder that indicates expression evaluation
84
+ {
85
+ "_expression" => substituted_content,
86
+ "_note" => "Expression evaluation not implemented in Ruby gem"
87
+ }
88
+ end
89
+ rescue JSON::ParserError
90
+ # Return expression as-is if it can't be parsed as JSON
91
+ {
92
+ "_expression" => substituted_content,
93
+ "_note" => "Expression evaluation not implemented in Ruby gem"
94
+ }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -14,6 +14,8 @@ require_relative 'components/media'
14
14
  require_relative 'components/utilities'
15
15
  require_relative 'components/meta'
16
16
  require_relative 'components/template'
17
+ require_relative 'components/output_schema'
18
+ require_relative 'components/tool_definition'
17
19
 
18
20
  module Poml
19
21
  # Update the component mapping after all components are loaded
@@ -126,6 +128,14 @@ module Poml
126
128
  # Meta components
127
129
  meta: MetaComponent,
128
130
  Meta: MetaComponent,
131
+ 'output-schema': OutputSchemaComponent,
132
+ 'outputschema': OutputSchemaComponent,
133
+ OutputSchema: OutputSchemaComponent,
134
+ 'tool-definition': ToolDefinitionComponent,
135
+ 'tooldefinition': ToolDefinitionComponent,
136
+ ToolDefinition: ToolDefinitionComponent,
137
+ tool: ToolDefinitionComponent, # 'tool' is an alias for 'tool-definition'
138
+ Tool: ToolDefinitionComponent,
129
139
 
130
140
  # Template components
131
141
  include: IncludeComponent,
data/lib/poml/parser.rb CHANGED
@@ -236,7 +236,8 @@ module Poml
236
236
  def preprocess_void_elements(content)
237
237
  # List of HTML void elements that should be self-closing in XML
238
238
  # Note: 'meta' is removed from this list because POML meta components can have content
239
- void_elements = %w[br hr img input area base col embed link param source track wbr]
239
+ # Note: 'input' is removed because POML input components (for examples) can have content
240
+ void_elements = %w[br hr img area base col embed link param source track wbr]
240
241
 
241
242
  # Convert <element> to <element/> for void elements, but only if not already self-closing
242
243
  void_elements.each do |element|
data/lib/poml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Poml
4
- VERSION = "0.0.5"
4
+ VERSION = "0.0.6"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ghennadii Mirosnicenco
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-21 00:00:00.000000000 Z
10
+ date: 2025-08-22 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rexml
@@ -64,6 +64,7 @@ files:
64
64
  - examples/301_generate_poml.poml
65
65
  - examples/301_new_schema_syntax.poml
66
66
  - examples/302_schema_compatibility.poml
67
+ - examples/303_new_component_syntax.poml
67
68
  - examples/README.md
68
69
  - examples/assets/101_jerry_mouse.jpg
69
70
  - examples/assets/101_tom_and_jerry.docx
@@ -110,9 +111,11 @@ files:
110
111
  - lib/poml/components/lists.rb
111
112
  - lib/poml/components/media.rb
112
113
  - lib/poml/components/meta.rb
114
+ - lib/poml/components/output_schema.rb
113
115
  - lib/poml/components/styling.rb
114
116
  - lib/poml/components/template.rb
115
117
  - lib/poml/components/text.rb
118
+ - lib/poml/components/tool_definition.rb
116
119
  - lib/poml/components/utilities.rb
117
120
  - lib/poml/components/workflow.rb
118
121
  - lib/poml/components_new.rb