Model System¶
This module provides foundational classes and utilities for dynamically creating and managing data models within LionAGI. These “operatives” form the building blocks of the system, offering features like:
Runtime field creation (add, update, remove fields)
Nested validation via Pydantic
Hashing/serialization for easy storage and transport
Schema generation (via
FieldModel
andModelParams
)
Below is an overview of the major components in this subsystem.
1. FieldModel (field_model.py
)¶
A configurable field definition that captures:
Name (str)
Annotation (type or Any)
Default or default_factory
Optional validator (a function)
Documentation info (title, description, examples)
Additional Pydantic flags (e.g.
exclude
,deprecated
, etc.)
Usage:
from lionagi.operatives.models.field_model import FieldModel
field = FieldModel(
name="age",
annotation=int,
default=0,
description="User age in years",
validator=lambda cls, v: v if v >= 0 else 0
)
# Then use field.field_info or field.field_validator as needed
2. ModelParams (model_params.py
)¶
Collects configuration for generating a new Pydantic model class, such as:
parameter_fields
: a dictionary mapping field names toFieldInfo
.field_models
: a list ofFieldModel
objects.exclude_fields
: a list of fields to remove from the final model.inherit_base
: Whether to extend a base model class (default: True).config_dict
: Pydantic config overrides (e.g.,frozen=True
).doc
: Docstring for the generated model.
Finally, call create_new_model()
to get a brand-new Pydantic class
(with your specified fields, validators, docstring, etc.).
Example:
from pydantic import BaseModel
from lionagi.operatives.models.model_params import ModelParams
from lionagi.operatives.models.field_model import FieldModel
params = ModelParams(
name="DynamicUser",
base_type=BaseModel,
field_models=[
FieldModel(name="username", annotation=str, default="guest"),
FieldModel(name="age", annotation=int, default=0),
],
doc="Dynamically created user model."
)
DynamicUser = params.create_new_model()
user = DynamicUser(username="Alice", age=30)
print(user) # => DynamicUser(username='Alice', age=30)
3. OperableModel (operable_model.py
)¶
This is a Pydantic model that allows runtime modifications to its schema, including:
add_field(name, value=..., annotation=...)()
: Add a new field.update_field(...)()
: Update an existing field or create if absent.remove_field(name)()
: Remove a field.
All extra fields are stored in extra_fields
(mapping from
name to pydantic.FieldInfo
) and extra_field_models
(mapping from name to FieldModel
). The underlying dictionary
structure remains valid with Pydantic’s type checks and serialization logic.
Example:
from lionagi.operatives.models.operable_model import OperableModel
class User(OperableModel):
name: str = "default_name"
user = User()
user.add_field("age", value=25, annotation=int)
print(user.age) # => 25
user.update_field("age", value=26)
print(user.age) # => 26
user.remove_field("age")
4. Note (note.py
)¶
A specialized object for managing nested dictionary data:
get(indices, default)()
,set(indices, value)()
,pop(indices)()
, etc. for deeply nested access or updates.update(indices, value)()
merges with an existing dict or appends to a list.keys(flat=True|False)()
: Optionally flatten nested structures.
It’s a convenient alternative to constantly handling deeply nested dictionaries manually in your code.
Example:
from lionagi.operatives.models.note import Note
note = Note(user={"name": "John", "settings": {"theme": "dark"}})
name = note.get(["user", "name"]) # "John"
note.update(["user", "settings"], {"language": "en"})
print(note.content)
# => {"user": {"name": "John", "settings": {"theme": "dark", "language": "en"}}}
5. SchemaModel (schema_model.py
)¶
A lightweight extension of HashableModel
that sets Pydantic config
to forbid extra fields by default and use enum values. Provides a
:method:`keys()` utility that returns the field names defined in the schema.
6. HashableModel (hashable_model.py
)¶
Enables your model to be hashable, so it can be used as keys in a
dictionary or placed in a set. It does this by converting all fields to
a dictionary (via to_dict()
) and then hashing the sorted key-value pairs.
Note: Some fields may need to be serialized or converted to strings if they are not inherently hashable.
Example:
from lionagi.utils import HashableModel
class MyConfig(HashableModel):
alpha: float
beta: str
c1 = MyConfig(alpha=1.0, beta="test")
c2 = MyConfig(alpha=1.0, beta="test")
s = {c1, c2}
print(len(s)) # => 1 because c1 and c2 have the same hash
Putting It All Together¶
Typical use case for these model classes:
Define a base model with core fields (like a user or config).
Add or update fields at runtime if the structure is not fixed (e.g., an “Operable” approach for flexible schemas).
Dynamically create entire new models with
ModelParams
(for advanced code generation scenarios).Store nested data in
Note
objects for iterative or complicated updates.Output or persist model objects as needed; they can be hashed, used as dictionary keys, or automatically serialized with LionAGI’s adapter system.
This design allows building truly “operable” data structures in a dynamic environment—where the model schema might evolve during runtime, and you need robust type checking, validation, and hashing to maintain data integrity.