lionagi 0.18.0__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/_errors.py +0 -5
 - 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/fields.py +83 -0
 - lionagi/ln/__init__.py +3 -1
 - lionagi/ln/_async_call.py +2 -2
 - lionagi/ln/concurrency/primitives.py +4 -4
 - lionagi/ln/concurrency/task.py +1 -0
 - 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 +69 -7
 - lionagi/models/hashable_model.py +2 -3
 - lionagi/models/model_params.py +4 -3
 - lionagi/operations/ReAct/ReAct.py +1 -1
 - lionagi/operations/act/act.py +3 -3
 - lionagi/operations/builder.py +5 -7
 - lionagi/operations/fields.py +380 -0
 - lionagi/operations/flow.py +4 -6
 - lionagi/operations/node.py +4 -4
 - lionagi/operations/operate/operate.py +123 -89
 - lionagi/operations/operate/operative.py +198 -0
 - lionagi/operations/operate/step.py +203 -0
 - lionagi/operations/select/select.py +1 -1
 - lionagi/operations/select/utils.py +7 -1
 - lionagi/operations/types.py +7 -7
 - lionagi/protocols/action/manager.py +5 -6
 - lionagi/protocols/contracts.py +2 -2
 - lionagi/protocols/generic/__init__.py +22 -0
 - lionagi/protocols/generic/element.py +36 -127
 - lionagi/protocols/generic/pile.py +9 -10
 - lionagi/protocols/generic/progression.py +23 -22
 - lionagi/protocols/graph/edge.py +6 -5
 - lionagi/protocols/ids.py +6 -49
 - lionagi/protocols/messages/__init__.py +3 -1
 - lionagi/protocols/messages/base.py +7 -6
 - lionagi/protocols/messages/instruction.py +0 -1
 - lionagi/protocols/messages/message.py +2 -2
 - lionagi/protocols/types.py +1 -11
 - lionagi/service/connections/__init__.py +3 -0
 - lionagi/service/connections/providers/claude_code_cli.py +3 -2
 - lionagi/service/hooks/_types.py +1 -1
 - lionagi/service/hooks/_utils.py +1 -1
 - lionagi/service/hooks/hook_event.py +3 -8
 - lionagi/service/hooks/hook_registry.py +5 -5
 - lionagi/service/hooks/hooked_event.py +61 -1
 - lionagi/service/imodel.py +24 -20
 - lionagi/service/third_party/claude_code.py +1 -2
 - lionagi/service/third_party/openai_models.py +24 -22
 - lionagi/service/token_calculator.py +1 -94
 - lionagi/session/branch.py +26 -228
 - lionagi/session/session.py +5 -90
 - lionagi/version.py +1 -1
 - {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/METADATA +6 -5
 - {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/RECORD +62 -82
 - lionagi/fields/__init__.py +0 -47
 - lionagi/fields/action.py +0 -188
 - lionagi/fields/base.py +0 -153
 - lionagi/fields/code.py +0 -239
 - lionagi/fields/file.py +0 -234
 - lionagi/fields/instruct.py +0 -135
 - lionagi/fields/reason.py +0 -55
 - lionagi/fields/research.py +0 -52
 - lionagi/operations/brainstorm/__init__.py +0 -2
 - lionagi/operations/brainstorm/brainstorm.py +0 -498
 - lionagi/operations/brainstorm/prompt.py +0 -11
 - lionagi/operations/instruct/__init__.py +0 -2
 - lionagi/operations/instruct/instruct.py +0 -28
 - lionagi/operations/plan/__init__.py +0 -6
 - lionagi/operations/plan/plan.py +0 -386
 - lionagi/operations/plan/prompt.py +0 -25
 - lionagi/operations/utils.py +0 -45
 - lionagi/protocols/forms/__init__.py +0 -2
 - lionagi/protocols/forms/base.py +0 -85
 - lionagi/protocols/forms/flow.py +0 -79
 - lionagi/protocols/forms/form.py +0 -86
 - lionagi/protocols/forms/report.py +0 -48
 - lionagi/protocols/mail/__init__.py +0 -2
 - lionagi/protocols/mail/exchange.py +0 -220
 - lionagi/protocols/mail/mail.py +0 -51
 - lionagi/protocols/mail/mailbox.py +0 -103
 - lionagi/protocols/mail/manager.py +0 -218
 - lionagi/protocols/mail/package.py +0 -101
 - lionagi/protocols/operatives/__init__.py +0 -2
 - lionagi/protocols/operatives/operative.py +0 -362
 - lionagi/protocols/operatives/step.py +0 -227
 - {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/WHEEL +0 -0
 - {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/licenses/LICENSE +0 -0
 
    
        lionagi/ln/types/spec.py
    ADDED
    
    | 
         @@ -0,0 +1,441 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Spec - Universal type specification for framework-agnostic field definitions.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            This module provides the Spec class for defining field specifications that can be
         
     | 
| 
      
 4 
     | 
    
         
            +
            adapted to any framework (Pydantic, attrs, dataclasses, etc.) via adapters.
         
     | 
| 
      
 5 
     | 
    
         
            +
            """
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            from __future__ import annotations
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            import contextlib
         
     | 
| 
      
 10 
     | 
    
         
            +
            import inspect
         
     | 
| 
      
 11 
     | 
    
         
            +
            import os
         
     | 
| 
      
 12 
     | 
    
         
            +
            import threading
         
     | 
| 
      
 13 
     | 
    
         
            +
            from dataclasses import dataclass
         
     | 
| 
      
 14 
     | 
    
         
            +
            from enum import Enum
         
     | 
| 
      
 15 
     | 
    
         
            +
            from typing import Any, Callable
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            from typing_extensions import Annotated, OrderedDict
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            from lionagi.ln.concurrency.utils import is_coro_func
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            from ._sentinel import MaybeUndefined, Undefined, is_sentinel, not_sentinel
         
     | 
| 
      
 22 
     | 
    
         
            +
            from .base import Meta
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            # Global cache for annotated types with bounded size
         
     | 
| 
      
 25 
     | 
    
         
            +
            _MAX_CACHE_SIZE = int(os.environ.get("LIONAGI_FIELD_CACHE_SIZE", "10000"))
         
     | 
| 
      
 26 
     | 
    
         
            +
            _annotated_cache: OrderedDict[tuple[type, tuple[Meta, ...]], type] = (
         
     | 
| 
      
 27 
     | 
    
         
            +
                OrderedDict()
         
     | 
| 
      
 28 
     | 
    
         
            +
            )
         
     | 
| 
      
 29 
     | 
    
         
            +
            _cache_lock = threading.RLock()  # Thread-safe access to cache
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            __all__ = ("Spec", "CommonMeta")
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            class CommonMeta(Enum):
         
     | 
| 
      
 36 
     | 
    
         
            +
                """Common metadata keys used across field specifications."""
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                NAME = "name"
         
     | 
| 
      
 39 
     | 
    
         
            +
                NULLABLE = "nullable"
         
     | 
| 
      
 40 
     | 
    
         
            +
                LISTABLE = "listable"
         
     | 
| 
      
 41 
     | 
    
         
            +
                VALIDATOR = "validator"
         
     | 
| 
      
 42 
     | 
    
         
            +
                DEFAULT = "default"
         
     | 
| 
      
 43 
     | 
    
         
            +
                DEFAULT_FACTORY = "default_factory"
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 46 
     | 
    
         
            +
                def allowed(cls) -> set[str]:
         
     | 
| 
      
 47 
     | 
    
         
            +
                    """Return all allowed common metadata keys."""
         
     | 
| 
      
 48 
     | 
    
         
            +
                    return {i.value for i in cls}
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 51 
     | 
    
         
            +
                def _validate_common_metas(cls, **kw):
         
     | 
| 
      
 52 
     | 
    
         
            +
                    """Validate common metadata constraints."""
         
     | 
| 
      
 53 
     | 
    
         
            +
                    if kw.get("default") and kw.get("default_factory"):
         
     | 
| 
      
 54 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 55 
     | 
    
         
            +
                            "Cannot provide both 'default' and 'default_factory'"
         
     | 
| 
      
 56 
     | 
    
         
            +
                        )
         
     | 
| 
      
 57 
     | 
    
         
            +
                    if _df := kw.get("default_factory"):
         
     | 
| 
      
 58 
     | 
    
         
            +
                        if not callable(_df):
         
     | 
| 
      
 59 
     | 
    
         
            +
                            raise ValueError("'default_factory' must be callable")
         
     | 
| 
      
 60 
     | 
    
         
            +
                    if _val := kw.get("validator"):
         
     | 
| 
      
 61 
     | 
    
         
            +
                        _val = [_val] if not isinstance(_val, list) else _val
         
     | 
| 
      
 62 
     | 
    
         
            +
                        if not all(callable(v) for v in _val):
         
     | 
| 
      
 63 
     | 
    
         
            +
                            raise ValueError(
         
     | 
| 
      
 64 
     | 
    
         
            +
                                "Validators must be a list of functions or a function"
         
     | 
| 
      
 65 
     | 
    
         
            +
                            )
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 68 
     | 
    
         
            +
                def prepare(
         
     | 
| 
      
 69 
     | 
    
         
            +
                    cls, *args: Meta, metadata: tuple[Meta, ...] = None, **kw: Any
         
     | 
| 
      
 70 
     | 
    
         
            +
                ) -> tuple[Meta, ...]:
         
     | 
| 
      
 71 
     | 
    
         
            +
                    """Prepare metadata tuple from various inputs, checking for duplicates.
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 74 
     | 
    
         
            +
                        *args: Individual Meta objects
         
     | 
| 
      
 75 
     | 
    
         
            +
                        metadata: Existing metadata tuple
         
     | 
| 
      
 76 
     | 
    
         
            +
                        **kw: Keyword arguments to convert to Meta objects
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 79 
     | 
    
         
            +
                        Tuple of Meta objects
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    Raises:
         
     | 
| 
      
 82 
     | 
    
         
            +
                        ValueError: If duplicate keys are found
         
     | 
| 
      
 83 
     | 
    
         
            +
                    """
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # Lazy import to avoid circular dependency
         
     | 
| 
      
 85 
     | 
    
         
            +
                    from .._to_list import to_list
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    seen_keys = set()
         
     | 
| 
      
 88 
     | 
    
         
            +
                    metas = []
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    # Process existing metadata
         
     | 
| 
      
 91 
     | 
    
         
            +
                    if metadata:
         
     | 
| 
      
 92 
     | 
    
         
            +
                        for meta in metadata:
         
     | 
| 
      
 93 
     | 
    
         
            +
                            if meta.key in seen_keys:
         
     | 
| 
      
 94 
     | 
    
         
            +
                                raise ValueError(f"Duplicate metadata key: {meta.key}")
         
     | 
| 
      
 95 
     | 
    
         
            +
                            seen_keys.add(meta.key)
         
     | 
| 
      
 96 
     | 
    
         
            +
                            metas.append(meta)
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    # Process args
         
     | 
| 
      
 99 
     | 
    
         
            +
                    if args:
         
     | 
| 
      
 100 
     | 
    
         
            +
                        _args = to_list(
         
     | 
| 
      
 101 
     | 
    
         
            +
                            args, flatten=True, flatten_tuple_set=True, dropna=True
         
     | 
| 
      
 102 
     | 
    
         
            +
                        )
         
     | 
| 
      
 103 
     | 
    
         
            +
                        for meta in _args:
         
     | 
| 
      
 104 
     | 
    
         
            +
                            if meta.key in seen_keys:
         
     | 
| 
      
 105 
     | 
    
         
            +
                                raise ValueError(f"Duplicate metadata key: {meta.key}")
         
     | 
| 
      
 106 
     | 
    
         
            +
                            seen_keys.add(meta.key)
         
     | 
| 
      
 107 
     | 
    
         
            +
                            metas.append(meta)
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    # Process kwargs
         
     | 
| 
      
 110 
     | 
    
         
            +
                    for k, v in kw.items():
         
     | 
| 
      
 111 
     | 
    
         
            +
                        if k in seen_keys:
         
     | 
| 
      
 112 
     | 
    
         
            +
                            raise ValueError(f"Duplicate metadata key: {k}")
         
     | 
| 
      
 113 
     | 
    
         
            +
                        seen_keys.add(k)
         
     | 
| 
      
 114 
     | 
    
         
            +
                        metas.append(Meta(k, v))
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                    # Validate common metadata constraints
         
     | 
| 
      
 117 
     | 
    
         
            +
                    meta_dict = {m.key: m.value for m in metas}
         
     | 
| 
      
 118 
     | 
    
         
            +
                    cls._validate_common_metas(**meta_dict)
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    return tuple(metas)
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            @dataclass(frozen=True, slots=True, init=False)
         
     | 
| 
      
 124 
     | 
    
         
            +
            class Spec:
         
     | 
| 
      
 125 
     | 
    
         
            +
                """Framework-agnostic field specification.
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                A Spec defines the type and metadata for a field without coupling to any
         
     | 
| 
      
 128 
     | 
    
         
            +
                specific framework. Use adapters to convert Spec to framework-specific
         
     | 
| 
      
 129 
     | 
    
         
            +
                field definitions (e.g., Pydantic Field, attrs attribute).
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                Attributes:
         
     | 
| 
      
 132 
     | 
    
         
            +
                    base_type: The base Python type for this field
         
     | 
| 
      
 133 
     | 
    
         
            +
                    metadata: Tuple of metadata objects attached to this spec
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                Example:
         
     | 
| 
      
 136 
     | 
    
         
            +
                    >>> spec = Spec(str, name="username", nullable=False)
         
     | 
| 
      
 137 
     | 
    
         
            +
                    >>> spec.name
         
     | 
| 
      
 138 
     | 
    
         
            +
                    'username'
         
     | 
| 
      
 139 
     | 
    
         
            +
                    >>> spec.annotation
         
     | 
| 
      
 140 
     | 
    
         
            +
                    str
         
     | 
| 
      
 141 
     | 
    
         
            +
                """
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                base_type: type
         
     | 
| 
      
 144 
     | 
    
         
            +
                metadata: tuple[Meta, ...]
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                def __init__(
         
     | 
| 
      
 147 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 148 
     | 
    
         
            +
                    base_type: type = None,
         
     | 
| 
      
 149 
     | 
    
         
            +
                    *args,
         
     | 
| 
      
 150 
     | 
    
         
            +
                    metadata: tuple[Meta, ...] = None,
         
     | 
| 
      
 151 
     | 
    
         
            +
                    **kw,
         
     | 
| 
      
 152 
     | 
    
         
            +
                ) -> None:
         
     | 
| 
      
 153 
     | 
    
         
            +
                    """Initialize Spec with type and metadata.
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 156 
     | 
    
         
            +
                        base_type: Base Python type
         
     | 
| 
      
 157 
     | 
    
         
            +
                        *args: Additional Meta objects
         
     | 
| 
      
 158 
     | 
    
         
            +
                        metadata: Existing metadata tuple
         
     | 
| 
      
 159 
     | 
    
         
            +
                        **kw: Keyword arguments converted to Meta objects
         
     | 
| 
      
 160 
     | 
    
         
            +
                    """
         
     | 
| 
      
 161 
     | 
    
         
            +
                    metas = CommonMeta.prepare(*args, metadata=metadata, **kw)
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                    if not_sentinel(base_type, True):
         
     | 
| 
      
 164 
     | 
    
         
            +
                        import types
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                        is_valid_type = (
         
     | 
| 
      
 167 
     | 
    
         
            +
                            isinstance(base_type, type)
         
     | 
| 
      
 168 
     | 
    
         
            +
                            or hasattr(base_type, "__origin__")
         
     | 
| 
      
 169 
     | 
    
         
            +
                            or isinstance(base_type, types.UnionType)
         
     | 
| 
      
 170 
     | 
    
         
            +
                            or str(type(base_type)) == "<class 'types.UnionType'>"
         
     | 
| 
      
 171 
     | 
    
         
            +
                        )
         
     | 
| 
      
 172 
     | 
    
         
            +
                        if not is_valid_type:
         
     | 
| 
      
 173 
     | 
    
         
            +
                            raise ValueError(
         
     | 
| 
      
 174 
     | 
    
         
            +
                                f"base_type must be a type or type annotation, got {base_type}"
         
     | 
| 
      
 175 
     | 
    
         
            +
                            )
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                    # Check for async default factory and warn
         
     | 
| 
      
 178 
     | 
    
         
            +
                    if kw.get("default_factory") and is_coro_func(kw["default_factory"]):
         
     | 
| 
      
 179 
     | 
    
         
            +
                        import warnings
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                        warnings.warn(
         
     | 
| 
      
 182 
     | 
    
         
            +
                            "Async default factories are not yet fully supported by all adapters. "
         
     | 
| 
      
 183 
     | 
    
         
            +
                            "Consider using sync factories for compatibility.",
         
     | 
| 
      
 184 
     | 
    
         
            +
                            UserWarning,
         
     | 
| 
      
 185 
     | 
    
         
            +
                            stacklevel=2,
         
     | 
| 
      
 186 
     | 
    
         
            +
                        )
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
                    object.__setattr__(self, "base_type", base_type)
         
     | 
| 
      
 189 
     | 
    
         
            +
                    object.__setattr__(self, "metadata", metas)
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                def __getitem__(self, key: str) -> Any:
         
     | 
| 
      
 192 
     | 
    
         
            +
                    """Get metadata value by key.
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 195 
     | 
    
         
            +
                        key: Metadata key
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 198 
     | 
    
         
            +
                        Metadata value
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                    Raises:
         
     | 
| 
      
 201 
     | 
    
         
            +
                        KeyError: If key not found
         
     | 
| 
      
 202 
     | 
    
         
            +
                    """
         
     | 
| 
      
 203 
     | 
    
         
            +
                    for meta in self.metadata:
         
     | 
| 
      
 204 
     | 
    
         
            +
                        if meta.key == key:
         
     | 
| 
      
 205 
     | 
    
         
            +
                            return meta.value
         
     | 
| 
      
 206 
     | 
    
         
            +
                    raise KeyError(f"Metadata key '{key}' undefined in Spec.")
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                def get(self, key: str, default: Any = Undefined) -> Any:
         
     | 
| 
      
 209 
     | 
    
         
            +
                    """Get metadata value by key with default.
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 212 
     | 
    
         
            +
                        key: Metadata key
         
     | 
| 
      
 213 
     | 
    
         
            +
                        default: Default value if key not found
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 216 
     | 
    
         
            +
                        Metadata value or default
         
     | 
| 
      
 217 
     | 
    
         
            +
                    """
         
     | 
| 
      
 218 
     | 
    
         
            +
                    with contextlib.suppress(KeyError):
         
     | 
| 
      
 219 
     | 
    
         
            +
                        return self[key]
         
     | 
| 
      
 220 
     | 
    
         
            +
                    return default
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                @property
         
     | 
| 
      
 223 
     | 
    
         
            +
                def name(self) -> MaybeUndefined[str]:
         
     | 
| 
      
 224 
     | 
    
         
            +
                    """Get the field name from metadata."""
         
     | 
| 
      
 225 
     | 
    
         
            +
                    return self.get(CommonMeta.NAME.value)
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                @property
         
     | 
| 
      
 228 
     | 
    
         
            +
                def is_nullable(self) -> bool:
         
     | 
| 
      
 229 
     | 
    
         
            +
                    """Check if field is nullable."""
         
     | 
| 
      
 230 
     | 
    
         
            +
                    return self.get(CommonMeta.NULLABLE.value) is True
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                @property
         
     | 
| 
      
 233 
     | 
    
         
            +
                def is_listable(self) -> bool:
         
     | 
| 
      
 234 
     | 
    
         
            +
                    """Check if field is listable."""
         
     | 
| 
      
 235 
     | 
    
         
            +
                    return self.get(CommonMeta.LISTABLE.value) is True
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                @property
         
     | 
| 
      
 238 
     | 
    
         
            +
                def default(self) -> MaybeUndefined[Any]:
         
     | 
| 
      
 239 
     | 
    
         
            +
                    """Get default value or factory."""
         
     | 
| 
      
 240 
     | 
    
         
            +
                    return self.get(
         
     | 
| 
      
 241 
     | 
    
         
            +
                        CommonMeta.DEFAULT.value,
         
     | 
| 
      
 242 
     | 
    
         
            +
                        self.get(CommonMeta.DEFAULT_FACTORY.value),
         
     | 
| 
      
 243 
     | 
    
         
            +
                    )
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                @property
         
     | 
| 
      
 246 
     | 
    
         
            +
                def has_default_factory(self) -> bool:
         
     | 
| 
      
 247 
     | 
    
         
            +
                    """Check if this spec has a default factory."""
         
     | 
| 
      
 248 
     | 
    
         
            +
                    return _is_factory(self.get(CommonMeta.DEFAULT_FACTORY.value))[0]
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                @property
         
     | 
| 
      
 251 
     | 
    
         
            +
                def has_async_default_factory(self) -> bool:
         
     | 
| 
      
 252 
     | 
    
         
            +
                    """Check if this spec has an async default factory."""
         
     | 
| 
      
 253 
     | 
    
         
            +
                    return _is_factory(self.get(CommonMeta.DEFAULT_FACTORY.value))[1]
         
     | 
| 
      
 254 
     | 
    
         
            +
             
     | 
| 
      
 255 
     | 
    
         
            +
                def create_default_value(self) -> Any:
         
     | 
| 
      
 256 
     | 
    
         
            +
                    """Create default value synchronously.
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 259 
     | 
    
         
            +
                        Default value
         
     | 
| 
      
 260 
     | 
    
         
            +
             
     | 
| 
      
 261 
     | 
    
         
            +
                    Raises:
         
     | 
| 
      
 262 
     | 
    
         
            +
                        ValueError: If no default or factory defined, or if factory is async
         
     | 
| 
      
 263 
     | 
    
         
            +
                    """
         
     | 
| 
      
 264 
     | 
    
         
            +
                    if self.default is Undefined:
         
     | 
| 
      
 265 
     | 
    
         
            +
                        raise ValueError("No default value or factory defined in Spec.")
         
     | 
| 
      
 266 
     | 
    
         
            +
                    if self.has_async_default_factory:
         
     | 
| 
      
 267 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 268 
     | 
    
         
            +
                            "Default factory is asynchronous; cannot create default synchronously. "
         
     | 
| 
      
 269 
     | 
    
         
            +
                            "Use 'await spec.acreate_default_value()' instead."
         
     | 
| 
      
 270 
     | 
    
         
            +
                        )
         
     | 
| 
      
 271 
     | 
    
         
            +
                    if self.has_default_factory:
         
     | 
| 
      
 272 
     | 
    
         
            +
                        return self.default()
         
     | 
| 
      
 273 
     | 
    
         
            +
                    return self.default
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
      
 275 
     | 
    
         
            +
                async def acreate_default_value(self) -> Any:
         
     | 
| 
      
 276 
     | 
    
         
            +
                    """Create default value asynchronously.
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 279 
     | 
    
         
            +
                        Default value
         
     | 
| 
      
 280 
     | 
    
         
            +
                    """
         
     | 
| 
      
 281 
     | 
    
         
            +
                    if self.has_async_default_factory:
         
     | 
| 
      
 282 
     | 
    
         
            +
                        return await self.default()
         
     | 
| 
      
 283 
     | 
    
         
            +
                    return self.create_default_value()
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                def with_updates(self, **kw):
         
     | 
| 
      
 286 
     | 
    
         
            +
                    """Create new Spec with updated metadata.
         
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
      
 288 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 289 
     | 
    
         
            +
                        **kw: Metadata updates
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
      
 291 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 292 
     | 
    
         
            +
                        New Spec instance with updates
         
     | 
| 
      
 293 
     | 
    
         
            +
                    """
         
     | 
| 
      
 294 
     | 
    
         
            +
                    _filtered = [meta for meta in self.metadata if meta.key not in kw]
         
     | 
| 
      
 295 
     | 
    
         
            +
                    for k, v in kw.items():
         
     | 
| 
      
 296 
     | 
    
         
            +
                        if not_sentinel(v):
         
     | 
| 
      
 297 
     | 
    
         
            +
                            _filtered.append(Meta(k, v))
         
     | 
| 
      
 298 
     | 
    
         
            +
                    _metas = tuple(_filtered)
         
     | 
| 
      
 299 
     | 
    
         
            +
                    return type(self)(self.base_type, metadata=_metas)
         
     | 
| 
      
 300 
     | 
    
         
            +
             
     | 
| 
      
 301 
     | 
    
         
            +
                def as_nullable(self) -> Spec:
         
     | 
| 
      
 302 
     | 
    
         
            +
                    """Create nullable version of this spec."""
         
     | 
| 
      
 303 
     | 
    
         
            +
                    return self.with_updates(nullable=True)
         
     | 
| 
      
 304 
     | 
    
         
            +
             
     | 
| 
      
 305 
     | 
    
         
            +
                def as_listable(self) -> Spec:
         
     | 
| 
      
 306 
     | 
    
         
            +
                    """Create listable version of this spec."""
         
     | 
| 
      
 307 
     | 
    
         
            +
                    return self.with_updates(listable=True)
         
     | 
| 
      
 308 
     | 
    
         
            +
             
     | 
| 
      
 309 
     | 
    
         
            +
                def with_default(self, default: Any) -> Spec:
         
     | 
| 
      
 310 
     | 
    
         
            +
                    """Create spec with default value or factory.
         
     | 
| 
      
 311 
     | 
    
         
            +
             
     | 
| 
      
 312 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 313 
     | 
    
         
            +
                        default: Default value or factory function
         
     | 
| 
      
 314 
     | 
    
         
            +
             
     | 
| 
      
 315 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 316 
     | 
    
         
            +
                        New Spec with default
         
     | 
| 
      
 317 
     | 
    
         
            +
                    """
         
     | 
| 
      
 318 
     | 
    
         
            +
                    if callable(default):
         
     | 
| 
      
 319 
     | 
    
         
            +
                        return self.with_updates(default_factory=default)
         
     | 
| 
      
 320 
     | 
    
         
            +
                    return self.with_updates(default=default)
         
     | 
| 
      
 321 
     | 
    
         
            +
             
     | 
| 
      
 322 
     | 
    
         
            +
                def with_validator(
         
     | 
| 
      
 323 
     | 
    
         
            +
                    self, validator: Callable[..., Any] | list[Callable[..., Any]]
         
     | 
| 
      
 324 
     | 
    
         
            +
                ) -> Spec:
         
     | 
| 
      
 325 
     | 
    
         
            +
                    """Create spec with validator(s).
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
      
 327 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 328 
     | 
    
         
            +
                        validator: Single validator or list of validators
         
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
      
 330 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 331 
     | 
    
         
            +
                        New Spec with validator(s)
         
     | 
| 
      
 332 
     | 
    
         
            +
                    """
         
     | 
| 
      
 333 
     | 
    
         
            +
                    return self.with_updates(validator=validator)
         
     | 
| 
      
 334 
     | 
    
         
            +
             
     | 
| 
      
 335 
     | 
    
         
            +
                @property
         
     | 
| 
      
 336 
     | 
    
         
            +
                def annotation(self) -> type[Any]:
         
     | 
| 
      
 337 
     | 
    
         
            +
                    """Plain type annotation representing base type, nullable, and listable.
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 340 
     | 
    
         
            +
                        Type annotation
         
     | 
| 
      
 341 
     | 
    
         
            +
                    """
         
     | 
| 
      
 342 
     | 
    
         
            +
                    if is_sentinel(self.base_type, none_as_sentinel=True):
         
     | 
| 
      
 343 
     | 
    
         
            +
                        return Any
         
     | 
| 
      
 344 
     | 
    
         
            +
                    t_ = self.base_type
         
     | 
| 
      
 345 
     | 
    
         
            +
                    if self.is_listable:
         
     | 
| 
      
 346 
     | 
    
         
            +
                        t_ = list[t_]
         
     | 
| 
      
 347 
     | 
    
         
            +
                    if self.is_nullable:
         
     | 
| 
      
 348 
     | 
    
         
            +
                        return t_ | None
         
     | 
| 
      
 349 
     | 
    
         
            +
                    return t_
         
     | 
| 
      
 350 
     | 
    
         
            +
             
     | 
| 
      
 351 
     | 
    
         
            +
                def annotated(self) -> type[Any]:
         
     | 
| 
      
 352 
     | 
    
         
            +
                    """Materialize this spec into an Annotated type.
         
     | 
| 
      
 353 
     | 
    
         
            +
             
     | 
| 
      
 354 
     | 
    
         
            +
                    This method is cached to ensure repeated calls return the same
         
     | 
| 
      
 355 
     | 
    
         
            +
                    type object for performance and identity checks. The cache is bounded
         
     | 
| 
      
 356 
     | 
    
         
            +
                    using LRU eviction to prevent unbounded memory growth.
         
     | 
| 
      
 357 
     | 
    
         
            +
             
     | 
| 
      
 358 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 359 
     | 
    
         
            +
                        Annotated type with all metadata attached
         
     | 
| 
      
 360 
     | 
    
         
            +
                    """
         
     | 
| 
      
 361 
     | 
    
         
            +
                    # Check cache first with thread safety
         
     | 
| 
      
 362 
     | 
    
         
            +
                    cache_key = (self.base_type, self.metadata)
         
     | 
| 
      
 363 
     | 
    
         
            +
             
     | 
| 
      
 364 
     | 
    
         
            +
                    with _cache_lock:
         
     | 
| 
      
 365 
     | 
    
         
            +
                        if cache_key in _annotated_cache:
         
     | 
| 
      
 366 
     | 
    
         
            +
                            # Move to end to mark as recently used
         
     | 
| 
      
 367 
     | 
    
         
            +
                            _annotated_cache.move_to_end(cache_key)
         
     | 
| 
      
 368 
     | 
    
         
            +
                            return _annotated_cache[cache_key]
         
     | 
| 
      
 369 
     | 
    
         
            +
             
     | 
| 
      
 370 
     | 
    
         
            +
                        # Handle nullable case - wrap in Optional-like union
         
     | 
| 
      
 371 
     | 
    
         
            +
                        actual_type = (
         
     | 
| 
      
 372 
     | 
    
         
            +
                            Any
         
     | 
| 
      
 373 
     | 
    
         
            +
                            if is_sentinel(self.base_type, none_as_sentinel=True)
         
     | 
| 
      
 374 
     | 
    
         
            +
                            else self.base_type
         
     | 
| 
      
 375 
     | 
    
         
            +
                        )
         
     | 
| 
      
 376 
     | 
    
         
            +
                        current_metadata = (
         
     | 
| 
      
 377 
     | 
    
         
            +
                            ()
         
     | 
| 
      
 378 
     | 
    
         
            +
                            if is_sentinel(self.metadata, none_as_sentinel=True)
         
     | 
| 
      
 379 
     | 
    
         
            +
                            else self.metadata
         
     | 
| 
      
 380 
     | 
    
         
            +
                        )
         
     | 
| 
      
 381 
     | 
    
         
            +
             
     | 
| 
      
 382 
     | 
    
         
            +
                        if any(m.key == "nullable" and m.value for m in current_metadata):
         
     | 
| 
      
 383 
     | 
    
         
            +
                            # Use union syntax for nullable
         
     | 
| 
      
 384 
     | 
    
         
            +
                            actual_type = actual_type | None  # type: ignore
         
     | 
| 
      
 385 
     | 
    
         
            +
             
     | 
| 
      
 386 
     | 
    
         
            +
                        if current_metadata:
         
     | 
| 
      
 387 
     | 
    
         
            +
                            args = [actual_type] + list(current_metadata)
         
     | 
| 
      
 388 
     | 
    
         
            +
                            result = Annotated.__class_getitem__(tuple(args))  # type: ignore
         
     | 
| 
      
 389 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 390 
     | 
    
         
            +
                            result = actual_type  # type: ignore[misc]
         
     | 
| 
      
 391 
     | 
    
         
            +
             
     | 
| 
      
 392 
     | 
    
         
            +
                        # Cache the result with LRU eviction
         
     | 
| 
      
 393 
     | 
    
         
            +
                        _annotated_cache[cache_key] = result  # type: ignore[assignment]
         
     | 
| 
      
 394 
     | 
    
         
            +
             
     | 
| 
      
 395 
     | 
    
         
            +
                        # Evict oldest if cache is too large (guard against empty cache)
         
     | 
| 
      
 396 
     | 
    
         
            +
                        while len(_annotated_cache) > _MAX_CACHE_SIZE:
         
     | 
| 
      
 397 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 398 
     | 
    
         
            +
                                _annotated_cache.popitem(last=False)  # Remove oldest
         
     | 
| 
      
 399 
     | 
    
         
            +
                            except KeyError:
         
     | 
| 
      
 400 
     | 
    
         
            +
                                # Cache became empty during race, safe to continue
         
     | 
| 
      
 401 
     | 
    
         
            +
                                break
         
     | 
| 
      
 402 
     | 
    
         
            +
             
     | 
| 
      
 403 
     | 
    
         
            +
                    return result  # type: ignore[return-value]
         
     | 
| 
      
 404 
     | 
    
         
            +
             
     | 
| 
      
 405 
     | 
    
         
            +
                def metadict(
         
     | 
| 
      
 406 
     | 
    
         
            +
                    self, exclude: set[str] | None = None, exclude_common: bool = False
         
     | 
| 
      
 407 
     | 
    
         
            +
                ) -> dict[str, Any]:
         
     | 
| 
      
 408 
     | 
    
         
            +
                    """Get metadata as dictionary.
         
     | 
| 
      
 409 
     | 
    
         
            +
             
     | 
| 
      
 410 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 411 
     | 
    
         
            +
                        exclude: Keys to exclude
         
     | 
| 
      
 412 
     | 
    
         
            +
                        exclude_common: Exclude all common metadata keys
         
     | 
| 
      
 413 
     | 
    
         
            +
             
     | 
| 
      
 414 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 415 
     | 
    
         
            +
                        Dictionary of metadata
         
     | 
| 
      
 416 
     | 
    
         
            +
                    """
         
     | 
| 
      
 417 
     | 
    
         
            +
                    if exclude is None:
         
     | 
| 
      
 418 
     | 
    
         
            +
                        exclude = set()
         
     | 
| 
      
 419 
     | 
    
         
            +
                    if exclude_common:
         
     | 
| 
      
 420 
     | 
    
         
            +
                        exclude = exclude | CommonMeta.allowed()
         
     | 
| 
      
 421 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 422 
     | 
    
         
            +
                        meta.key: meta.value
         
     | 
| 
      
 423 
     | 
    
         
            +
                        for meta in self.metadata
         
     | 
| 
      
 424 
     | 
    
         
            +
                        if meta.key not in exclude
         
     | 
| 
      
 425 
     | 
    
         
            +
                    }
         
     | 
| 
      
 426 
     | 
    
         
            +
             
     | 
| 
      
 427 
     | 
    
         
            +
             
     | 
| 
      
 428 
     | 
    
         
            +
            def _is_factory(obj: Any) -> tuple[bool, bool]:
         
     | 
| 
      
 429 
     | 
    
         
            +
                """Check if object is a factory function.
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 432 
     | 
    
         
            +
                    obj: Object to check
         
     | 
| 
      
 433 
     | 
    
         
            +
             
     | 
| 
      
 434 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 435 
     | 
    
         
            +
                    Tuple of (is_factory, is_async)
         
     | 
| 
      
 436 
     | 
    
         
            +
                """
         
     | 
| 
      
 437 
     | 
    
         
            +
                if not callable(obj):
         
     | 
| 
      
 438 
     | 
    
         
            +
                    return (False, False)
         
     | 
| 
      
 439 
     | 
    
         
            +
                if is_coro_func(obj):
         
     | 
| 
      
 440 
     | 
    
         
            +
                    return (True, True)
         
     | 
| 
      
 441 
     | 
    
         
            +
                return (True, False)
         
     | 
    
        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,14 +77,15 @@ 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]
         
     | 
| 
       85 
86 
     | 
    
         
             
                metadata: tuple[Meta, ...]
         
     | 
| 
       86 
87 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
                def __init__(self, **kwargs: Any) -> None:
         
     | 
| 
      
 88 
     | 
    
         
            +
                def __init__(self, base_type: type[Any] = None, **kwargs: Any) -> None:
         
     | 
| 
       88 
89 
     | 
    
         
             
                    """Initialize FieldModel with legacy compatibility.
         
     | 
| 
       89 
90 
     | 
    
         | 
| 
       90 
91 
     | 
    
         
             
                    Handles backward compatibility by converting old-style kwargs to the new
         
     | 
| 
         @@ -94,6 +95,8 @@ class FieldModel(Params): 
     | 
|
| 
       94 
95 
     | 
    
         
             
                        **kwargs: Arbitrary keyword arguments, including legacy ones
         
     | 
| 
       95 
96 
     | 
    
         
             
                    """
         
     | 
| 
       96 
97 
     | 
    
         
             
                    # Convert legacy kwargs to proper format
         
     | 
| 
      
 98 
     | 
    
         
            +
                    if base_type is not None:
         
     | 
| 
      
 99 
     | 
    
         
            +
                        kwargs["base_type"] = base_type
         
     | 
| 
       97 
100 
     | 
    
         
             
                    converted = self._convert_kwargs_to_params(**kwargs)
         
     | 
| 
       98 
101 
     | 
    
         | 
| 
       99 
102 
     | 
    
         
             
                    # Set fields directly and validate
         
     | 
| 
         @@ -538,7 +541,7 @@ class FieldModel(Params): 
     | 
|
| 
       538 
541 
     | 
    
         
             
                    field_info = PydanticField(**field_kwargs)
         
     | 
| 
       539 
542 
     | 
    
         | 
| 
       540 
543 
     | 
    
         
             
                    # Set the annotation from base_type for backward compatibility
         
     | 
| 
       541 
     | 
    
         
            -
                    field_info.annotation = self. 
     | 
| 
      
 544 
     | 
    
         
            +
                    field_info.annotation = self.annotation
         
     | 
| 
       542 
545 
     | 
    
         | 
| 
       543 
546 
     | 
    
         
             
                    return field_info
         
     | 
| 
       544 
547 
     | 
    
         | 
| 
         @@ -745,8 +748,14 @@ class FieldModel(Params): 
     | 
|
| 
       745 
748 
     | 
    
         | 
| 
       746 
749 
     | 
    
         
             
                @property
         
     | 
| 
       747 
750 
     | 
    
         
             
                def annotation(self) -> type[Any]:
         
     | 
| 
       748 
     | 
    
         
            -
                     
     | 
| 
       749 
     | 
    
         
            -
             
     | 
| 
      
 751 
     | 
    
         
            +
                    if self._is_sentinel(self.base_type):
         
     | 
| 
      
 752 
     | 
    
         
            +
                        return Any
         
     | 
| 
      
 753 
     | 
    
         
            +
                    t_ = self.base_type
         
     | 
| 
      
 754 
     | 
    
         
            +
                    if self.is_listable:
         
     | 
| 
      
 755 
     | 
    
         
            +
                        t_ = list[t_]
         
     | 
| 
      
 756 
     | 
    
         
            +
                    if self.is_nullable:
         
     | 
| 
      
 757 
     | 
    
         
            +
                        t_ = t_ | None
         
     | 
| 
      
 758 
     | 
    
         
            +
                    return t_
         
     | 
| 
       750 
759 
     | 
    
         | 
| 
       751 
760 
     | 
    
         
             
                def to_dict(self) -> dict[str, Any]:
         
     | 
| 
       752 
761 
     | 
    
         
             
                    """Convert field model to dictionary for backward compatibility.
         
     | 
| 
         @@ -774,6 +783,59 @@ class FieldModel(Params): 
     | 
|
| 
       774 
783 
     | 
    
         
             
                        ]
         
     | 
| 
       775 
784 
     | 
    
         
             
                    )
         
     | 
| 
       776 
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 
     | 
    
         
            +
             
     | 
| 
       777 
839 
     | 
    
         
             
                def metadata_dict(
         
     | 
| 
       778 
840 
     | 
    
         
             
                    self, exclude: list[str] | None = None
         
     | 
| 
       779 
841 
     | 
    
         
             
                ) -> dict[str, Any]:
         
     | 
    
        lionagi/models/hashable_model.py
    CHANGED
    
    | 
         @@ -94,12 +94,11 @@ class HashableModel(BaseModel): 
     | 
|
| 
       94 
94 
     | 
    
         
             
            def _get_default_hashable_serializer():
         
     | 
| 
       95 
95 
     | 
    
         
             
                global _DEFAULT_HASHABLE_SERIALIZER
         
     | 
| 
       96 
96 
     | 
    
         
             
                if _DEFAULT_HASHABLE_SERIALIZER is None:
         
     | 
| 
       97 
     | 
    
         
            -
                    from lionagi.protocols.ids import Element 
     | 
| 
      
 97 
     | 
    
         
            +
                    from lionagi.protocols.ids import Element
         
     | 
| 
       98 
98 
     | 
    
         | 
| 
       99 
99 
     | 
    
         
             
                    _DEFAULT_HASHABLE_SERIALIZER = ln.get_orjson_default(
         
     | 
| 
       100 
     | 
    
         
            -
                        order=[ 
     | 
| 
      
 100 
     | 
    
         
            +
                        order=[Element, BaseModel],
         
     | 
| 
       101 
101 
     | 
    
         
             
                        additional={
         
     | 
| 
       102 
     | 
    
         
            -
                            IDType: lambda o: str(o),
         
     | 
| 
       103 
102 
     | 
    
         
             
                            Element: lambda o: o.to_dict(),
         
     | 
| 
       104 
103 
     | 
    
         
             
                            BaseModel: lambda o: o.model_dump(mode="json"),
         
     | 
| 
       105 
104 
     | 
    
         
             
                        },
         
     | 
    
        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
         
     | 
| 
         @@ -9,7 +9,6 @@ from pydantic import BaseModel 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            logger = logging.getLogger(__name__)
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            from lionagi.fields.instruct import Instruct
         
     | 
| 
       13 
12 
     | 
    
         
             
            from lionagi.libs.schema.as_readable import as_readable
         
     | 
| 
       14 
13 
     | 
    
         
             
            from lionagi.libs.validate.common_field_validators import (
         
     | 
| 
       15 
14 
     | 
    
         
             
                validate_model_to_type,
         
     | 
| 
         @@ -18,6 +17,7 @@ from lionagi.ln.fuzzy import FuzzyMatchKeysParams 
     | 
|
| 
       18 
17 
     | 
    
         
             
            from lionagi.models.field_model import FieldModel
         
     | 
| 
       19 
18 
     | 
    
         
             
            from lionagi.service.imodel import iModel
         
     | 
| 
       20 
19 
     | 
    
         | 
| 
      
 20 
     | 
    
         
            +
            from ..fields import Instruct
         
     | 
| 
       21 
21 
     | 
    
         
             
            from ..types import (
         
     | 
| 
       22 
22 
     | 
    
         
             
                ActionParam,
         
     | 
| 
       23 
23 
     | 
    
         
             
                ChatParam,
         
     | 
    
        lionagi/operations/act/act.py
    CHANGED
    
    | 
         @@ -6,10 +6,10 @@ from typing import TYPE_CHECKING, Literal 
     | 
|
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            from pydantic import BaseModel
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
            from lionagi. 
     | 
| 
       10 
     | 
    
         
            -
            from lionagi. 
     | 
| 
       11 
     | 
    
         
            -
            from lionagi.protocols.types import ActionRequest, ActionResponse
         
     | 
| 
      
 9 
     | 
    
         
            +
            from lionagi.ln import AlcallParams
         
     | 
| 
      
 10 
     | 
    
         
            +
            from lionagi.protocols.messages import ActionRequest, ActionResponse
         
     | 
| 
       12 
11 
     | 
    
         | 
| 
      
 12 
     | 
    
         
            +
            from ..fields import ActionResponseModel
         
     | 
| 
       13 
13 
     | 
    
         
             
            from ..types import ActionParam
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
            if TYPE_CHECKING:
         
     | 
    
        lionagi/operations/builder.py
    CHANGED
    
    | 
         @@ -46,9 +46,9 @@ class OperationGraphBuilder: 
     | 
|
| 
       46 
46 
     | 
    
         
             
                    >>> result = await session.flow(graph)
         
     | 
| 
       47 
47 
     | 
    
         
             
                    >>>
         
     | 
| 
       48 
48 
     | 
    
         
             
                    >>> # Expand based on results
         
     | 
| 
       49 
     | 
    
         
            -
                    >>> if hasattr(result, ' 
     | 
| 
      
 49 
     | 
    
         
            +
                    >>> if hasattr(result, 'instruct_model'):
         
     | 
| 
       50 
50 
     | 
    
         
             
                    ...     builder.expand_from_result(
         
     | 
| 
       51 
     | 
    
         
            -
                    ...         result. 
     | 
| 
      
 51 
     | 
    
         
            +
                    ...         result.instruct_model,
         
     | 
| 
       52 
52 
     | 
    
         
             
                    ...         source_node_id=builder.last_operation_id,
         
     | 
| 
       53 
53 
     | 
    
         
             
                    ...         operation="instruct"
         
     | 
| 
       54 
54 
     | 
    
         
             
                    ...     )
         
     | 
| 
         @@ -150,7 +150,7 @@ class OperationGraphBuilder: 
     | 
|
| 
       150 
150 
     | 
    
         
             
                    based on results.
         
     | 
| 
       151 
151 
     | 
    
         | 
| 
       152 
152 
     | 
    
         
             
                    Args:
         
     | 
| 
       153 
     | 
    
         
            -
                        items: Items from result to expand (e.g.,  
     | 
| 
      
 153 
     | 
    
         
            +
                        items: Items from result to expand (e.g., instruct_model)
         
     | 
| 
       154 
154 
     | 
    
         
             
                        source_node_id: ID of node that produced these items
         
     | 
| 
       155 
155 
     | 
    
         
             
                        operation: Operation to apply to each item
         
     | 
| 
       156 
156 
     | 
    
         
             
                        strategy: How to organize the expanded operations
         
     | 
| 
         @@ -253,11 +253,9 @@ class OperationGraphBuilder: 
     | 
|
| 
       253 
253 
     | 
    
         
             
                    if not sources:
         
     | 
| 
       254 
254 
     | 
    
         
             
                        raise ValueError("No source nodes for aggregation")
         
     | 
| 
       255 
255 
     | 
    
         | 
| 
       256 
     | 
    
         
            -
                    # Add aggregation metadata - convert  
     | 
| 
      
 256 
     | 
    
         
            +
                    # Add aggregation metadata - convert UUID to strings for JSON serialization
         
     | 
| 
       257 
257 
     | 
    
         
             
                    agg_params = {
         
     | 
| 
       258 
     | 
    
         
            -
                        "aggregation_sources": [
         
     | 
| 
       259 
     | 
    
         
            -
                            str(s) for s in sources
         
     | 
| 
       260 
     | 
    
         
            -
                        ],  # Convert IDType to strings
         
     | 
| 
      
 258 
     | 
    
         
            +
                        "aggregation_sources": [str(s) for s in sources],
         
     | 
| 
       261 
259 
     | 
    
         
             
                        "aggregation_count": len(sources),
         
     | 
| 
       262 
260 
     | 
    
         
             
                        **parameters,
         
     | 
| 
       263 
261 
     | 
    
         
             
                    }
         
     |