diff --git a/pyproject.toml b/pyproject.toml index a1eeee12..eaf8b48c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ dependencies = [ "duckdb>=1.4.3", "pandas>=2", "async-lru>=2.0.5", + "apscheduler>=3.10.0", + "fastapi-sso>=0.10.0", ] diff --git a/src/agentics/core/agentics.py b/src/agentics/core/agentics.py index 87b6bc9b..6514c826 100644 --- a/src/agentics/core/agentics.py +++ b/src/agentics/core/agentics.py @@ -93,7 +93,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, @@ -138,11 +138,11 @@ class AG(BaseModel, Generic[T]): 20, description="The size of the bathes to be used when transduction type is amap", ) - areduce_batches: List[BaseModel] = [] + areduce_batches: List[BaseModel] = Field(default_factory=list) save_amap_batches_to_path: Optional[str] = None crew_prompt_params: Optional[Dict[str, str]] = Field( - { + default_factory=lambda: { "role": "Task Executor", "goal": "You execute tasks", "backstory": "You are always faithful and provide only fact based answers.", @@ -1063,21 +1063,26 @@ def product(self, other: AG) -> AG: def merge_states(self, other: AG) -> AG: """ - Merge states of two AGs pairwise - + Merge states of two AGs pairwise. + + The 'other' AG's fields take precedence over 'self' fields when there are conflicts. + This ensures that newly generated data (other) overwrites existing data (self). """ if len(self) == len(other): merged = self.clone() merged.states = [] merged.explanations = [] + # Put self.atype first so its fields appear first in the merged schema, + # but other.atype values will overwrite in the instance creation below merged.atype = merge_pydantic_models( self.atype, other.atype, name=f"Merged{self.atype.__name__}#{other.atype.__name__}", ) + # Create instances with self first, then other overwrites for self_state, other_state in zip(self, other): merged.states.append( - merged.atype(**other_state.model_dump(), **self_state.model_dump()) + merged.atype(**self_state.model_dump(), **other_state.model_dump()) ) return merged else: diff --git a/src/agentics/core/mellea_pydantic_transducer.py b/src/agentics/core/mellea_pydantic_transducer.py index 95774861..260539b0 100644 --- a/src/agentics/core/mellea_pydantic_transducer.py +++ b/src/agentics/core/mellea_pydantic_transducer.py @@ -65,13 +65,21 @@ async def structured_decoding_using_mellea( # At this point we're outside the `with`: session should be closed if mellea_output is None or mellea_output.content is None: - return targetAtype() + logging.warning( + f"Received None or empty response from LLM call. " + f"LLM: {llm}, Input length: {len(input)} chars. " + f"Check LLM configuration, API credentials, and model availability." + ) + raise ValueError( + "Invalid response from LLM call - None or empty. " + "Please check your LLM configuration and API credentials." + ) raw = mellea_output.content # If Mellea gave us a dict-like object: if isinstance(raw, dict): return targetAtype.model_validate(raw) - # If it’s a JSON string: + # If it's a JSON string: if isinstance(raw, str): return targetAtype.model_validate_json(raw)