diff --git a/src/agentics/core/agentics.py b/src/agentics/core/agentics.py index ee63c0225..c8fd5aac6 100644 --- a/src/agentics/core/agentics.py +++ b/src/agentics/core/agentics.py @@ -94,7 +94,7 @@ class AG(BaseModel, Generic[T]): None, description="""Python code for the used type""", ) - states: List[BaseModel] = [] + states: List[BaseModel] = Field(default_factory=list) tools: Optional[List[Any]] = Field(None, exclude=True) transduce_fields: Optional[List[str]] = Field( None, @@ -549,6 +549,48 @@ async def llm_call(input: AGString) -> AGString: if self.transduce_fields else self.atype ) + + # If self has no states, we need to initialize with empty states based on the input + if len(self.states) == 0: + # Helper function to create an empty state, handling required fields + def create_empty_state(): + try: + return self.atype() + except Exception: + # If the model has required fields, use model_construct with defaults + # Get default values for all fields + defaults = {} + for field_name, field_info in self.atype.model_fields.items(): + if field_info.is_required(): + # Provide sensible defaults based on field type + if hasattr(field_info.annotation, "__origin__"): + origin = field_info.annotation.__origin__ + if origin is list: + defaults[field_name] = [] + elif origin is dict: + defaults[field_name] = {} + else: + defaults[field_name] = None + else: + defaults[field_name] = None + return self.atype.model_construct(**defaults) + + if isinstance(other, AG): + # Match the number of states in other + self.states = [create_empty_state() for _ in range(len(other.states))] + elif is_str_or_list_of_str(other): + # For strings or list of strings, create one state per input + if isinstance(other, str): + self.states = [create_empty_state()] + else: + self.states = [create_empty_state() for _ in range(len(other))] + elif isinstance(other, list): + # For other lists, create one state per item + self.states = [create_empty_state() for _ in range(len(other))] + else: + # For single non-list inputs, create one state + self.states = [create_empty_state()] + if isinstance(other, AG): if other.prompt_template: prompt_template = PromptTemplate.from_template(other.prompt_template) @@ -673,19 +715,37 @@ async def llm_call(input: AGString) -> AGString: ) allowed = self.atype.model_fields.keys() # pydantic v2 filtered = {k: v for k, v in data.items() if k in allowed} - merged = self.atype(**filtered) + try: + merged = self.atype(**filtered) + except Exception: + # If validation fails due to missing required fields, use model_construct with defaults + defaults = {} + for field_name, field_info in self.atype.model_fields.items(): + if field_info.is_required() and field_name not in filtered: + # Provide sensible defaults based on field type + if hasattr(field_info.annotation, "__origin__"): + origin = field_info.annotation.__origin__ + if origin is list: + defaults[field_name] = [] + elif origin is dict: + defaults[field_name] = {} + else: + defaults[field_name] = None + else: + defaults[field_name] = None + merged = self.atype.model_construct(**{**defaults, **filtered}) output.states.append(merged) # elif is_str_or_list_of_str(other): elif isinstance(other, list): for i in range(len(other)): - if isinstance(output_states[i], self.atype): + if i < len(output_states) and isinstance(output_states[i], self.atype): output.states.append(self.atype(**output_states[i].model_dump())) else: output.states.append(self.atype()) else: - if isinstance(output_states[0], self.atype): - output.states.append(self.atype(**output_states[i].model_dump())) + if len(output_states) > 0 and isinstance(output_states[0], self.atype): + output.states.append(self.atype(**output_states[0].model_dump())) if self.provide_explanations and isinstance(other, AG): target_explanation = AG(atype=Explanation) diff --git a/tests/official_tests/transducible_functions.py b/tests/official_tests/transducible_functions.py index 710245a6a..b96020967 100644 --- a/tests/official_tests/transducible_functions.py +++ b/tests/official_tests/transducible_functions.py @@ -37,4 +37,4 @@ async def write_an_email(state: GenericInput) -> Email: ) ) ) -print(single_mail.model_dump_json(indent=2)) +print(single_mail.value.model_dump_json(indent=2)) diff --git a/tutorials/transducible_functions.ipynb b/tutorials/transducible_functions.ipynb index 263cbc5d1..1c280b4a8 100644 --- a/tutorials/transducible_functions.ipynb +++ b/tutorials/transducible_functions.ipynb @@ -281,7 +281,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.11.14" } }, "nbformat": 4, diff --git a/uv.lock b/uv.lock index 5d891f633..03feb7ce3 100644 --- a/uv.lock +++ b/uv.lock @@ -16,12 +16,6 @@ required-markers = [ "platform_machine == 'x86_64' and sys_platform == 'linux'", ] -[manifest] -members = [ - "agentics-py", - "polymarkets", -] - [[package]] name = "absl-py" version = "2.3.1" @@ -3516,11 +3510,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/7c/535646d75a1c510065169ea65693613c7a6bc64491bea13e7dad4f028ff3/polyfactory-3.1.0-py3-none-any.whl", hash = "sha256:78171232342c25906d542513c9f00ebf41eadec2c67b498490a577024dd7e867", size = 61836, upload-time = "2025-11-25T08:10:14.893Z" }, ] -[[package]] -name = "polymarkets" -version = "0.1.0" -source = { virtual = "sandbox/polymarkets" } - [[package]] name = "portalocker" version = "2.7.0"