lionagi 0.18.1__py3-none-any.whl → 0.18.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lionagi/__init__.py +102 -59
 - lionagi/adapters/spec_adapters/__init__.py +9 -0
 - lionagi/adapters/spec_adapters/_protocol.py +236 -0
 - lionagi/adapters/spec_adapters/pydantic_field.py +158 -0
 - lionagi/ln/_async_call.py +2 -2
 - lionagi/ln/fuzzy/_fuzzy_match.py +2 -2
 - lionagi/ln/types/__init__.py +51 -0
 - lionagi/ln/types/_sentinel.py +154 -0
 - lionagi/ln/{types.py → types/base.py} +108 -168
 - lionagi/ln/types/operable.py +221 -0
 - lionagi/ln/types/spec.py +441 -0
 - lionagi/models/field_model.py +57 -3
 - lionagi/models/model_params.py +4 -3
 - lionagi/operations/operate/operate.py +116 -84
 - lionagi/operations/operate/operative.py +142 -305
 - lionagi/operations/operate/step.py +162 -181
 - lionagi/operations/types.py +6 -6
 - lionagi/protocols/messages/message.py +2 -2
 - lionagi/version.py +1 -1
 - {lionagi-0.18.1.dist-info → lionagi-0.18.2.dist-info}/METADATA +1 -1
 - {lionagi-0.18.1.dist-info → lionagi-0.18.2.dist-info}/RECORD +23 -16
 - {lionagi-0.18.1.dist-info → lionagi-0.18.2.dist-info}/WHEEL +0 -0
 - {lionagi-0.18.1.dist-info → lionagi-0.18.2.dist-info}/licenses/LICENSE +0 -0
 
    
        lionagi/models/field_model.py
    CHANGED
    
    | 
         @@ -16,7 +16,7 @@ from typing import Annotated, Any, ClassVar 
     | 
|
| 
       16 
16 
     | 
    
         
             
            from typing_extensions import Self, override
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
            from .._errors import ValidationError
         
     | 
| 
       19 
     | 
    
         
            -
            from ..ln.types import Meta, Params
         
     | 
| 
      
 19 
     | 
    
         
            +
            from ..ln.types import Meta, ModelConfig, Params, Spec
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
            # Cache of valid Pydantic Field parameters
         
     | 
| 
       22 
22 
     | 
    
         
             
            _PYDANTIC_FIELD_PARAMS: set[str] | None = None
         
     | 
| 
         @@ -77,8 +77,9 @@ class FieldModel(Params): 
     | 
|
| 
       77 
77 
     | 
    
         
             
                """
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
79 
     | 
    
         
             
                # Class configuration - let Params handle Unset population
         
     | 
| 
       80 
     | 
    
         
            -
                 
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
      
 80 
     | 
    
         
            +
                _config: ClassVar[ModelConfig] = ModelConfig(
         
     | 
| 
      
 81 
     | 
    
         
            +
                    prefill_unset=True, none_as_sentinel=True
         
     | 
| 
      
 82 
     | 
    
         
            +
                )
         
     | 
| 
       82 
83 
     | 
    
         | 
| 
       83 
84 
     | 
    
         
             
                # Public fields (all start as Unset when not provided)
         
     | 
| 
       84 
85 
     | 
    
         
             
                base_type: type[Any]
         
     | 
| 
         @@ -782,6 +783,59 @@ class FieldModel(Params): 
     | 
|
| 
       782 
783 
     | 
    
         
             
                        ]
         
     | 
| 
       783 
784 
     | 
    
         
             
                    )
         
     | 
| 
       784 
785 
     | 
    
         | 
| 
      
 786 
     | 
    
         
            +
                def to_spec(self) -> "Spec":
         
     | 
| 
      
 787 
     | 
    
         
            +
                    """Convert FieldModel to Spec.
         
     | 
| 
      
 788 
     | 
    
         
            +
             
     | 
| 
      
 789 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 790 
     | 
    
         
            +
                        Spec object with equivalent configuration
         
     | 
| 
      
 791 
     | 
    
         
            +
                    """
         
     | 
| 
      
 792 
     | 
    
         
            +
                    from ..ln.types import Spec
         
     | 
| 
      
 793 
     | 
    
         
            +
             
     | 
| 
      
 794 
     | 
    
         
            +
                    # Build kwargs for Spec constructor
         
     | 
| 
      
 795 
     | 
    
         
            +
                    kwargs = {}
         
     | 
| 
      
 796 
     | 
    
         
            +
             
     | 
| 
      
 797 
     | 
    
         
            +
                    # Extract name from metadata
         
     | 
| 
      
 798 
     | 
    
         
            +
                    name = self.extract_metadata("name")
         
     | 
| 
      
 799 
     | 
    
         
            +
                    if name:
         
     | 
| 
      
 800 
     | 
    
         
            +
                        kwargs["name"] = name
         
     | 
| 
      
 801 
     | 
    
         
            +
             
     | 
| 
      
 802 
     | 
    
         
            +
                    # Add nullable/listable flags using properties
         
     | 
| 
      
 803 
     | 
    
         
            +
                    kwargs["nullable"] = self.is_nullable
         
     | 
| 
      
 804 
     | 
    
         
            +
                    kwargs["listable"] = self.is_listable
         
     | 
| 
      
 805 
     | 
    
         
            +
             
     | 
| 
      
 806 
     | 
    
         
            +
                    # Extract default/default_factory
         
     | 
| 
      
 807 
     | 
    
         
            +
                    default = self.extract_metadata("default")
         
     | 
| 
      
 808 
     | 
    
         
            +
                    if default is not None:
         
     | 
| 
      
 809 
     | 
    
         
            +
                        kwargs["default"] = default
         
     | 
| 
      
 810 
     | 
    
         
            +
             
     | 
| 
      
 811 
     | 
    
         
            +
                    default_factory = self.extract_metadata("default_factory")
         
     | 
| 
      
 812 
     | 
    
         
            +
                    if default_factory is not None:
         
     | 
| 
      
 813 
     | 
    
         
            +
                        kwargs["default_factory"] = default_factory
         
     | 
| 
      
 814 
     | 
    
         
            +
             
     | 
| 
      
 815 
     | 
    
         
            +
                    # Extract validator
         
     | 
| 
      
 816 
     | 
    
         
            +
                    validator = self.extract_metadata("validator")
         
     | 
| 
      
 817 
     | 
    
         
            +
                    if validator is not None:
         
     | 
| 
      
 818 
     | 
    
         
            +
                        kwargs["validator"] = validator
         
     | 
| 
      
 819 
     | 
    
         
            +
             
     | 
| 
      
 820 
     | 
    
         
            +
                    # Extract description
         
     | 
| 
      
 821 
     | 
    
         
            +
                    description = self.extract_metadata("description")
         
     | 
| 
      
 822 
     | 
    
         
            +
                    if description:
         
     | 
| 
      
 823 
     | 
    
         
            +
                        kwargs["description"] = description
         
     | 
| 
      
 824 
     | 
    
         
            +
             
     | 
| 
      
 825 
     | 
    
         
            +
                    # Extract other common metadata
         
     | 
| 
      
 826 
     | 
    
         
            +
                    for key in ["title", "alias", "frozen", "exclude"]:
         
     | 
| 
      
 827 
     | 
    
         
            +
                        val = self.extract_metadata(key)
         
     | 
| 
      
 828 
     | 
    
         
            +
                        if val is not None:
         
     | 
| 
      
 829 
     | 
    
         
            +
                            kwargs[key] = val
         
     | 
| 
      
 830 
     | 
    
         
            +
             
     | 
| 
      
 831 
     | 
    
         
            +
                    # Extract json_schema_extra
         
     | 
| 
      
 832 
     | 
    
         
            +
                    json_schema_extra = self.extract_metadata("json_schema_extra")
         
     | 
| 
      
 833 
     | 
    
         
            +
                    if json_schema_extra:
         
     | 
| 
      
 834 
     | 
    
         
            +
                        for k, v in json_schema_extra.items():
         
     | 
| 
      
 835 
     | 
    
         
            +
                            kwargs[k] = v
         
     | 
| 
      
 836 
     | 
    
         
            +
             
     | 
| 
      
 837 
     | 
    
         
            +
                    return Spec(self.base_type, **kwargs)
         
     | 
| 
      
 838 
     | 
    
         
            +
             
     | 
| 
       785 
839 
     | 
    
         
             
                def metadata_dict(
         
     | 
| 
       786 
840 
     | 
    
         
             
                    self, exclude: list[str] | None = None
         
     | 
| 
       787 
841 
     | 
    
         
             
                ) -> dict[str, Any]:
         
     | 
    
        lionagi/models/model_params.py
    CHANGED
    
    | 
         @@ -21,7 +21,7 @@ from typing import Any, ClassVar 
     | 
|
| 
       21 
21 
     | 
    
         
             
            from pydantic import BaseModel, create_model
         
     | 
| 
       22 
22 
     | 
    
         
             
            from pydantic.fields import FieldInfo
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
            from lionagi.ln.types import Params
         
     | 
| 
      
 24 
     | 
    
         
            +
            from lionagi.ln.types import ModelConfig, Params
         
     | 
| 
       25 
25 
     | 
    
         
             
            from lionagi.utils import copy
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            from .field_model import FieldModel
         
     | 
| 
         @@ -83,8 +83,9 @@ class ModelParams(Params): 
     | 
|
| 
       83 
83 
     | 
    
         
             
                """
         
     | 
| 
       84 
84 
     | 
    
         | 
| 
       85 
85 
     | 
    
         
             
                # Class configuration - let Params handle Unset population
         
     | 
| 
       86 
     | 
    
         
            -
                 
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
      
 86 
     | 
    
         
            +
                _config: ClassVar[ModelConfig] = ModelConfig(
         
     | 
| 
      
 87 
     | 
    
         
            +
                    prefill_unset=True, none_as_sentinel=True
         
     | 
| 
      
 88 
     | 
    
         
            +
                )
         
     | 
| 
       88 
89 
     | 
    
         | 
| 
       89 
90 
     | 
    
         
             
                # Public fields (all start as Unset when not provided)
         
     | 
| 
       90 
91 
     | 
    
         
             
                name: str | None
         
     | 
| 
         @@ -2,13 +2,14 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # SPDX-License-Identifier: Apache-2.0
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            import warnings
         
     | 
| 
       5 
     | 
    
         
            -
            from typing import TYPE_CHECKING, Literal
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import TYPE_CHECKING, Literal, Union
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            from pydantic import BaseModel, JsonValue
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
            from lionagi.ln import AlcallParams
         
     | 
| 
       10 
10 
     | 
    
         
             
            from lionagi.ln.fuzzy import FuzzyMatchKeysParams
         
     | 
| 
       11 
     | 
    
         
            -
            from lionagi. 
     | 
| 
      
 11 
     | 
    
         
            +
            from lionagi.ln.types import Spec
         
     | 
| 
      
 12 
     | 
    
         
            +
            from lionagi.models import FieldModel
         
     | 
| 
       12 
13 
     | 
    
         
             
            from lionagi.protocols.generic import Progression
         
     | 
| 
       13 
14 
     | 
    
         
             
            from lionagi.protocols.messages import Instruction, SenderRecipient
         
     | 
| 
       14 
15 
     | 
    
         | 
| 
         @@ -32,7 +33,7 @@ def prepare_operate_kw( 
     | 
|
| 
       32 
33 
     | 
    
         
             
                sender: SenderRecipient = None,
         
     | 
| 
       33 
34 
     | 
    
         
             
                recipient: SenderRecipient = None,
         
     | 
| 
       34 
35 
     | 
    
         
             
                progression: Progression = None,
         
     | 
| 
       35 
     | 
    
         
            -
                imodel: "iModel" = None,  # deprecated 
     | 
| 
      
 36 
     | 
    
         
            +
                imodel: "iModel" = None,  # deprecated
         
     | 
| 
       36 
37 
     | 
    
         
             
                chat_model: "iModel" = None,
         
     | 
| 
       37 
38 
     | 
    
         
             
                invoke_actions: bool = True,
         
     | 
| 
       38 
39 
     | 
    
         
             
                tool_schemas: list[dict] = None,
         
     | 
| 
         @@ -40,35 +41,32 @@ def prepare_operate_kw( 
     | 
|
| 
       40 
41 
     | 
    
         
             
                image_detail: Literal["low", "high", "auto"] = None,
         
     | 
| 
       41 
42 
     | 
    
         
             
                parse_model: "iModel" = None,
         
     | 
| 
       42 
43 
     | 
    
         
             
                skip_validation: bool = False,
         
     | 
| 
      
 44 
     | 
    
         
            +
                handle_validation: HandleValidation = "return_value",
         
     | 
| 
       43 
45 
     | 
    
         
             
                tools: "ToolRef" = None,
         
     | 
| 
       44 
46 
     | 
    
         
             
                operative: "Operative" = None,
         
     | 
| 
       45 
     | 
    
         
            -
                response_format: type[BaseModel] = None, 
     | 
| 
      
 47 
     | 
    
         
            +
                response_format: type[BaseModel] = None,
         
     | 
| 
       46 
48 
     | 
    
         
             
                actions: bool = False,
         
     | 
| 
       47 
49 
     | 
    
         
             
                reason: bool = False,
         
     | 
| 
       48 
50 
     | 
    
         
             
                call_params: AlcallParams = None,
         
     | 
| 
       49 
51 
     | 
    
         
             
                action_strategy: Literal["sequential", "concurrent"] = "concurrent",
         
     | 
| 
       50 
52 
     | 
    
         
             
                verbose_action: bool = False,
         
     | 
| 
       51 
     | 
    
         
            -
                field_models: list[FieldModel] = None,
         
     | 
| 
       52 
     | 
    
         
            -
                 
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
       54 
     | 
    
         
            -
                request_param_kwargs: dict = None,
         
     | 
| 
       55 
     | 
    
         
            -
                handle_validation: HandleValidation = "return_value",
         
     | 
| 
       56 
     | 
    
         
            -
                operative_model: type[BaseModel] = None,
         
     | 
| 
       57 
     | 
    
         
            -
                request_model: type[BaseModel] = None,
         
     | 
| 
      
 53 
     | 
    
         
            +
                field_models: list[FieldModel | Spec] = None,
         
     | 
| 
      
 54 
     | 
    
         
            +
                operative_model: type[BaseModel] = None,  # deprecated
         
     | 
| 
      
 55 
     | 
    
         
            +
                request_model: type[BaseModel] = None,  # deprecated
         
     | 
| 
       58 
56 
     | 
    
         
             
                include_token_usage_to_model: bool = False,
         
     | 
| 
       59 
57 
     | 
    
         
             
                clear_messages: bool = False,
         
     | 
| 
       60 
58 
     | 
    
         
             
                **kwargs,
         
     | 
| 
       61 
     | 
    
         
            -
            ) ->  
     | 
| 
      
 59 
     | 
    
         
            +
            ) -> dict:
         
     | 
| 
       62 
60 
     | 
    
         
             
                # Handle deprecated parameters
         
     | 
| 
       63 
61 
     | 
    
         
             
                if operative_model:
         
     | 
| 
       64 
62 
     | 
    
         
             
                    warnings.warn(
         
     | 
| 
       65 
     | 
    
         
            -
                        "Parameter 'operative_model' is deprecated. Use 'response_format' 
     | 
| 
      
 63 
     | 
    
         
            +
                        "Parameter 'operative_model' is deprecated. Use 'response_format'.",
         
     | 
| 
       66 
64 
     | 
    
         
             
                        DeprecationWarning,
         
     | 
| 
       67 
65 
     | 
    
         
             
                        stacklevel=2,
         
     | 
| 
       68 
66 
     | 
    
         
             
                    )
         
     | 
| 
       69 
67 
     | 
    
         
             
                if imodel:
         
     | 
| 
       70 
68 
     | 
    
         
             
                    warnings.warn(
         
     | 
| 
       71 
     | 
    
         
            -
                        "Parameter 'imodel' is deprecated. Use 'chat_model' 
     | 
| 
      
 69 
     | 
    
         
            +
                        "Parameter 'imodel' is deprecated. Use 'chat_model'.",
         
     | 
| 
       72 
70 
     | 
    
         
             
                        DeprecationWarning,
         
     | 
| 
       73 
71 
     | 
    
         
             
                        stacklevel=2,
         
     | 
| 
       74 
72 
     | 
    
         
             
                    )
         
     | 
| 
         @@ -79,26 +77,23 @@ def prepare_operate_kw( 
     | 
|
| 
       79 
77 
     | 
    
         
             
                    or (response_format and request_model)
         
     | 
| 
       80 
78 
     | 
    
         
             
                ):
         
     | 
| 
       81 
79 
     | 
    
         
             
                    raise ValueError(
         
     | 
| 
       82 
     | 
    
         
            -
                        "Cannot specify  
     | 
| 
       83 
     | 
    
         
            -
                        "as they are aliases of each other."
         
     | 
| 
      
 80 
     | 
    
         
            +
                        "Cannot specify multiple of: operative_model, response_format, request_model"
         
     | 
| 
       84 
81 
     | 
    
         
             
                    )
         
     | 
| 
       85 
82 
     | 
    
         | 
| 
       86 
83 
     | 
    
         
             
                response_format = response_format or operative_model or request_model
         
     | 
| 
       87 
84 
     | 
    
         
             
                chat_model = chat_model or imodel or branch.chat_model
         
     | 
| 
       88 
85 
     | 
    
         
             
                parse_model = parse_model or chat_model
         
     | 
| 
       89 
86 
     | 
    
         | 
| 
       90 
     | 
    
         
            -
                # Convert dict-based instructions 
     | 
| 
      
 87 
     | 
    
         
            +
                # Convert dict-based instructions
         
     | 
| 
       91 
88 
     | 
    
         
             
                if isinstance(instruct, dict):
         
     | 
| 
       92 
89 
     | 
    
         
             
                    instruct = Instruct(**instruct)
         
     | 
| 
       93 
90 
     | 
    
         | 
| 
       94 
     | 
    
         
            -
                # Or create a new Instruct if not provided
         
     | 
| 
       95 
91 
     | 
    
         
             
                instruct = instruct or Instruct(
         
     | 
| 
       96 
92 
     | 
    
         
             
                    instruction=instruction,
         
     | 
| 
       97 
93 
     | 
    
         
             
                    guidance=guidance,
         
     | 
| 
       98 
94 
     | 
    
         
             
                    context=context,
         
     | 
| 
       99 
95 
     | 
    
         
             
                )
         
     | 
| 
       100 
96 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
                # If reason or actions are requested, apply them to instruct
         
     | 
| 
       102 
97 
     | 
    
         
             
                if reason:
         
     | 
| 
       103 
98 
     | 
    
         
             
                    instruct.reason = True
         
     | 
| 
       104 
99 
     | 
    
         
             
                if actions:
         
     | 
| 
         @@ -106,21 +101,40 @@ def prepare_operate_kw( 
     | 
|
| 
       106 
101 
     | 
    
         
             
                    if action_strategy:
         
     | 
| 
       107 
102 
     | 
    
         
             
                        instruct.action_strategy = action_strategy
         
     | 
| 
       108 
103 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
                #  
     | 
| 
       110 
     | 
    
         
            -
                 
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
                     
     | 
| 
       114 
     | 
    
         
            -
             
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
      
 104 
     | 
    
         
            +
                # Convert field_models to Spec if needed
         
     | 
| 
      
 105 
     | 
    
         
            +
                fields_dict = None
         
     | 
| 
      
 106 
     | 
    
         
            +
                if field_models:
         
     | 
| 
      
 107 
     | 
    
         
            +
                    fields_dict = {}
         
     | 
| 
      
 108 
     | 
    
         
            +
                    for fm in field_models:
         
     | 
| 
      
 109 
     | 
    
         
            +
                        # Convert FieldModel to Spec
         
     | 
| 
      
 110 
     | 
    
         
            +
                        if isinstance(fm, FieldModel):
         
     | 
| 
      
 111 
     | 
    
         
            +
                            spec = fm.to_spec()
         
     | 
| 
      
 112 
     | 
    
         
            +
                        elif isinstance(fm, Spec):
         
     | 
| 
      
 113 
     | 
    
         
            +
                            spec = fm
         
     | 
| 
      
 114 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 115 
     | 
    
         
            +
                            raise TypeError(f"Expected FieldModel or Spec, got {type(fm)}")
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                        if spec.name:
         
     | 
| 
      
 118 
     | 
    
         
            +
                            fields_dict[spec.name] = spec
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                # Build Operative if needed
         
     | 
| 
      
 121 
     | 
    
         
            +
                operative = None
         
     | 
| 
      
 122 
     | 
    
         
            +
                if instruct.reason or instruct.actions or response_format or fields_dict:
         
     | 
| 
      
 123 
     | 
    
         
            +
                    from .step import Step
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    operative = Step.request_operative(
         
     | 
| 
      
 126 
     | 
    
         
            +
                        base_type=response_format,
         
     | 
| 
      
 127 
     | 
    
         
            +
                        reason=instruct.reason,
         
     | 
| 
      
 128 
     | 
    
         
            +
                        actions=instruct.actions or actions,
         
     | 
| 
      
 129 
     | 
    
         
            +
                        fields=fields_dict,
         
     | 
| 
      
 130 
     | 
    
         
            +
                    )
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                    # Create response model
         
     | 
| 
      
 133 
     | 
    
         
            +
                    operative = Step.respond_operative(operative)
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                final_response_format = (
         
     | 
| 
      
 136 
     | 
    
         
            +
                    operative.response_type if operative else response_format
         
     | 
| 
       120 
137 
     | 
    
         
             
                )
         
     | 
| 
       121 
     | 
    
         
            -
                # Use the operative's request_type which is a proper Pydantic model
         
     | 
| 
       122 
     | 
    
         
            -
                # created from field_models if provided
         
     | 
| 
       123 
     | 
    
         
            -
                final_response_format = operative.request_type
         
     | 
| 
       124 
138 
     | 
    
         | 
| 
       125 
139 
     | 
    
         
             
                # Build contexts
         
     | 
| 
       126 
140 
     | 
    
         
             
                chat_param = ChatParam(
         
     | 
| 
         @@ -146,7 +160,7 @@ def prepare_operate_kw( 
     | 
|
| 
       146 
160 
     | 
    
         
             
                    parse_param = ParseParam(
         
     | 
| 
       147 
161 
     | 
    
         
             
                        response_format=final_response_format,
         
     | 
| 
       148 
162 
     | 
    
         
             
                        fuzzy_match_params=FuzzyMatchKeysParams(),
         
     | 
| 
       149 
     | 
    
         
            -
                        handle_validation= 
     | 
| 
      
 163 
     | 
    
         
            +
                        handle_validation=handle_validation,
         
     | 
| 
       150 
164 
     | 
    
         
             
                        alcall_params=get_default_call(),
         
     | 
| 
       151 
165 
     | 
    
         
             
                        imodel=parse_model,
         
     | 
| 
       152 
166 
     | 
    
         
             
                        imodel_kw={},
         
     | 
| 
         @@ -175,25 +189,43 @@ def prepare_operate_kw( 
     | 
|
| 
       175 
189 
     | 
    
         
             
                    "invoke_actions": invoke_actions,
         
     | 
| 
       176 
190 
     | 
    
         
             
                    "skip_validation": skip_validation,
         
     | 
| 
       177 
191 
     | 
    
         
             
                    "clear_messages": clear_messages,
         
     | 
| 
      
 192 
     | 
    
         
            +
                    "operative": operative,
         
     | 
| 
       178 
193 
     | 
    
         
             
                }
         
     | 
| 
       179 
194 
     | 
    
         | 
| 
       180 
195 
     | 
    
         | 
| 
       181 
196 
     | 
    
         
             
            async def operate(
         
     | 
| 
       182 
197 
     | 
    
         
             
                branch: "Branch",
         
     | 
| 
       183 
     | 
    
         
            -
                instruction: JsonValue  
     | 
| 
      
 198 
     | 
    
         
            +
                instruction: Union[JsonValue, Instruction],
         
     | 
| 
       184 
199 
     | 
    
         
             
                chat_param: ChatParam,
         
     | 
| 
       185 
     | 
    
         
            -
                action_param: ActionParam  
     | 
| 
       186 
     | 
    
         
            -
                parse_param: ParseParam  
     | 
| 
      
 200 
     | 
    
         
            +
                action_param: Union[ActionParam, None] = None,
         
     | 
| 
      
 201 
     | 
    
         
            +
                parse_param: Union[ParseParam, None] = None,
         
     | 
| 
       187 
202 
     | 
    
         
             
                handle_validation: HandleValidation = "return_value",
         
     | 
| 
       188 
203 
     | 
    
         
             
                invoke_actions: bool = True,
         
     | 
| 
       189 
204 
     | 
    
         
             
                skip_validation: bool = False,
         
     | 
| 
       190 
205 
     | 
    
         
             
                clear_messages: bool = False,
         
     | 
| 
       191 
206 
     | 
    
         
             
                reason: bool = False,
         
     | 
| 
       192 
     | 
    
         
            -
                field_models: list[FieldModel]  
     | 
| 
       193 
     | 
    
         
            -
             
     | 
| 
       194 
     | 
    
         
            -
             
     | 
| 
       195 
     | 
    
         
            -
                 
     | 
| 
       196 
     | 
    
         
            -
             
     | 
| 
      
 207 
     | 
    
         
            +
                field_models: Union[list[Union[FieldModel, Spec]], None] = None,
         
     | 
| 
      
 208 
     | 
    
         
            +
                operative: Union["Operative", None] = None,
         
     | 
| 
      
 209 
     | 
    
         
            +
            ) -> Union[BaseModel, dict, str, None]:
         
     | 
| 
      
 210 
     | 
    
         
            +
                """Execute operation with optional action handling.
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 213 
     | 
    
         
            +
                    branch: Branch instance
         
     | 
| 
      
 214 
     | 
    
         
            +
                    instruction: Instruction or JSON value
         
     | 
| 
      
 215 
     | 
    
         
            +
                    chat_param: Chat parameters
         
     | 
| 
      
 216 
     | 
    
         
            +
                    action_param: Action parameters
         
     | 
| 
      
 217 
     | 
    
         
            +
                    parse_param: Parse parameters
         
     | 
| 
      
 218 
     | 
    
         
            +
                    handle_validation: Validation handling strategy
         
     | 
| 
      
 219 
     | 
    
         
            +
                    invoke_actions: Whether to invoke actions
         
     | 
| 
      
 220 
     | 
    
         
            +
                    skip_validation: Whether to skip validation
         
     | 
| 
      
 221 
     | 
    
         
            +
                    clear_messages: Whether to clear messages
         
     | 
| 
      
 222 
     | 
    
         
            +
                    reason: Whether to include reasoning
         
     | 
| 
      
 223 
     | 
    
         
            +
                    field_models: List of FieldModel or Spec objects
         
     | 
| 
      
 224 
     | 
    
         
            +
                    operative: Operative instance
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 227 
     | 
    
         
            +
                    Result of operation
         
     | 
| 
      
 228 
     | 
    
         
            +
                """
         
     | 
| 
       197 
229 
     | 
    
         
             
                _cctx = chat_param
         
     | 
| 
       198 
230 
     | 
    
         
             
                _pctx = (
         
     | 
| 
       199 
231 
     | 
    
         
             
                    parse_param.with_updates(handle_validation="return_value")
         
     | 
| 
         @@ -205,12 +237,12 @@ async def operate( 
     | 
|
| 
       205 
237 
     | 
    
         
             
                    )
         
     | 
| 
       206 
238 
     | 
    
         
             
                )
         
     | 
| 
       207 
239 
     | 
    
         | 
| 
       208 
     | 
    
         
            -
                # Update tool schemas 
     | 
| 
      
 240 
     | 
    
         
            +
                # Update tool schemas
         
     | 
| 
       209 
241 
     | 
    
         
             
                if tools := (action_param.tools or True) if action_param else None:
         
     | 
| 
       210 
242 
     | 
    
         
             
                    tool_schemas = branch.acts.get_tool_schema(tools=tools)
         
     | 
| 
       211 
243 
     | 
    
         
             
                    _cctx = _cctx.with_updates(tool_schemas=tool_schemas)
         
     | 
| 
       212 
244 
     | 
    
         | 
| 
       213 
     | 
    
         
            -
                # Extract model class 
     | 
| 
      
 245 
     | 
    
         
            +
                # Extract model class
         
     | 
| 
       214 
246 
     | 
    
         
             
                model_class = None
         
     | 
| 
       215 
247 
     | 
    
         
             
                if chat_param.response_format is not None:
         
     | 
| 
       216 
248 
     | 
    
         
             
                    if isinstance(chat_param.response_format, type) and issubclass(
         
     | 
| 
         @@ -220,36 +252,38 @@ async def operate( 
     | 
|
| 
       220 
252 
     | 
    
         
             
                    elif isinstance(chat_param.response_format, BaseModel):
         
     | 
| 
       221 
253 
     | 
    
         
             
                        model_class = type(chat_param.response_format)
         
     | 
| 
       222 
254 
     | 
    
         | 
| 
       223 
     | 
    
         
            -
                 
     | 
| 
       224 
     | 
    
         
            -
             
     | 
| 
       225 
     | 
    
         
            -
             
     | 
| 
       226 
     | 
    
         
            -
                     
     | 
| 
       227 
     | 
    
         
            -
             
     | 
| 
       228 
     | 
    
         
            -
             
     | 
| 
       229 
     | 
    
         
            -
             
     | 
| 
       230 
     | 
    
         
            -
             
     | 
| 
       231 
     | 
    
         
            -
             
     | 
| 
       232 
     | 
    
         
            -
             
     | 
| 
       233 
     | 
    
         
            -
             
     | 
| 
      
 255 
     | 
    
         
            +
                # Convert field_models to fields dict
         
     | 
| 
      
 256 
     | 
    
         
            +
                fields_dict = None
         
     | 
| 
      
 257 
     | 
    
         
            +
                if field_models:
         
     | 
| 
      
 258 
     | 
    
         
            +
                    fields_dict = {}
         
     | 
| 
      
 259 
     | 
    
         
            +
                    for fm in field_models:
         
     | 
| 
      
 260 
     | 
    
         
            +
                        if isinstance(fm, FieldModel):
         
     | 
| 
      
 261 
     | 
    
         
            +
                            spec = fm.to_spec()
         
     | 
| 
      
 262 
     | 
    
         
            +
                        elif isinstance(fm, Spec):
         
     | 
| 
      
 263 
     | 
    
         
            +
                            spec = fm
         
     | 
| 
      
 264 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 265 
     | 
    
         
            +
                            raise TypeError(f"Expected FieldModel or Spec, got {type(fm)}")
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                        if spec.name:
         
     | 
| 
      
 268 
     | 
    
         
            +
                            fields_dict[spec.name] = spec
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                # Create operative if needed
         
     | 
| 
      
 271 
     | 
    
         
            +
                if not operative and (model_class or action_param or fields_dict):
         
     | 
| 
       234 
272 
     | 
    
         
             
                    from .step import Step
         
     | 
| 
       235 
273 
     | 
    
         | 
| 
       236 
274 
     | 
    
         
             
                    operative = Step.request_operative(
         
     | 
| 
       237 
     | 
    
         
            -
                        reason=reason,
         
     | 
| 
       238 
     | 
    
         
            -
                        actions=bool(action_param is not None),
         
     | 
| 
       239 
275 
     | 
    
         
             
                        base_type=model_class,
         
     | 
| 
       240 
     | 
    
         
            -
                         
     | 
| 
      
 276 
     | 
    
         
            +
                        reason=reason,
         
     | 
| 
      
 277 
     | 
    
         
            +
                        actions=bool(action_param),
         
     | 
| 
      
 278 
     | 
    
         
            +
                        fields=fields_dict,
         
     | 
| 
       241 
279 
     | 
    
         
             
                    )
         
     | 
| 
       242 
     | 
    
         
            -
                     
     | 
| 
       243 
     | 
    
         
            -
             
     | 
| 
       244 
     | 
    
         
            -
                     
     | 
| 
       245 
     | 
    
         
            -
             
     | 
| 
       246 
     | 
    
         
            -
                     
     | 
| 
       247 
     | 
    
         
            -
             
     | 
| 
       248 
     | 
    
         
            -
                         
     | 
| 
       249 
     | 
    
         
            -
                            dict_[fm.name] = str(fm.annotated())
         
     | 
| 
       250 
     | 
    
         
            -
                    # Update contexts with dict format
         
     | 
| 
       251 
     | 
    
         
            -
                    _cctx = _cctx.with_updates(response_format=dict_)
         
     | 
| 
       252 
     | 
    
         
            -
                    _pctx = _pctx.with_updates(response_format=dict_)
         
     | 
| 
      
 280 
     | 
    
         
            +
                    operative = Step.respond_operative(operative)
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
                    # Update contexts
         
     | 
| 
      
 283 
     | 
    
         
            +
                    response_fmt = operative.response_type or model_class
         
     | 
| 
      
 284 
     | 
    
         
            +
                    if response_fmt:
         
     | 
| 
      
 285 
     | 
    
         
            +
                        _cctx = _cctx.with_updates(response_format=response_fmt)
         
     | 
| 
      
 286 
     | 
    
         
            +
                        _pctx = _pctx.with_updates(response_format=response_fmt)
         
     | 
| 
       253 
287 
     | 
    
         | 
| 
       254 
288 
     | 
    
         
             
                from ..communicate.communicate import communicate
         
     | 
| 
       255 
289 
     | 
    
         | 
| 
         @@ -262,8 +296,10 @@ async def operate( 
     | 
|
| 
       262 
296 
     | 
    
         
             
                    skip_validation=skip_validation,
         
     | 
| 
       263 
297 
     | 
    
         
             
                    request_fields=None,
         
     | 
| 
       264 
298 
     | 
    
         
             
                )
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
       265 
300 
     | 
    
         
             
                if skip_validation:
         
     | 
| 
       266 
301 
     | 
    
         
             
                    return result
         
     | 
| 
      
 302 
     | 
    
         
            +
             
     | 
| 
       267 
303 
     | 
    
         
             
                if model_class and not isinstance(result, model_class):
         
     | 
| 
       268 
304 
     | 
    
         
             
                    match handle_validation:
         
     | 
| 
       269 
305 
     | 
    
         
             
                        case "return_value":
         
     | 
| 
         @@ -271,12 +307,12 @@ async def operate( 
     | 
|
| 
       271 
307 
     | 
    
         
             
                        case "return_none":
         
     | 
| 
       272 
308 
     | 
    
         
             
                            return None
         
     | 
| 
       273 
309 
     | 
    
         
             
                        case "raise":
         
     | 
| 
       274 
     | 
    
         
            -
                            raise ValueError(
         
     | 
| 
       275 
     | 
    
         
            -
             
     | 
| 
       276 
     | 
    
         
            -
                            )
         
     | 
| 
      
 310 
     | 
    
         
            +
                            raise ValueError("Failed to parse LLM response.")
         
     | 
| 
      
 311 
     | 
    
         
            +
             
     | 
| 
       277 
312 
     | 
    
         
             
                if not invoke_actions:
         
     | 
| 
       278 
313 
     | 
    
         
             
                    return result
         
     | 
| 
       279 
314 
     | 
    
         | 
| 
      
 315 
     | 
    
         
            +
                # Handle actions
         
     | 
| 
       280 
316 
     | 
    
         
             
                requests = (
         
     | 
| 
       281 
317 
     | 
    
         
             
                    getattr(result, "action_requests", None)
         
     | 
| 
       282 
318 
     | 
    
         
             
                    if model_class
         
     | 
| 
         @@ -287,32 +323,28 @@ async def operate( 
     | 
|
| 
       287 
323 
     | 
    
         
             
                if action_param and requests is not None:
         
     | 
| 
       288 
324 
     | 
    
         
             
                    from ..act.act import act
         
     | 
| 
       289 
325 
     | 
    
         | 
| 
       290 
     | 
    
         
            -
                    action_response_models = await act(
         
     | 
| 
       291 
     | 
    
         
            -
                        branch,
         
     | 
| 
       292 
     | 
    
         
            -
                        requests,
         
     | 
| 
       293 
     | 
    
         
            -
                        action_param,
         
     | 
| 
       294 
     | 
    
         
            -
                    )
         
     | 
| 
      
 326 
     | 
    
         
            +
                    action_response_models = await act(branch, requests, action_param)
         
     | 
| 
       295 
327 
     | 
    
         | 
| 
       296 
328 
     | 
    
         
             
                if not action_response_models:
         
     | 
| 
       297 
329 
     | 
    
         
             
                    return result
         
     | 
| 
       298 
330 
     | 
    
         | 
| 
       299 
     | 
    
         
            -
                # Filter  
     | 
| 
      
 331 
     | 
    
         
            +
                # Filter None values
         
     | 
| 
       300 
332 
     | 
    
         
             
                action_response_models = [
         
     | 
| 
       301 
333 
     | 
    
         
             
                    r for r in action_response_models if r is not None
         
     | 
| 
       302 
334 
     | 
    
         
             
                ]
         
     | 
| 
       303 
335 
     | 
    
         | 
| 
       304 
     | 
    
         
            -
                if not action_response_models: 
     | 
| 
      
 336 
     | 
    
         
            +
                if not action_response_models:
         
     | 
| 
       305 
337 
     | 
    
         
             
                    return result
         
     | 
| 
       306 
338 
     | 
    
         | 
| 
       307 
339 
     | 
    
         
             
                if not model_class:  # Dict response
         
     | 
| 
       308 
340 
     | 
    
         
             
                    result.update({"action_responses": action_response_models})
         
     | 
| 
       309 
341 
     | 
    
         
             
                    return result
         
     | 
| 
       310 
342 
     | 
    
         | 
| 
       311 
     | 
    
         
            -
                 
     | 
| 
       312 
     | 
    
         
            -
             
     | 
| 
      
 343 
     | 
    
         
            +
                # If we have model_class, we must have operative (created at line 268)
         
     | 
| 
      
 344 
     | 
    
         
            +
                # First set the response_model to the existing result
         
     | 
| 
       313 
345 
     | 
    
         
             
                operative.response_model = result
         
     | 
| 
       314 
     | 
    
         
            -
                 
     | 
| 
       315 
     | 
    
         
            -
             
     | 
| 
       316 
     | 
    
         
            -
                     
     | 
| 
      
 346 
     | 
    
         
            +
                # Then update it with action_responses
         
     | 
| 
      
 347 
     | 
    
         
            +
                operative.update_response_model(
         
     | 
| 
      
 348 
     | 
    
         
            +
                    data={"action_responses": action_response_models}
         
     | 
| 
       317 
349 
     | 
    
         
             
                )
         
     | 
| 
       318 
350 
     | 
    
         
             
                return operative.response_model
         
     |