Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions src/agentics/core/agentics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/official_tests/transducible_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
2 changes: 1 addition & 1 deletion tutorials/transducible_functions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.12"
"version": "3.11.14"
}
},
"nbformat": 4,
Expand Down
11 changes: 0 additions & 11 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading