lionagi 0.5.0__py3-none-any.whl → 0.5.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.
Files changed (79) hide show
  1. lionagi/__init__.py +0 -1
  2. lionagi/core/action/tool.py +3 -5
  3. lionagi/core/communication/action_request.py +3 -3
  4. lionagi/core/communication/message.py +3 -3
  5. lionagi/core/communication/utils.py +3 -3
  6. lionagi/core/generic/component.py +4 -4
  7. lionagi/core/generic/element.py +51 -47
  8. lionagi/core/generic/graph.py +1 -1
  9. lionagi/core/generic/log.py +2 -2
  10. lionagi/core/generic/pile.py +10 -11
  11. lionagi/core/generic/progression.py +19 -12
  12. lionagi/core/generic/utils.py +6 -3
  13. lionagi/core/models/base.py +11 -68
  14. lionagi/core/models/field_model.py +42 -19
  15. lionagi/core/models/{new_model_params.py → model_params.py} +5 -6
  16. lionagi/core/models/note.py +2 -2
  17. lionagi/core/models/operable_model.py +8 -4
  18. lionagi/core/models/schema_model.py +9 -31
  19. lionagi/core/models/types.py +15 -6
  20. lionagi/core/session/branch.py +10 -7
  21. lionagi/core/session/branch_mixins.py +52 -15
  22. lionagi/core/session/session.py +1 -2
  23. lionagi/core/typing/__init__.py +4 -4
  24. lionagi/core/typing/{concepts.py → _concepts.py} +43 -2
  25. lionagi/core/typing/_id.py +104 -0
  26. lionagi/integrations/anthropic_/AnthropicModel.py +8 -3
  27. lionagi/integrations/anthropic_/AnthropicService.py +4 -0
  28. lionagi/integrations/groq_/GroqModel.py +11 -4
  29. lionagi/integrations/groq_/GroqService.py +4 -0
  30. lionagi/integrations/litellm_/imodel.py +10 -8
  31. lionagi/integrations/ollama_/OllamaService.py +4 -0
  32. lionagi/integrations/openai_/OpenAIModel.py +12 -3
  33. lionagi/integrations/openai_/image_token_calculator/image_token_calculator.py +14 -8
  34. lionagi/integrations/perplexity_/PerplexityModel.py +8 -3
  35. lionagi/integrations/perplexity_/PerplexityService.py +4 -0
  36. lionagi/libs/func/async_calls/__init__.py +6 -3
  37. lionagi/libs/func/async_calls/alcall.py +46 -0
  38. lionagi/libs/func/async_calls/bcall.py +49 -1
  39. lionagi/libs/func/async_calls/rcall.py +32 -0
  40. lionagi/libs/utils.py +12 -1
  41. lionagi/operations/brainstorm/brainstorm.py +3 -3
  42. lionagi/operations/plan/plan.py +3 -3
  43. lionagi/protocols/__init__.py +3 -0
  44. lionagi/protocols/configs/__init__.py +0 -15
  45. lionagi/protocols/configs/branch_config.py +1 -1
  46. lionagi/protocols/configs/imodel_config.py +2 -2
  47. lionagi/protocols/configs/log_config.py +1 -1
  48. lionagi/protocols/configs/types.py +15 -0
  49. lionagi/protocols/operatives/__init__.py +3 -15
  50. lionagi/protocols/operatives/action.py +4 -0
  51. lionagi/protocols/operatives/instruct.py +6 -2
  52. lionagi/protocols/operatives/operative.py +9 -21
  53. lionagi/protocols/operatives/prompts.py +4 -0
  54. lionagi/protocols/operatives/reason.py +4 -0
  55. lionagi/protocols/operatives/step.py +11 -23
  56. lionagi/protocols/operatives/types.py +19 -0
  57. lionagi/protocols/registries/__init__.py +3 -0
  58. lionagi/protocols/registries/_component_registry.py +4 -0
  59. lionagi/protocols/registries/_pile_registry.py +4 -0
  60. lionagi/service/__init__.py +3 -0
  61. lionagi/service/imodel.py +22 -7
  62. lionagi/service/service.py +4 -0
  63. lionagi/service/service_match_util.py +4 -4
  64. lionagi/settings.py +10 -18
  65. lionagi/strategies/base.py +4 -5
  66. lionagi/strategies/concurrent.py +4 -3
  67. lionagi/strategies/concurrent_chunk.py +3 -3
  68. lionagi/strategies/concurrent_sequential_chunk.py +3 -3
  69. lionagi/strategies/params.py +7 -4
  70. lionagi/version.py +1 -1
  71. {lionagi-0.5.0.dist-info → lionagi-0.5.2.dist-info}/METADATA +214 -15
  72. {lionagi-0.5.0.dist-info → lionagi-0.5.2.dist-info}/RECORD +77 -76
  73. lionagi/core/typing/config.py +0 -15
  74. lionagi/core/typing/id.py +0 -221
  75. /lionagi/core/typing/{pydantic_.py → _pydantic.py} +0 -0
  76. /lionagi/core/typing/{typing_.py → _typing.py} +0 -0
  77. /lionagi/integrations/{services.py → _services.py} +0 -0
  78. {lionagi-0.5.0.dist-info → lionagi-0.5.2.dist-info}/WHEEL +0 -0
  79. {lionagi-0.5.0.dist-info → lionagi-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,9 +2,13 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from ..typing.pydantic_ import ConfigDict, Field, FieldInfo, field_validator
6
- from ..typing.typing_ import UNDEFINED, Any, Callable
7
- from .schema_model import SchemaModel, common_config
5
+ from lionagi.libs.utils import is_same_dtype
6
+
7
+ from ..typing._pydantic import ConfigDict, Field, FieldInfo, field_validator
8
+ from ..typing._typing import UNDEFINED, Any, Callable, UndefinedType
9
+ from .schema_model import SchemaModel
10
+
11
+ __all__ = ("FieldModel",)
8
12
 
9
13
 
10
14
  class FieldModel(SchemaModel):
@@ -51,24 +55,34 @@ class FieldModel(SchemaModel):
51
55
  """
52
56
 
53
57
  model_config = ConfigDict(
54
- extra="allow", validate_default=False, **common_config
58
+ extra="allow",
59
+ validate_default=False,
60
+ populate_by_name=True,
61
+ arbitrary_types_allowed=True,
62
+ use_enum_values=True,
55
63
  )
56
64
 
57
65
  # Field configuration attributes
58
66
  default: Any = UNDEFINED # Default value
59
- default_factory: Callable = UNDEFINED # Factory function for default value
60
- title: str = UNDEFINED # Field title
61
- description: str = UNDEFINED # Field description
62
- examples: list = UNDEFINED # Example values
63
- validators: list = UNDEFINED # Validation functions
64
- exclude: bool = UNDEFINED # Exclude from serialization
65
- deprecated: bool = UNDEFINED # Mark as deprecated
66
- frozen: bool = UNDEFINED # Mark as immutable
67
- alias: str = UNDEFINED # Alternative field name
68
- alias_priority: int = UNDEFINED # Priority for alias resolution
67
+ default_factory: Callable | UndefinedType = (
68
+ UNDEFINED # Factory function for default value
69
+ )
70
+ title: str | UndefinedType = UNDEFINED # Field title
71
+ description: str | UndefinedType = UNDEFINED # Field description
72
+ examples: list | UndefinedType = UNDEFINED # Example values
73
+ validators: list | UndefinedType = UNDEFINED # Validation functions
74
+ exclude: bool | UndefinedType = UNDEFINED # Exclude from serialization
75
+ deprecated: bool | UndefinedType = UNDEFINED # Mark as deprecated
76
+ frozen: bool | UndefinedType = UNDEFINED # Mark as immutable
77
+ alias: str | UndefinedType = UNDEFINED # Alternative field name
78
+ alias_priority: int | UndefinedType = (
79
+ UNDEFINED # Priority for alias resolution
80
+ )
69
81
 
70
82
  # Core field attributes
71
- name: str = Field(..., exclude=True) # Field name (required)
83
+ name: str | UndefinedType = Field(
84
+ ..., exclude=True
85
+ ) # Field name (required)
72
86
  annotation: type | Any = Field(UNDEFINED, exclude=True) # Type annotation
73
87
  validator: Callable | Any = Field(
74
88
  UNDEFINED, exclude=True
@@ -77,6 +91,18 @@ class FieldModel(SchemaModel):
77
91
  default_factory=dict, exclude=True
78
92
  ) # Validator parameters
79
93
 
94
+ @field_validator("validators", mode="before")
95
+ def _validate_validators(cls, v) -> list[Callable]:
96
+ if v is None or v is UNDEFINED:
97
+ return []
98
+ if isinstance(v, Callable):
99
+ return [v]
100
+ if isinstance(v, list) and is_same_dtype(v, Callable):
101
+ return v
102
+ raise ValueError(
103
+ "Validators must be a list of functions or a function"
104
+ )
105
+
80
106
  @property
81
107
  def field_info(self) -> FieldInfo:
82
108
  """Generate Pydantic FieldInfo object from field configuration.
@@ -92,7 +118,7 @@ class FieldModel(SchemaModel):
92
118
  annotation = (
93
119
  self.annotation if self.annotation is not UNDEFINED else Any
94
120
  )
95
- field_obj: FieldInfo = Field(**self.to_dict(True)) # type: ignore
121
+ field_obj: FieldInfo = Field(**self.to_dict()) # type: ignore
96
122
  field_obj.annotation = annotation
97
123
  return field_obj
98
124
 
@@ -117,6 +143,3 @@ class FieldModel(SchemaModel):
117
143
  self.validator
118
144
  )
119
145
  }
120
-
121
-
122
- __all__ = ["FieldModel"]
@@ -9,7 +9,7 @@ from pydantic import BaseModel
9
9
  from lionagi.libs.parse import validate_boolean
10
10
  from lionagi.libs.utils import copy
11
11
 
12
- from ..typing.pydantic_ import (
12
+ from ..typing._pydantic import (
13
13
  Field,
14
14
  FieldInfo,
15
15
  PrivateAttr,
@@ -17,12 +17,14 @@ from ..typing.pydantic_ import (
17
17
  field_validator,
18
18
  model_validator,
19
19
  )
20
- from ..typing.typing_ import Callable, Self
20
+ from ..typing._typing import Callable, Self
21
21
  from .field_model import FieldModel
22
22
  from .schema_model import SchemaModel
23
23
 
24
+ __all__ = ("ModelParams",)
24
25
 
25
- class NewModelParams(SchemaModel):
26
+
27
+ class ModelParams(SchemaModel):
26
28
  """Configuration class for dynamically creating new Pydantic models."""
27
29
 
28
30
  name: str | None = None
@@ -190,6 +192,3 @@ class NewModelParams(SchemaModel):
190
192
  if self.frozen:
191
193
  a.model_config["frozen"] = True
192
194
  return a
193
-
194
-
195
- __all__ = ["NewModelParams"]
@@ -5,8 +5,8 @@
5
5
  from lionagi.libs.parse import flatten, nget, ninsert, npop, nset, to_list
6
6
  from lionagi.libs.utils import copy
7
7
 
8
- from ..typing.pydantic_ import ConfigDict, Field, field_serializer
9
- from ..typing.typing_ import (
8
+ from ..typing._pydantic import ConfigDict, Field, field_serializer
9
+ from ..typing._typing import (
10
10
  UNDEFINED,
11
11
  Any,
12
12
  ItemsView,
@@ -4,7 +4,7 @@
4
4
 
5
5
  from lionagi.libs.utils import is_same_dtype
6
6
 
7
- from ..typing.pydantic_ import (
7
+ from ..typing._pydantic import (
8
8
  ConfigDict,
9
9
  Field,
10
10
  FieldInfo,
@@ -12,8 +12,8 @@ from ..typing.pydantic_ import (
12
12
  field_serializer,
13
13
  field_validator,
14
14
  )
15
- from ..typing.typing_ import UNDEFINED, Any, TypeVar, override
16
- from .base import BaseAutoModel, common_config
15
+ from ..typing._typing import UNDEFINED, Any, TypeVar, override
16
+ from .base import BaseAutoModel
17
17
  from .field_model import FieldModel
18
18
 
19
19
  FIELD_NAME = TypeVar("FIELD_NAME", bound=str)
@@ -55,7 +55,11 @@ class OperableModel(BaseAutoModel):
55
55
  """
56
56
 
57
57
  model_config = ConfigDict(
58
- extra="forbid", validate_default=False, **common_config
58
+ extra="forbid",
59
+ validate_default=False,
60
+ populate_by_name=True,
61
+ arbitrary_types_allowed=True,
62
+ use_enum_values=True,
59
63
  )
60
64
 
61
65
  extra_fields: dict[str, Any] = Field(default_factory=dict)
@@ -2,39 +2,20 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from ..typing.pydantic_ import ConfigDict
6
- from .base import BaseAutoModel, common_config
5
+ from ..typing._pydantic import ConfigDict
6
+ from .base import BaseAutoModel
7
7
 
8
+ __all__ = ("SchemaModel",)
8
9
 
9
- class SchemaModel(BaseAutoModel):
10
- """Schema definition model with strict validation.
11
-
12
- Extends BaseAutoModel to provide:
13
- - Extra field forbidding
14
- - Disabled default validation
15
- - Field name listing
16
- - Nested validation
17
-
18
- Example:
19
- ```python
20
- class UserSchema(SchemaModel):
21
- name: str = Field(min_length=2)
22
- age: int = Field(gt=0)
23
- settings: dict[str, Any] = Field(default_factory=dict)
24
-
25
- # Raises error - extra fields forbidden
26
- user = UserSchema(name="John", age=30, extra="value")
27
- ```
28
10
 
29
- Attributes:
30
- model_config: Schema-specific configuration
31
- - extra: "forbid" to prevent extra fields
32
- - validate_default: False to skip default validation
33
- - Plus inherited BaseAutoModel config
34
- """
11
+ class SchemaModel(BaseAutoModel):
35
12
 
36
13
  model_config = ConfigDict(
37
- extra="forbid", validate_default=False, **common_config
14
+ extra="forbid",
15
+ validate_default=False,
16
+ populate_by_name=True,
17
+ arbitrary_types_allowed=True,
18
+ use_enum_values=True,
38
19
  )
39
20
 
40
21
  @classmethod
@@ -45,6 +26,3 @@ class SchemaModel(BaseAutoModel):
45
26
  List of field names defined in model schema
46
27
  """
47
28
  return list(cls.model_fields.keys())
48
-
49
-
50
- __all__ = ["SchemaModel"]
@@ -2,9 +2,18 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from .base import *
6
- from .field_model import *
7
- from .new_model_params import *
8
- from .note import *
9
- from .operable_model import *
10
- from .schema_model import *
5
+ from .base import BaseAutoModel
6
+ from .field_model import FieldModel
7
+ from .model_params import ModelParams
8
+ from .note import Note
9
+ from .operable_model import OperableModel
10
+ from .schema_model import SchemaModel
11
+
12
+ __all__ = (
13
+ "BaseAutoModel",
14
+ "FieldModel",
15
+ "ModelParams",
16
+ "Note",
17
+ "OperableModel",
18
+ "SchemaModel",
19
+ )
@@ -9,7 +9,6 @@ from pydantic import model_validator
9
9
 
10
10
  from lionagi.core.generic.types import Component, LogManager, Pile, Progression
11
11
  from lionagi.core.typing import ID
12
- from lionagi.integrations.litellm_.imodel import LiteiModel
13
12
  from lionagi.protocols.operatives.instruct import Instruct, OperationInstruct
14
13
  from lionagi.service import iModel
15
14
  from lionagi.settings import Settings
@@ -25,8 +24,8 @@ class Branch(Component, BranchActionMixin, BranchOperationMixin):
25
24
  name: str | None = None
26
25
  msgs: MessageManager = None
27
26
  acts: ActionManager = None
28
- imodel: iModel | LiteiModel | None = None
29
- parse_imodel: iModel | LiteiModel | None = None
27
+ imodel: iModel | None = None
28
+ parse_imodel: iModel | None = None
30
29
 
31
30
  @model_validator(mode="before")
32
31
  def _validate_data(cls, data: dict) -> dict:
@@ -42,29 +41,33 @@ class Branch(Component, BranchActionMixin, BranchOperationMixin):
42
41
  )
43
42
  if not message_manager.logger:
44
43
  message_manager.logger = LogManager(
45
- **Settings.Branch.BRANCH.message_log_config.to_dict(True)
44
+ **Settings.Branch.BRANCH.message_log_config.to_dict()
46
45
  )
47
46
 
48
47
  acts = data.pop("acts", None)
49
48
  if not acts:
50
49
  acts = ActionManager()
51
50
  acts.logger = LogManager(
52
- **Settings.Branch.BRANCH.action_log_config.to_dict(True)
51
+ **Settings.Branch.BRANCH.action_log_config.to_dict()
53
52
  )
54
53
  if "tools" in data:
55
54
  acts.register_tools(data.pop("tools"))
56
55
 
57
56
  imodel = data.pop(
58
57
  "imodel",
59
- LiteiModel(**Settings.iModel.CHAT),
58
+ iModel(**Settings.iModel.CHAT.to_dict()),
59
+ )
60
+ parse_imodel = data.pop(
61
+ "parse_imodel",
62
+ iModel(**Settings.iModel.PARSE.to_dict()),
60
63
  )
61
-
62
64
  out = {
63
65
  "user": user,
64
66
  "name": name,
65
67
  "msgs": message_manager,
66
68
  "acts": acts,
67
69
  "imodel": imodel,
70
+ "parse_imodel": parse_imodel,
68
71
  **data,
69
72
  }
70
73
  return out
@@ -14,13 +14,12 @@ from lionagi.core.typing import (
14
14
  UNDEFINED,
15
15
  BaseModel,
16
16
  FieldModel,
17
- NewModelParams,
17
+ ModelParams,
18
18
  )
19
- from lionagi.integrations.litellm_.imodel import LiteiModel
20
19
  from lionagi.integrations.pydantic_ import break_down_pydantic_annotation
21
20
  from lionagi.libs.func.types import alcall
22
21
  from lionagi.libs.parse import to_json, validate_mapping
23
- from lionagi.protocols.operatives import (
22
+ from lionagi.protocols.operatives.types import (
24
23
  ActionRequestModel,
25
24
  ActionResponseModel,
26
25
  Operative,
@@ -108,7 +107,7 @@ class BranchOperationMixin(ABC):
108
107
  recipient=None,
109
108
  operative_model: type[BaseModel] = None,
110
109
  progress=None,
111
- imodel: iModel | LiteiModel = None,
110
+ imodel: iModel = None,
112
111
  reason: bool = False,
113
112
  actions: bool = False,
114
113
  exclude_fields: list | dict | None = None,
@@ -120,15 +119,15 @@ class BranchOperationMixin(ABC):
120
119
  images: list = None,
121
120
  image_detail: Literal["low", "high", "auto"] = None,
122
121
  max_retries: int = None,
123
- retry_imodel: iModel | LiteiModel = None,
122
+ retry_imodel: iModel = None,
124
123
  retry_kwargs: dict = {},
125
124
  auto_retry_parse: bool = True,
126
125
  field_models: list[FieldModel] | None = None,
127
126
  skip_validation: bool = False,
128
127
  tools: str | Tool | list[Tool | str] | bool = None,
129
- request_params: NewModelParams = None,
128
+ request_params: ModelParams = None,
130
129
  request_param_kwargs: dict = {},
131
- response_params: NewModelParams = None,
130
+ response_params: ModelParams = None,
132
131
  response_param_kwargs: dict = {},
133
132
  **kwargs,
134
133
  ) -> list | BaseModel | None | dict | str:
@@ -269,7 +268,7 @@ class BranchOperationMixin(ABC):
269
268
  request_fields=None,
270
269
  request_model: type[BaseModel] = None,
271
270
  progress=None,
272
- imodel: iModel | LiteiModel = None,
271
+ imodel: iModel = None,
273
272
  tool_schemas=None,
274
273
  images: list = None,
275
274
  image_detail: Literal["low", "high", "auto"] = None,
@@ -288,14 +287,42 @@ class BranchOperationMixin(ABC):
288
287
  image_detail=image_detail,
289
288
  tool_schemas=tool_schemas,
290
289
  )
291
- kwargs["messages"] = self.msgs.to_chat_msgs(progress)
292
- kwargs["messages"].append(ins.chat_msg)
293
290
 
291
+ progress = progress or self.msgs.progress
292
+ messages = [self.msgs.messages[i] for i in progress]
293
+
294
+ if self.msgs.system and "system" not in imodel.allowed_roles:
295
+ messages = [msg for msg in messages if msg.role != "system"]
296
+ first_instruction = None
297
+
298
+ if len(messages) == 0:
299
+ first_instruction = ins.model_copy()
300
+ first_instruction.guidance = self.msgs.system.system_info + (
301
+ first_instruction.guidance or ""
302
+ )
303
+ messages.append(first_instruction)
304
+ elif len(messages) >= 1:
305
+ first_instruction = messages[0]
306
+ if not isinstance(first_instruction, Instruction):
307
+ raise ValueError(
308
+ "First message in progress must be an Instruction or System"
309
+ )
310
+ first_instruction = first_instruction.model_copy()
311
+ first_instruction.guidance = self.msgs.system.system_info + (
312
+ first_instruction.guidance or ""
313
+ )
314
+ messages[0] = first_instruction
315
+
316
+ else:
317
+ messages.append(ins)
318
+
319
+ kwargs["messages"] = [i.chat_msg for i in messages]
294
320
  imodel = imodel or self.imodel
295
321
  api_response = None
296
- if isinstance(imodel, LiteiModel):
322
+
323
+ if not hasattr(imodel, "parse_to_data_model"):
297
324
  api_response = await imodel.invoke(**kwargs)
298
- elif isinstance(imodel, iModel):
325
+ else:
299
326
  data_model = imodel.parse_to_data_model(**kwargs)
300
327
  api_response = await imodel.invoke(**data_model)
301
328
 
@@ -316,12 +343,12 @@ class BranchOperationMixin(ABC):
316
343
  progress: ID.IDSeq = None,
317
344
  request_model: type[BaseModel] | BaseModel = None,
318
345
  request_fields: dict | list[str] = None,
319
- imodel: iModel | LiteiModel = None,
346
+ imodel: iModel = None,
320
347
  images: list = None,
321
348
  image_detail: Literal["low", "high", "auto"] = None,
322
349
  tools: str | FUNCTOOL | list[FUNCTOOL | str] | bool = None,
323
350
  num_parse_retries: int = 0,
324
- retry_imodel: iModel | LiteiModel = None,
351
+ retry_imodel: iModel = None,
325
352
  retry_kwargs: dict = {},
326
353
  handle_validation: Literal[
327
354
  "raise", "return_value", "return_none"
@@ -329,12 +356,22 @@ class BranchOperationMixin(ABC):
329
356
  skip_validation: bool = False,
330
357
  clear_messages: bool = False,
331
358
  invoke_action: bool = True,
359
+ response_format: (
360
+ type[BaseModel] | BaseModel
361
+ ) = None, # alias of request_model
332
362
  **kwargs,
333
363
  ):
364
+ if response_format and request_model:
365
+ raise ValueError(
366
+ "Cannot specify both response_format and request_model"
367
+ "as they are aliases for the same parameter."
368
+ )
369
+ request_model = request_model or response_format
370
+
334
371
  imodel = imodel or self.imodel
335
372
  retry_imodel = retry_imodel or imodel
336
373
  if clear_messages:
337
- self.clear_messages()
374
+ self.msgs.clear_messages()
338
375
 
339
376
  if num_parse_retries > 5:
340
377
  logging.warning(
@@ -8,7 +8,6 @@ import pandas as pd
8
8
 
9
9
  from lionagi.core.generic.types import Component, Pile, Progression
10
10
  from lionagi.core.typing import ID, Field, ItemNotFoundError, JsonValue
11
- from lionagi.integrations.litellm_.imodel import LiteiModel
12
11
  from lionagi.libs.parse import to_list
13
12
  from lionagi.service import iModel
14
13
 
@@ -39,7 +38,7 @@ class Session(Component):
39
38
  system_datetime: bool | str = None,
40
39
  user: ID.SenderRecipient = None,
41
40
  name: str | None = None,
42
- imodel: iModel | LiteiModel | None = None,
41
+ imodel: iModel | None = None,
43
42
  messages: Pile[RoledMessage] = None,
44
43
  progress: Progression = None,
45
44
  tool_manager: ActionManager = None,
@@ -3,7 +3,7 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
  from ..models import *
6
- from .concepts import *
7
- from .id import *
8
- from .pydantic_ import *
9
- from .typing_ import *
6
+ from ._concepts import *
7
+ from ._id import *
8
+ from ._pydantic import *
9
+ from ._typing import *
@@ -2,19 +2,59 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ from __future__ import annotations
6
+
7
+ import uuid
5
8
  from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass
6
10
  from typing import Any, Generic, TypeVar
7
11
 
12
+ from lionagi.settings import Settings
13
+
14
+
15
+ class IDType:
16
+
17
+ def __init__(self, _id: uuid.UUID):
18
+ self._id = _id
19
+
20
+ @classmethod
21
+ def validate(cls, value) -> IDType:
22
+ if isinstance(value, cls):
23
+ return value
24
+ if isinstance(value, str | int):
25
+ try:
26
+ _id = uuid.UUID(value, version=Settings.Config.UUID_VERSION)
27
+ return cls(_id=_id)
28
+ except Exception as e:
29
+ raise ValueError(f"Invalid IDType value: {value}") from e
30
+ raise ValueError(f"Invalid IDType value: {value}")
31
+
32
+ def __str__(self):
33
+ return str(self._id)
34
+
35
+ def __repr__(self):
36
+ return f"{self.__class__.__name__}({self._id})"
37
+
38
+ def __hash__(self):
39
+ return hash(self._id)
40
+
41
+ def __eq__(self, other):
42
+ if not isinstance(other, IDType):
43
+ return False
44
+ return self._id == other._id
45
+
46
+ __slots__ = ("_id",)
47
+
8
48
 
9
49
  class Observable(ABC):
10
50
  """
11
51
  Abstract base class for objects that can be uniquely identified and tracked.
12
52
 
13
- All Observable objects must have a unique identifier (ln_id) that allows them
53
+ All Observable objects must have a unique identifier (id) that allows them
14
54
  to be tracked and referenced within the Lion system.
15
55
  """
16
56
 
17
- ln_id: str
57
+ ln_id: IDType
18
58
 
19
59
 
20
60
  T = TypeVar("T", bound=Observable)
@@ -129,4 +169,5 @@ __all__ = [
129
169
  "Condition",
130
170
  "Structure",
131
171
  "RelationError",
172
+ "IDType",
132
173
  ]
@@ -0,0 +1,104 @@
1
+ # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import uuid
8
+
9
+ from lionagi.settings import Settings
10
+
11
+ from ._concepts import (
12
+ Container,
13
+ Generic,
14
+ IDType,
15
+ ItemError,
16
+ Observable,
17
+ Ordering,
18
+ T,
19
+ )
20
+ from ._typing import Container, Mapping, Sequence, TypeAlias
21
+
22
+
23
+ class IDError(ItemError):
24
+ """Exception raised when an item does not have a Lion ID."""
25
+
26
+ def __init__(
27
+ self,
28
+ message: str = "Item must contain a Lion ID.",
29
+ item_id: str | None = None,
30
+ ):
31
+ super().__init__(message, item_id)
32
+
33
+
34
+ class ID(Generic[T]):
35
+ """
36
+ A generic class that provides ID-related functionality for Observable objects.
37
+
38
+ This class handles the generation, validation, and management of unique identifiers
39
+ within the Lion system. It provides type aliases for various ID-related operations
40
+ and methods for working with IDs.
41
+ """
42
+
43
+ # For functions that accept either ID or item
44
+ Ref: TypeAlias = IDType | T # type: ignore
45
+
46
+ # For functions requiring just the ID
47
+ ID: TypeAlias = IDType
48
+
49
+ # For functions requiring Observable object
50
+ Item = T # type: ignore
51
+
52
+ # For collections
53
+ IDSeq: TypeAlias = list[IDType] | Ordering
54
+ ItemSeq: TypeAlias = ( # type: ignore
55
+ Sequence[T] | Mapping[IDType, T] | Container[IDType | T]
56
+ )
57
+ RefSeq: TypeAlias = IDSeq | ItemSeq
58
+
59
+ # For system-level interactions
60
+ SenderRecipient: TypeAlias = IDType | T | str # type: ignore
61
+
62
+ @staticmethod
63
+ def id():
64
+ return IDType(
65
+ _id=getattr(uuid, f"uuid{Settings.Config.UUID_VERSION}")()
66
+ )
67
+
68
+ @staticmethod
69
+ def get_id(item, /) -> IDType:
70
+
71
+ if isinstance(item, Observable):
72
+ return item.ln_id
73
+ try:
74
+ return IDType.validate(item)
75
+ except ValueError as e:
76
+ raise IDError(
77
+ f"The input object of type <{type(item).__name__}> does "
78
+ "not contain or is not a valid Lion ID. Item must be an instance"
79
+ " of `Observable` or a valid `id`."
80
+ ) from e
81
+
82
+ @staticmethod
83
+ def is_id(item, /) -> bool:
84
+ """
85
+ Check if an item is or contains a valid Lion ID.
86
+
87
+ Args:
88
+ item: The item to check.
89
+ config: Configuration dictionary for ID validation.
90
+
91
+ Returns:
92
+ True if the item is a valid Lion ID, False otherwise.
93
+ """
94
+ try:
95
+ ID.get_id(item)
96
+ return True
97
+ except IDError:
98
+ return False
99
+
100
+
101
+ __all__ = [
102
+ "ID",
103
+ "IDError",
104
+ ]