2424 {FieldKind .INT , FieldKind .LONG , FieldKind .DOUBLE , FieldKind .BOOLEAN }
2525)
2626
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+
2734# The render engine OWNS format-keyed escaping; the Format enum's UPPER values
2835# ("JSON"/"XML") map to the lowercase escaper-registry keys.
2936def _escape_xml (s : str ) -> str :
@@ -52,27 +59,8 @@ def render_output_format(spec: OutputFormatSpec, overrides: PromptOverrides) ->
5259
5360def _render_inline (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
5461 if spec .format is Format .XML :
55- return _render_xml_inline (spec , overrides )
56- return _render_json_inline (spec , overrides )
57-
58-
59- def _render_xml_inline (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
60- lines = [
61- f" <{ f .name } >{ _escape_xml (_inline_content (f , overrides ))} </{ f .name } >\n "
62- for f in spec .fields
63- ]
64- return f"<{ spec .root_name } >\n { '' .join (lines )} </{ spec .root_name } >"
65-
66-
67- def _render_json_inline (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
68- lines = [
69- f' "{ f .name } ": "{ _escape_json (_inline_content (f , overrides ))} "'
70- for f in spec .fields
71- ]
72- # Empty object is `{\n}` (cross-port parity), not `{\n\n}`.
73- if not lines :
74- return "{\n }"
75- return "{\n " + ",\n " .join (lines ) + "\n }"
62+ return _render_xml_skeleton (spec , overrides , _MODE_INLINE )
63+ return _render_json_skeleton (spec , overrides , _MODE_INLINE )
7664
7765
7866def _inline_content (field : PromptField , overrides : PromptOverrides ) -> str :
@@ -99,67 +87,224 @@ def _resolve_instruction(field: PromptField, overrides: PromptOverrides) -> str
9987
10088def _render_guide (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
10189 sb = "Fill in each field as described below:\n "
102- for field in spec .fields :
103- req = "required" if field .required else "optional"
104- sb += f"- { field .name } ({ req } )"
105- instruction = _resolve_instruction (field , overrides )
106- if instruction is not None :
107- sb += f": { instruction } "
108- sb += "\n "
109- if field .kind is FieldKind .ENUM and field .enum_values :
110- sb += f" one of { ', ' .join (field .enum_values )} \n "
111- enum_doc = field .enum_doc
112- if enum_doc is not None :
113- for val in field .enum_values :
114- doc = enum_doc .get (val )
115- if doc is not None :
116- sb += f" { val } = { doc } \n "
117- eg = _example_value_if_declared (field , overrides )
118- if eg is not None :
119- sb += f" e.g. { eg } \n "
90+ sb += _guide_fields (spec , overrides , "" , {id (spec )}, 0 )
12091 sb += "\n Respond exactly like this:\n "
12192 sb += _render_example_only (spec , overrides )
12293 return sb
12394
12495
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+
125142# ---- EXAMPLE-ONLY (also the skeleton appended by GUIDE) ---------------------
126143
127144
128145def _render_example_only (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
129146 if spec .format is Format .XML :
130- return _render_xml_skeleton (spec , overrides )
131- return _render_json_skeleton (spec , overrides )
147+ return _render_xml_skeleton (spec , overrides , _MODE_EXAMPLE )
148+ return _render_json_skeleton (spec , overrides , _MODE_EXAMPLE )
132149
133150
134- def _render_xml_skeleton (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
135- lines = [
136- f" <{ f .name } >{ _escape_xml (_example_value (f , overrides ))} </{ f .name } >\n "
137- for f in spec .fields
138- ]
139- return f"<{ spec .root_name } >\n { '' .join (lines )} </{ spec .root_name } >"
151+ # ---- JSON skeleton (recursive) ---------------------------------------------
140152
141153
142- def _render_json_skeleton (spec : OutputFormatSpec , overrides : PromptOverrides ) -> str :
143- # NOTE: FieldKind.OBJECT / nested fields are not expanded here — they render as a
144- # "{fieldName}" placeholder. Nested-object expansion is a bounded deferral
145- # (mirrors Java/C#/TS).
146- lines = [
147- f' "{ f .name } ": { _json_skeleton_value (f , overrides )} ' for f in spec .fields
148- ]
149- # Empty object is `{\n}` (cross-port parity), not `{\n\n}`.
150- if not lines :
151- return "{\n }"
152- return "{\n " + ",\n " .join (lines ) + "\n }"
154+ def _render_json_skeleton (
155+ spec : OutputFormatSpec , overrides : PromptOverrides , mode : str
156+ ) -> str :
157+ return _json_object (spec , overrides , "" , mode , {id (spec )}, 0 )
153158
154159
155- def _json_skeleton_value (field : PromptField , overrides : PromptOverrides ) -> str :
156- """The example value as a JSON literal: bare for numeric/boolean, else quoted."""
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 )) + '"'
157198 value = _example_value (field , overrides )
158199 if _is_numeric_or_boolean (field .kind , value ):
159200 return value
160201 return '"' + _escape_json (value ) + '"'
161202
162203
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+
163308def _example_value_if_declared (field : PromptField , overrides : PromptOverrides ) -> str | None :
164309 from_override = overrides .examples .get (field .name )
165310 if from_override is not None :
0 commit comments