3838import collections .abc
3939import functools
4040import inspect
41- import textwrap
4241import types
4342import typing
4443from collections .abc import Callable
6968)
7069from effectful .handlers .llm .harness .synthesis .function import (
7170 SplicedRegion ,
72- SynthesizedFunction ,
71+ _checked_source ,
7372 _def_nodes ,
7473 _recover_skill_def ,
74+ _synthesized_source_schema ,
7575)
7676from effectful .handlers .llm .types import Encodable , Skill , Tool
7777from effectful .ops .semantics import fwd , handler
@@ -177,40 +177,20 @@ def __class_getitem__(cls, item):
177177 return types .GenericAlias (cls , item )
178178
179179
180- class SynthesizedSkillBody (SynthesizedFunction ):
181- """Structured output for synthesizing a `Skill`'s body (`write_and_run_body`).
180+ # A Skill body is type-checked against the Skill's own (already-annotated) signature
181+ # by `_splice_body`, so the synthesized body's own annotations are optional.
182+ _SKILL_BODY_CONSTRAINTS : list [str ] = [
183+ "Write the function with the Skill's signature; parameter and return "
184+ "annotations are optional." ,
185+ "Do not include a docstring or doctests; the Skill's are supplied automatically." ,
186+ ]
182187
183- Decoded through `_pydantic_skill_body`: the function is type-checked against
184- the enclosing Skill's source and its doctests are run with self/recursive
185- calls routed to the synthesized implementation.
186-
187- Unlike `SynthesizedFunction`, the parameter and return *annotations* are not
188- required: a Skill body is type-checked against the Skill's own signature
189- (see `splice_skill_body`), so the model may omit or vary them -- in
190- particular it need not annotate the ``self`` receiver of an instance-method
191- Skill.
192- """
193-
194- code : str = pydantic .Field (
195- ...,
196- description = textwrap .dedent ("""
197- The complete Python source implementing the Skill shown in its spec.
198- The code MUST satisfy the following constraints, or it will fail validation:
199-
200- <constraints>
201- 1. The code MUST be one complete syntactically valid Python module.
202- 2. The code MUST NOT use star imports or ``__future__`` imports.
203- 3. The function definition MUST be the LAST statement - do not add any code after it.
204- 4. Write the function with the Skill's signature; parameter and return
205- annotations are optional.
206- 5. Do not include a docstring or doctests; the Skill's are supplied automatically.
207- </constraints>
208- """ ),
209- )
210-
211- # A Skill body is checked against the Skill's own (already-annotated)
212- # signature, so the synthesized body's annotations are optional.
213- _require_annotations : typing .ClassVar [bool ] = False
188+ _METHOD_SKILL_BODY_CONSTRAINTS : list [str ] = [
189+ "Write the function with the Skill's signature: its FIRST parameter is the "
190+ "instance receiver ``self`` (which you may leave unannotated); all other "
191+ "parameter and return annotations are optional too." ,
192+ "Do not include a docstring or doctests; the Skill's are supplied automatically." ,
193+ ]
214194
215195
216196@TypeToPydanticType .register (SkillBody )
@@ -223,30 +203,30 @@ def _pydantic_skill_body(ty: typing.Any) -> typing.Any:
223203 implementation, so a doctest that calls the Skill (including for recursion)
224204 exercises the freshly synthesized code rather than re-invoking the model.
225205 """
226- typed_enc = SynthesizedSkillBody . _create_model_from_callable_type (
206+ schema = _synthesized_source_schema (
227207 ty if typing .get_args (ty ) else Callable [..., typing .Any ], # type: ignore[arg-type]
208+ "the Skill shown in its spec" ,
209+ _SKILL_BODY_CONSTRAINTS ,
228210 )
229211
230212 def _validate (
231- value : SynthesizedSkillBody | dict | str | Callable ,
213+ value : str | Callable ,
232214 info : pydantic .ValidationInfo ,
233215 ) -> Callable :
234- if isinstance (value , str ):
235- value = typed_enc .model_validate ({"code" : value })
236- elif isinstance (value , dict ):
237- value = typed_enc .model_validate (value )
238- elif callable (value ):
216+ value = _checked_source (value , require_annotations = False )
217+ if not isinstance (value , str ):
239218 return typing .cast (Callable , value )
219+
240220 ctx = info .context or {}
241221 anchor = ctx .get (_TYPE_CHECK_ANCHOR_KEY )
242222 if anchor is not None :
243223 # skill bodies should not have access to call-local variables
244224 assert isinstance (anchor , Skill )
245225 ctx = anchor .__context__
246226
247- filename = f"<synthesis:{ id (value . code )} >"
227+ filename = f"<synthesis:{ id (value )} >"
248228 module : ast .Module = effectful .handlers .llm .harness .execution .hooks .parse (
249- value . code , filename
229+ value , filename
250230 )
251231
252232 # `None` means the Skill's source can't be recovered (REPL/exec/notebook
@@ -282,7 +262,7 @@ def _validate(
282262
283263 return typing .Annotated [
284264 pydantic .InstanceOf [ty_ ], # type: ignore
285- pydantic .BeforeValidator (_validate , json_schema_input_type = typed_enc ),
265+ pydantic .BeforeValidator (_validate , json_schema_input_type = schema ),
286266 pydantic .PlainSerializer (_serialize_callable , return_type = EncodedFunction ),
287267 ]
288268
@@ -299,58 +279,6 @@ class MethodSkillBody(SkillBody):
299279 """
300280
301281
302- class SynthesizedMethodSkillBody (SynthesizedSkillBody ):
303- """Structured output for synthesizing an *instance-method* `Skill`'s body.
304-
305- Decoded through `_pydantic_skill_body`: the function is type-checked against
306- the enclosing Skill's source and its doctests are run with self/recursive
307- calls routed to the synthesized implementation.
308-
309- Unlike `SynthesizedFunction`, the parameter and return *annotations* are not
310- required: a Skill body is type-checked against the Skill's own signature
311- (see `splice_skill_body`), so the model may omit or vary them -- in
312- particular it need not annotate the ``self`` receiver of an instance-method
313- Skill.
314- """
315-
316- code : str = pydantic .Field (
317- ...,
318- description = textwrap .dedent ("""
319- The complete Python source implementing the instance-method Skill shown in
320- its spec. The code MUST satisfy the following constraints, or it will fail
321- validation:
322-
323- <constraints>
324- 1. The code MUST be one complete syntactically valid Python module.
325- 2. The code MUST NOT use star imports or ``__future__`` imports.
326- 3. The function definition MUST be the LAST statement - do not add any code after it.
327- 4. Write the function with the Skill's signature: its FIRST parameter is the
328- instance receiver ``self`` (which you may leave unannotated); all other parameter
329- and return annotations are optional too.
330- 5. Do not include a docstring or doctests; the Skill's are supplied automatically.
331- </constraints>
332- """ ),
333- )
334-
335- @classmethod
336- def _param_names (cls , param_types : typing .Iterable [typing .Any ]) -> list [str ]:
337- # The method's callable type already carries the receiver as its first
338- # parameter (with an uninformative Agent-class type); relabel it ``self`` so
339- # the model reproduces it rather than inventing one -- do NOT prepend a receiver.
340- names = super ()._param_names (param_types )
341- if names :
342- names [0 ] = "self"
343- return names
344-
345- @classmethod
346- def _extra_instructions (cls ) -> str :
347- return (
348- "\n \n This implements an instance method: the first parameter is the "
349- "instance receiver `self`. Include it as the first parameter; you may "
350- "leave it unannotated."
351- )
352-
353-
354282def _class_skill_of (op : typing .Any ) -> typing .Any | None :
355283 """The class-level `Skill` underlying an Agent-method Skill ``op``.
356284
@@ -382,30 +310,31 @@ def _pydantic_method_skill_body(ty: typing.Any) -> typing.Any:
382310 their own instances -- route ``agent.method(...)`` on *any* instance to the
383311 synthesized implementation.
384312 """
385- typed_enc = SynthesizedMethodSkillBody . _create_model_from_callable_type (
313+ schema = _synthesized_source_schema (
386314 ty if typing .get_args (ty ) else Callable [..., typing .Any ], # type: ignore[arg-type]
315+ "the instance-method Skill shown in its spec" ,
316+ _METHOD_SKILL_BODY_CONSTRAINTS ,
317+ receiver = True ,
387318 )
388319
389320 def _validate (
390- value : SynthesizedMethodSkillBody | dict | str | Callable ,
321+ value : str | Callable ,
391322 info : pydantic .ValidationInfo ,
392323 ) -> Callable :
393- if isinstance (value , str ):
394- value = typed_enc .model_validate ({"code" : value })
395- elif isinstance (value , dict ):
396- value = typed_enc .model_validate (value )
397- elif callable (value ):
324+ value = _checked_source (value , require_annotations = False )
325+ if not isinstance (value , str ):
398326 return typing .cast (Callable , value )
327+
399328 ctx = info .context or {}
400329 anchor = ctx .get (_TYPE_CHECK_ANCHOR_KEY )
401330 if anchor is not None :
402331 # skill bodies should not have access to call-local variables
403332 assert isinstance (anchor , Skill )
404333 ctx = anchor .__context__
405334
406- filename = f"<synthesis:{ id (value . code )} >"
335+ filename = f"<synthesis:{ id (value )} >"
407336 module : ast .Module = effectful .handlers .llm .harness .execution .hooks .parse (
408- value . code , filename
337+ value , filename
409338 )
410339 anchor_asts = _recover_skill_def (anchor ) if anchor is not None else None
411340 if anchor_asts is not None :
@@ -445,7 +374,7 @@ def _doctest_apply(op, *args, **kwargs):
445374
446375 return typing .Annotated [
447376 pydantic .InstanceOf [ty_ ], # type: ignore
448- pydantic .BeforeValidator (_validate , json_schema_input_type = typed_enc ),
377+ pydantic .BeforeValidator (_validate , json_schema_input_type = schema ),
449378 pydantic .PlainSerializer (_serialize_callable , return_type = EncodedFunction ),
450379 ]
451380
0 commit comments