metaobjects 0.9.0__py3-none-any.whl
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.
- metaobjects/__init__.py +75 -0
- metaobjects/agent_context/__init__.py +55 -0
- metaobjects/agent_context/_content/README.md +14 -0
- metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
- metaobjects/agent_context/_content/servers/java.meta.json +5 -0
- metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
- metaobjects/agent_context/_content/servers/python.meta.json +5 -0
- metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
- metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
- metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
- metaobjects/agent_context/assemble.py +133 -0
- metaobjects/agent_context/content_root.py +54 -0
- metaobjects/agent_context/scaffold.py +191 -0
- metaobjects/agent_context/types.py +44 -0
- metaobjects/attr_class_map.py +23 -0
- metaobjects/cli.py +696 -0
- metaobjects/codegen/__init__.py +0 -0
- metaobjects/codegen/config.py +11 -0
- metaobjects/codegen/constants.py +13 -0
- metaobjects/codegen/extract_delegate_emitter.py +384 -0
- metaobjects/codegen/extract_schema_emitter.py +139 -0
- metaobjects/codegen/format.py +31 -0
- metaobjects/codegen/fr010_field_mapping.py +220 -0
- metaobjects/codegen/generator.py +62 -0
- metaobjects/codegen/generator_registry.py +163 -0
- metaobjects/codegen/generators/__init__.py +0 -0
- metaobjects/codegen/generators/entity_model.py +263 -0
- metaobjects/codegen/generators/extractor_generator.py +317 -0
- metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
- metaobjects/codegen/generators/m2m_codegen.py +192 -0
- metaobjects/codegen/generators/output_parser_generator.py +272 -0
- metaobjects/codegen/generators/output_prompt_generator.py +192 -0
- metaobjects/codegen/generators/payload_vo_generator.py +672 -0
- metaobjects/codegen/generators/render_helper_generator.py +451 -0
- metaobjects/codegen/generators/router_generator.py +635 -0
- metaobjects/codegen/generators/template_generator.py +70 -0
- metaobjects/codegen/generators/tph_plan.py +120 -0
- metaobjects/codegen/generators/trace_helper_generator.py +336 -0
- metaobjects/codegen/instance_artifacts.py +15 -0
- metaobjects/codegen/output_format_spec_emitter.py +79 -0
- metaobjects/codegen/overwrite_policy.py +27 -0
- metaobjects/codegen/runner.py +110 -0
- metaobjects/codegen/runtime/__init__.py +6 -0
- metaobjects/codegen/runtime/filter_parser.py +193 -0
- metaobjects/codegen/type_map.py +84 -0
- metaobjects/core_types.py +809 -0
- metaobjects/datatype.py +19 -0
- metaobjects/documentation/__init__.py +28 -0
- metaobjects/documentation/doc_constants.py +20 -0
- metaobjects/documentation/doc_provider.py +20 -0
- metaobjects/documentation/doc_schema.py +24 -0
- metaobjects/errors.py +124 -0
- metaobjects/loader/__init__.py +0 -0
- metaobjects/loader/merge.py +287 -0
- metaobjects/loader/meta_data_loader.py +245 -0
- metaobjects/loader/sources/__init__.py +24 -0
- metaobjects/loader/sources/directory_source.py +50 -0
- metaobjects/loader/sources/file_source.py +41 -0
- metaobjects/loader/sources/meta_data_source.py +67 -0
- metaobjects/loader/sources/uri_source.py +56 -0
- metaobjects/loader/validate_discriminator.py +181 -0
- metaobjects/loader/validate_field_readonly.py +146 -0
- metaobjects/loader/validate_source_parameter_ref.py +159 -0
- metaobjects/loader/validate_source_physical_names.py +140 -0
- metaobjects/loader/validation_passes.py +1513 -0
- metaobjects/meta/__init__.py +1 -0
- metaobjects/meta/core/__init__.py +0 -0
- metaobjects/meta/core/attr/__init__.py +0 -0
- metaobjects/meta/core/attr/attr_constants.py +31 -0
- metaobjects/meta/core/attr/meta_attr.py +136 -0
- metaobjects/meta/core/field/__init__.py +0 -0
- metaobjects/meta/core/field/field_constants.py +105 -0
- metaobjects/meta/core/field/meta_field.py +76 -0
- metaobjects/meta/core/identity/__init__.py +0 -0
- metaobjects/meta/core/identity/identity_constants.py +19 -0
- metaobjects/meta/core/identity/meta_identity.py +8 -0
- metaobjects/meta/core/object/__init__.py +0 -0
- metaobjects/meta/core/object/meta_object.py +65 -0
- metaobjects/meta/core/object/meta_object_aware.py +43 -0
- metaobjects/meta/core/object/object_class_registry.py +56 -0
- metaobjects/meta/core/object/object_constants.py +13 -0
- metaobjects/meta/core/object/object_extract.py +400 -0
- metaobjects/meta/core/object/value_object.py +70 -0
- metaobjects/meta/core/relationship/__init__.py +0 -0
- metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
- metaobjects/meta/core/relationship/meta_relationship.py +54 -0
- metaobjects/meta/core/relationship/relationship_constants.py +51 -0
- metaobjects/meta/core/validator/__init__.py +0 -0
- metaobjects/meta/core/validator/validator_constants.py +18 -0
- metaobjects/meta/meta_data.py +206 -0
- metaobjects/meta/meta_root.py +8 -0
- metaobjects/meta/persistence/__init__.py +0 -0
- metaobjects/meta/persistence/db/__init__.py +1 -0
- metaobjects/meta/persistence/db/db_constants.py +41 -0
- metaobjects/meta/persistence/db/db_provider.py +60 -0
- metaobjects/meta/persistence/origin/__init__.py +0 -0
- metaobjects/meta/persistence/origin/meta_origin.py +8 -0
- metaobjects/meta/persistence/origin/origin_constants.py +20 -0
- metaobjects/meta/persistence/source/__init__.py +0 -0
- metaobjects/meta/persistence/source/meta_source.py +137 -0
- metaobjects/meta/persistence/source/source_constants.py +115 -0
- metaobjects/meta/presentation/__init__.py +0 -0
- metaobjects/meta/presentation/layout/__init__.py +0 -0
- metaobjects/meta/presentation/layout/layout_constants.py +13 -0
- metaobjects/meta/presentation/layout/meta_layout.py +8 -0
- metaobjects/meta/presentation/view/__init__.py +0 -0
- metaobjects/meta/presentation/view/meta_view.py +8 -0
- metaobjects/meta/presentation/view/view_constants.py +22 -0
- metaobjects/meta/template/__init__.py +0 -0
- metaobjects/meta/template/meta_template.py +46 -0
- metaobjects/meta/template/template_constants.py +112 -0
- metaobjects/meta/template/template_provider.py +43 -0
- metaobjects/parser.py +380 -0
- metaobjects/parser_yaml.py +82 -0
- metaobjects/provider.py +111 -0
- metaobjects/py.typed +0 -0
- metaobjects/registry.py +210 -0
- metaobjects/registry_manifest.py +223 -0
- metaobjects/render/__init__.py +74 -0
- metaobjects/render/email_document.py +14 -0
- metaobjects/render/escapers.py +109 -0
- metaobjects/render/extract/__init__.py +59 -0
- metaobjects/render/extract/coerce.py +279 -0
- metaobjects/render/extract/extract.py +211 -0
- metaobjects/render/extract/extract_map.py +61 -0
- metaobjects/render/extract/json_forgiving_reader.py +203 -0
- metaobjects/render/extract/locate.py +65 -0
- metaobjects/render/extract/normalize.py +96 -0
- metaobjects/render/extract/strip.py +20 -0
- metaobjects/render/extract/types.py +332 -0
- metaobjects/render/extract/xml_forgiving_reader.py +162 -0
- metaobjects/render/filesystem_provider.py +51 -0
- metaobjects/render/prompt/__init__.py +32 -0
- metaobjects/render/prompt/output_format_renderer.py +340 -0
- metaobjects/render/prompt/output_format_spec.py +28 -0
- metaobjects/render/prompt/prompt_field.py +29 -0
- metaobjects/render/prompt/prompt_overrides.py +29 -0
- metaobjects/render/prompt/prompt_style.py +38 -0
- metaobjects/render/renderer.py +358 -0
- metaobjects/render/verify.py +266 -0
- metaobjects/runtime/__init__.py +39 -0
- metaobjects/runtime/llm_recorder.py +210 -0
- metaobjects/runtime/n2m_resolver.py +155 -0
- metaobjects/runtime/object_manager.py +715 -0
- metaobjects/runtime/tph.py +50 -0
- metaobjects/serializer_json.py +172 -0
- metaobjects/shared/__init__.py +0 -0
- metaobjects/shared/base_types.py +16 -0
- metaobjects/shared/separators.py +4 -0
- metaobjects/shared/structural.py +9 -0
- metaobjects/source/__init__.py +79 -0
- metaobjects/source/error_source.py +266 -0
- metaobjects/source/json_path.py +106 -0
- metaobjects/source/semantic_diff.py +98 -0
- metaobjects/source/yaml_positions.py +174 -0
- metaobjects/super_resolve.py +128 -0
- metaobjects/yaml_desugar.py +481 -0
- metaobjects-0.9.0.dist-info/METADATA +97 -0
- metaobjects-0.9.0.dist-info/RECORD +181 -0
- metaobjects-0.9.0.dist-info/WHEEL +4 -0
- metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
- metaobjects-0.9.0.dist-info/licenses/LICENSE +189 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""FR-010 artifact 1 — output-format prompt renderer ("produce your answer like this").
|
|
2
|
+
|
|
3
|
+
Renders an :class:`OutputFormatSpec` into a prompt fragment that teaches an LLM how
|
|
4
|
+
to shape its answer. Three comment-free styles (guide / inline / exampleOnly) × two
|
|
5
|
+
formats (json / xml). Guidance is carried in prose / inline placeholders / a filled
|
|
6
|
+
skeleton — NEVER in comments (models ignore them).
|
|
7
|
+
|
|
8
|
+
Cross-port INVARIANT: the rendered text is byte-identical to the Java/C#/Kotlin/TS
|
|
9
|
+
reference (``com.metaobjects.render.prompt.OutputFormatRenderer``). Do not change the
|
|
10
|
+
verbatim prose, skeleton shapes, or the numeric-vs-quoted decision.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
|
|
16
|
+
import metaobjects.render.escapers as escapers
|
|
17
|
+
from metaobjects.render.prompt.output_format_spec import OutputFormatSpec
|
|
18
|
+
from metaobjects.render.prompt.prompt_field import PromptField
|
|
19
|
+
from metaobjects.render.prompt.prompt_overrides import PromptOverrides
|
|
20
|
+
from metaobjects.render.prompt.prompt_style import PromptStyle
|
|
21
|
+
from metaobjects.render.extract import FieldKind, Format
|
|
22
|
+
|
|
23
|
+
_NUMERIC_KINDS: frozenset[FieldKind] = frozenset(
|
|
24
|
+
{FieldKind.INT, FieldKind.LONG, FieldKind.DOUBLE, FieldKind.BOOLEAN}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_INDENT = " "
|
|
28
|
+
_MAX_NEST_DEPTH = 8
|
|
29
|
+
|
|
30
|
+
# Render mode shared by the example/inline skeleton recursion.
|
|
31
|
+
_MODE_EXAMPLE = "example"
|
|
32
|
+
_MODE_INLINE = "inline"
|
|
33
|
+
|
|
34
|
+
# The render engine OWNS format-keyed escaping; the Format enum's UPPER values
|
|
35
|
+
# ("JSON"/"XML") map to the lowercase escaper-registry keys.
|
|
36
|
+
def _escape_xml(s: str) -> str:
|
|
37
|
+
return escapers.escape(escapers.FORMAT_XML, s)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _escape_json(s: str) -> str:
|
|
41
|
+
return escapers.escape(escapers.FORMAT_JSON, s)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def render_output_format(spec: OutputFormatSpec, overrides: PromptOverrides) -> str:
|
|
45
|
+
"""Render an :class:`OutputFormatSpec` into an output-format prompt fragment.
|
|
46
|
+
|
|
47
|
+
The effective style is the override's style if present, otherwise the spec's.
|
|
48
|
+
"""
|
|
49
|
+
effective_style = overrides.style if overrides.style is not None else spec.style
|
|
50
|
+
if effective_style is PromptStyle.EXAMPLE_ONLY:
|
|
51
|
+
return _render_example_only(spec, overrides)
|
|
52
|
+
if effective_style is PromptStyle.INLINE:
|
|
53
|
+
return _render_inline(spec, overrides)
|
|
54
|
+
return _render_guide(spec, overrides)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---- INLINE ----------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _render_inline(spec: OutputFormatSpec, overrides: PromptOverrides) -> str:
|
|
61
|
+
if spec.format is Format.XML:
|
|
62
|
+
return _render_xml_skeleton(spec, overrides, _MODE_INLINE)
|
|
63
|
+
return _render_json_skeleton(spec, overrides, _MODE_INLINE)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _inline_content(field: PromptField, overrides: PromptOverrides) -> str:
|
|
67
|
+
if field.kind is FieldKind.ENUM and field.enum_values:
|
|
68
|
+
return " | ".join(field.enum_values)
|
|
69
|
+
if field.kind is FieldKind.BOOLEAN:
|
|
70
|
+
return "true | false"
|
|
71
|
+
instruction = _resolve_instruction(field, overrides)
|
|
72
|
+
if instruction is not None:
|
|
73
|
+
return "{" + instruction + "}"
|
|
74
|
+
return "{" + field.name + "}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resolve_instruction(field: PromptField, overrides: PromptOverrides) -> str | None:
|
|
78
|
+
"""Effective instruction: override first, then the field default, else None."""
|
|
79
|
+
ov = overrides.instructions.get(field.name)
|
|
80
|
+
if ov is not None:
|
|
81
|
+
return ov
|
|
82
|
+
return field.instruction
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---- GUIDE -----------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _render_guide(spec: OutputFormatSpec, overrides: PromptOverrides) -> str:
|
|
89
|
+
sb = "Fill in each field as described below:\n"
|
|
90
|
+
sb += _guide_fields(spec, overrides, "", {id(spec)}, 0)
|
|
91
|
+
sb += "\nRespond exactly like this:\n"
|
|
92
|
+
sb += _render_example_only(spec, overrides)
|
|
93
|
+
return sb
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _guide_fields(
|
|
97
|
+
spec: OutputFormatSpec,
|
|
98
|
+
overrides: PromptOverrides,
|
|
99
|
+
prefix: str,
|
|
100
|
+
path: set[int],
|
|
101
|
+
depth: int,
|
|
102
|
+
) -> str:
|
|
103
|
+
sb = ""
|
|
104
|
+
for field in spec.fields:
|
|
105
|
+
display_name = prefix + field.name
|
|
106
|
+
sb += _guide_entry(field, overrides, display_name)
|
|
107
|
+
if _can_expand(field, path, depth):
|
|
108
|
+
nested = field.nested
|
|
109
|
+
assert nested is not None
|
|
110
|
+
child_prefix = (
|
|
111
|
+
f"{display_name}[]." if field.array else f"{display_name}."
|
|
112
|
+
)
|
|
113
|
+
path.add(id(nested))
|
|
114
|
+
sb += _guide_fields(nested, overrides, child_prefix, path, depth + 1)
|
|
115
|
+
path.discard(id(nested))
|
|
116
|
+
return sb
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _guide_entry(
|
|
120
|
+
field: PromptField, overrides: PromptOverrides, display_name: str
|
|
121
|
+
) -> str:
|
|
122
|
+
req = "required" if field.required else "optional"
|
|
123
|
+
sb = f"- {display_name} ({req})"
|
|
124
|
+
instruction = _resolve_instruction(field, overrides)
|
|
125
|
+
if instruction is not None:
|
|
126
|
+
sb += f": {instruction}"
|
|
127
|
+
sb += "\n"
|
|
128
|
+
if field.kind is FieldKind.ENUM and field.enum_values:
|
|
129
|
+
sb += f" one of {', '.join(field.enum_values)}\n"
|
|
130
|
+
enum_doc = field.enum_doc
|
|
131
|
+
if enum_doc is not None:
|
|
132
|
+
for val in field.enum_values:
|
|
133
|
+
doc = enum_doc.get(val)
|
|
134
|
+
if doc is not None:
|
|
135
|
+
sb += f" {val} = {doc}\n"
|
|
136
|
+
eg = _example_value_if_declared(field, overrides)
|
|
137
|
+
if eg is not None:
|
|
138
|
+
sb += f" e.g. {eg}\n"
|
|
139
|
+
return sb
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ---- EXAMPLE-ONLY (also the skeleton appended by GUIDE) ---------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _render_example_only(spec: OutputFormatSpec, overrides: PromptOverrides) -> str:
|
|
146
|
+
if spec.format is Format.XML:
|
|
147
|
+
return _render_xml_skeleton(spec, overrides, _MODE_EXAMPLE)
|
|
148
|
+
return _render_json_skeleton(spec, overrides, _MODE_EXAMPLE)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---- JSON skeleton (recursive) ---------------------------------------------
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _render_json_skeleton(
|
|
155
|
+
spec: OutputFormatSpec, overrides: PromptOverrides, mode: str
|
|
156
|
+
) -> str:
|
|
157
|
+
return _json_object(spec, overrides, "", mode, {id(spec)}, 0)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _json_object(
|
|
161
|
+
spec: OutputFormatSpec,
|
|
162
|
+
overrides: PromptOverrides,
|
|
163
|
+
brace_indent: str,
|
|
164
|
+
mode: str,
|
|
165
|
+
path: set[int],
|
|
166
|
+
depth: int,
|
|
167
|
+
) -> str:
|
|
168
|
+
# Empty object is `{\n<brace_indent>}` (cross-port parity), not `{\n\n}`.
|
|
169
|
+
if not spec.fields:
|
|
170
|
+
return f"{{\n{brace_indent}}}"
|
|
171
|
+
field_indent = brace_indent + _INDENT
|
|
172
|
+
lines = [
|
|
173
|
+
f'{field_indent}"{field.name}": '
|
|
174
|
+
f"{_json_value(field, overrides, field_indent, mode, path, depth)}"
|
|
175
|
+
for field in spec.fields
|
|
176
|
+
]
|
|
177
|
+
return "{\n" + ",\n".join(lines) + f"\n{brace_indent}}}"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _json_value(
|
|
181
|
+
field: PromptField,
|
|
182
|
+
overrides: PromptOverrides,
|
|
183
|
+
indent: str,
|
|
184
|
+
mode: str,
|
|
185
|
+
path: set[int],
|
|
186
|
+
depth: int,
|
|
187
|
+
) -> str:
|
|
188
|
+
if field.array:
|
|
189
|
+
return _json_array(field, overrides, indent, mode, path, depth)
|
|
190
|
+
if field.kind is FieldKind.OBJECT:
|
|
191
|
+
return _json_object_field(field, overrides, indent, mode, path, depth)
|
|
192
|
+
return _json_leaf(field, overrides, mode)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _json_leaf(field: PromptField, overrides: PromptOverrides, mode: str) -> str:
|
|
196
|
+
if mode == _MODE_INLINE:
|
|
197
|
+
return '"' + _escape_json(_inline_content(field, overrides)) + '"'
|
|
198
|
+
value = _example_value(field, overrides)
|
|
199
|
+
if _is_numeric_or_boolean(field.kind, value):
|
|
200
|
+
return value
|
|
201
|
+
return '"' + _escape_json(value) + '"'
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _json_object_field(
|
|
205
|
+
field: PromptField,
|
|
206
|
+
overrides: PromptOverrides,
|
|
207
|
+
indent: str,
|
|
208
|
+
mode: str,
|
|
209
|
+
path: set[int],
|
|
210
|
+
depth: int,
|
|
211
|
+
) -> str:
|
|
212
|
+
if not _can_expand(field, path, depth):
|
|
213
|
+
return _json_leaf(field, overrides, mode)
|
|
214
|
+
nested = field.nested
|
|
215
|
+
assert nested is not None
|
|
216
|
+
path.add(id(nested))
|
|
217
|
+
out = _json_object(nested, overrides, indent, mode, path, depth + 1)
|
|
218
|
+
path.discard(id(nested))
|
|
219
|
+
return out
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _json_array(
|
|
223
|
+
field: PromptField,
|
|
224
|
+
overrides: PromptOverrides,
|
|
225
|
+
indent: str,
|
|
226
|
+
mode: str,
|
|
227
|
+
path: set[int],
|
|
228
|
+
depth: int,
|
|
229
|
+
) -> str:
|
|
230
|
+
elem_indent = indent + _INDENT
|
|
231
|
+
if _can_expand(field, path, depth):
|
|
232
|
+
nested = field.nested
|
|
233
|
+
assert nested is not None
|
|
234
|
+
path.add(id(nested))
|
|
235
|
+
elem = _json_object(nested, overrides, elem_indent, mode, path, depth + 1)
|
|
236
|
+
path.discard(id(nested))
|
|
237
|
+
else:
|
|
238
|
+
elem = _json_leaf(field, overrides, mode)
|
|
239
|
+
return f"[\n{elem_indent}{elem}\n{indent}]"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---- XML skeleton (recursive) ----------------------------------------------
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _render_xml_skeleton(
|
|
246
|
+
spec: OutputFormatSpec, overrides: PromptOverrides, mode: str
|
|
247
|
+
) -> str:
|
|
248
|
+
body = _xml_body(spec, overrides, _INDENT, mode, {id(spec)}, 0)
|
|
249
|
+
return f"<{spec.root_name}>\n{body}</{spec.root_name}>"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _xml_body(
|
|
253
|
+
spec: OutputFormatSpec,
|
|
254
|
+
overrides: PromptOverrides,
|
|
255
|
+
indent: str,
|
|
256
|
+
mode: str,
|
|
257
|
+
path: set[int],
|
|
258
|
+
depth: int,
|
|
259
|
+
) -> str:
|
|
260
|
+
return "".join(
|
|
261
|
+
_xml_field(field, overrides, indent, mode, path, depth)
|
|
262
|
+
for field in spec.fields
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _xml_field(
|
|
267
|
+
field: PromptField,
|
|
268
|
+
overrides: PromptOverrides,
|
|
269
|
+
indent: str,
|
|
270
|
+
mode: str,
|
|
271
|
+
path: set[int],
|
|
272
|
+
depth: int,
|
|
273
|
+
) -> str:
|
|
274
|
+
if _can_expand(field, path, depth):
|
|
275
|
+
nested = field.nested
|
|
276
|
+
assert nested is not None
|
|
277
|
+
path.add(id(nested))
|
|
278
|
+
body = _xml_body(nested, overrides, indent + _INDENT, mode, path, depth + 1)
|
|
279
|
+
path.discard(id(nested))
|
|
280
|
+
return f"{indent}<{field.name}>\n{body}{indent}</{field.name}>\n"
|
|
281
|
+
content = (
|
|
282
|
+
_inline_content(field, overrides)
|
|
283
|
+
if mode == _MODE_INLINE
|
|
284
|
+
else _example_value(field, overrides)
|
|
285
|
+
)
|
|
286
|
+
return f"{indent}<{field.name}>{_escape_xml(content)}</{field.name}>\n"
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# ---- nested-expansion guard ------------------------------------------------
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _can_expand(field: PromptField, path: set[int], depth: int) -> bool:
|
|
293
|
+
"""Expand an OBJECT field only when it has a nested spec, the depth bound is not
|
|
294
|
+
exceeded, and it would not re-enter a spec already on the current path.
|
|
295
|
+
|
|
296
|
+
The cycle guard is REFERENCE IDENTITY via ``id()``: frozen dataclasses compare by
|
|
297
|
+
value (``eq=True``), so two value-equal sibling specs must both still expand. ``id``
|
|
298
|
+
keys distinguish them.
|
|
299
|
+
"""
|
|
300
|
+
return (
|
|
301
|
+
field.kind is FieldKind.OBJECT
|
|
302
|
+
and field.nested is not None
|
|
303
|
+
and depth < _MAX_NEST_DEPTH
|
|
304
|
+
and id(field.nested) not in path
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _example_value_if_declared(field: PromptField, overrides: PromptOverrides) -> str | None:
|
|
309
|
+
from_override = overrides.examples.get(field.name)
|
|
310
|
+
if from_override is not None:
|
|
311
|
+
return from_override
|
|
312
|
+
if field.example is not None:
|
|
313
|
+
return field.example
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _example_value(field: PromptField, overrides: PromptOverrides) -> str:
|
|
318
|
+
from_override = overrides.examples.get(field.name)
|
|
319
|
+
if from_override is not None:
|
|
320
|
+
return from_override
|
|
321
|
+
if field.example is not None:
|
|
322
|
+
return field.example
|
|
323
|
+
if field.kind is FieldKind.ENUM and field.enum_values:
|
|
324
|
+
return field.enum_values[0]
|
|
325
|
+
return "{" + field.name + "}"
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# A canonical ASCII numeric literal. Mirrors the extract engine's `_ASCII_NUMERIC`:
|
|
329
|
+
# `[0-9]` (not `\d`) keeps it ASCII-only, rejecting Unicode digits, underscore digit
|
|
330
|
+
# grouping, and radix prefixes (0x../0b../0o..) so the emitted JSON stays valid and
|
|
331
|
+
# cross-port-identical.
|
|
332
|
+
_ASCII_NUMERIC = re.compile(r"^[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _is_numeric_or_boolean(kind: FieldKind, value: str) -> bool:
|
|
336
|
+
if kind not in _NUMERIC_KINDS:
|
|
337
|
+
return False
|
|
338
|
+
if value in ("true", "false"):
|
|
339
|
+
return True
|
|
340
|
+
return bool(_ASCII_NUMERIC.match(value.strip()))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""FR-010 artifact 1 — a complete output-format descriptor."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from metaobjects.render.extract import Format
|
|
8
|
+
from metaobjects.render.prompt.prompt_style import PromptStyle
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from metaobjects.render.prompt.prompt_field import PromptField
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class OutputFormatSpec:
|
|
16
|
+
"""A complete output-format descriptor.
|
|
17
|
+
|
|
18
|
+
The format, the root element/object name, the default presentation style, and the
|
|
19
|
+
ordered fields. Drives :func:`render_output_format`.
|
|
20
|
+
|
|
21
|
+
Precondition: ``root_name`` must be identifier-safe (valid XML element name / JSON
|
|
22
|
+
key). The renderer does not escape it.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
format: Format
|
|
26
|
+
root_name: str
|
|
27
|
+
style: PromptStyle
|
|
28
|
+
fields: list[PromptField] = field(default_factory=list)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""FR-010 artifact 1 — one field of an output-format fragment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from metaobjects.render.extract import FieldKind
|
|
7
|
+
from metaobjects.render.prompt.output_format_spec import OutputFormatSpec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class PromptField:
|
|
12
|
+
"""One field of an output-format fragment.
|
|
13
|
+
|
|
14
|
+
``enum_values``/``enum_doc`` are non-None only for ENUM; ``nested`` non-None only
|
|
15
|
+
for OBJECT; ``example``/``instruction`` are nullable.
|
|
16
|
+
|
|
17
|
+
Precondition: ``name`` must be identifier-safe (valid XML element name / JSON key).
|
|
18
|
+
The renderer does not escape field names.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
kind: FieldKind
|
|
23
|
+
required: bool
|
|
24
|
+
array: bool = False
|
|
25
|
+
enum_values: list[str] | None = None
|
|
26
|
+
enum_doc: dict[str, str] | None = None
|
|
27
|
+
example: str | None = None
|
|
28
|
+
instruction: str | None = None
|
|
29
|
+
nested: OutputFormatSpec | None = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""FR-010 artifact 1 — render-time overrides of the metadata defaults."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
from metaobjects.render.prompt.prompt_style import PromptStyle
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class PromptOverrides:
|
|
12
|
+
"""Render-time overrides of the metadata defaults.
|
|
13
|
+
|
|
14
|
+
``style`` of ``None`` keeps the spec's style; the maps override
|
|
15
|
+
``PromptField.example``/``PromptField.instruction`` per field name.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
style: PromptStyle | None = None
|
|
19
|
+
examples: dict[str, str] = field(default_factory=dict)
|
|
20
|
+
instructions: dict[str, str] = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# No overrides — keep every metadata default. Mirrors Java ``PromptOverrides.none()``.
|
|
24
|
+
PROMPT_OVERRIDES_NONE: Final = PromptOverrides()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def no_overrides() -> PromptOverrides:
|
|
28
|
+
"""No overrides — keep every metadata default."""
|
|
29
|
+
return PROMPT_OVERRIDES_NONE
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""FR-010 artifact 1 — how the output-format fragment teaches the model.
|
|
2
|
+
|
|
3
|
+
Guidance is NEVER carried in comments (models ignore them) — it lives in prose /
|
|
4
|
+
inline placeholders / a filled skeleton. Default is ``"guide"``.
|
|
5
|
+
|
|
6
|
+
Tier-2 idiomatic Python: the Java SCREAMING_SNAKE enum (GUIDE/INLINE/EXAMPLE_ONLY)
|
|
7
|
+
becomes a string enum whose VALUES are the wire ``@promptStyle`` values
|
|
8
|
+
(``"guide"`` / ``"inline"`` / ``"exampleOnly"``) — no name<->wire mapping table.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PromptStyle(Enum):
|
|
16
|
+
"""Presentation style of the rendered output-format fragment.
|
|
17
|
+
|
|
18
|
+
- ``GUIDE``: prose field list ("Fill in each field…") followed by an example skeleton.
|
|
19
|
+
- ``INLINE``: a single skeleton whose field values are inline placeholders / enum choices.
|
|
20
|
+
- ``EXAMPLE_ONLY``: just a filled example skeleton, nothing else.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
GUIDE = "guide"
|
|
24
|
+
INLINE = "inline"
|
|
25
|
+
EXAMPLE_ONLY = "exampleOnly"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def prompt_style_from(s: str | None) -> PromptStyle:
|
|
29
|
+
"""Map the ``@promptStyle`` attribute string to a :class:`PromptStyle`.
|
|
30
|
+
|
|
31
|
+
``None`` or any unrecognized value falls back to ``GUIDE`` (matches Java
|
|
32
|
+
``PromptStyle.from``).
|
|
33
|
+
"""
|
|
34
|
+
if s == PromptStyle.INLINE.value:
|
|
35
|
+
return PromptStyle.INLINE
|
|
36
|
+
if s == PromptStyle.EXAMPLE_ONLY.value:
|
|
37
|
+
return PromptStyle.EXAMPLE_ONLY
|
|
38
|
+
return PromptStyle.GUIDE
|