lionagi 0.17.5__py3-none-any.whl → 0.17.6__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 (40) hide show
  1. lionagi/config.py +26 -0
  2. lionagi/fields/action.py +5 -3
  3. lionagi/libs/file/chunk.py +3 -14
  4. lionagi/libs/file/process.py +10 -92
  5. lionagi/libs/schema/breakdown_pydantic_annotation.py +45 -0
  6. lionagi/ln/_async_call.py +6 -6
  7. lionagi/ln/fuzzy/_fuzzy_match.py +3 -6
  8. lionagi/ln/fuzzy/_fuzzy_validate.py +3 -4
  9. lionagi/ln/fuzzy/_string_similarity.py +11 -5
  10. lionagi/ln/fuzzy/_to_dict.py +19 -19
  11. lionagi/ln/types.py +15 -0
  12. lionagi/operations/operate/operate.py +7 -11
  13. lionagi/operations/parse/parse.py +5 -3
  14. lionagi/protocols/generic/element.py +3 -6
  15. lionagi/protocols/generic/event.py +1 -1
  16. lionagi/protocols/mail/package.py +2 -2
  17. lionagi/protocols/messages/instruction.py +9 -1
  18. lionagi/protocols/operatives/operative.py +4 -3
  19. lionagi/service/broadcaster.py +61 -0
  20. lionagi/service/connections/api_calling.py +21 -140
  21. lionagi/service/hooks/__init__.py +2 -10
  22. lionagi/service/hooks/_types.py +1 -0
  23. lionagi/service/hooks/hooked_event.py +142 -0
  24. lionagi/service/imodel.py +2 -2
  25. lionagi/session/branch.py +46 -169
  26. lionagi/session/session.py +1 -44
  27. lionagi/tools/file/reader.py +6 -4
  28. lionagi/utils.py +3 -334
  29. lionagi/version.py +1 -1
  30. {lionagi-0.17.5.dist-info → lionagi-0.17.6.dist-info}/METADATA +2 -2
  31. {lionagi-0.17.5.dist-info → lionagi-0.17.6.dist-info}/RECORD +33 -37
  32. lionagi/libs/file/_utils.py +0 -10
  33. lionagi/libs/file/concat.py +0 -121
  34. lionagi/libs/file/concat_files.py +0 -85
  35. lionagi/libs/file/file_ops.py +0 -118
  36. lionagi/libs/file/save.py +0 -103
  37. lionagi/ln/concurrency/throttle.py +0 -83
  38. lionagi/settings.py +0 -71
  39. {lionagi-0.17.5.dist-info → lionagi-0.17.6.dist-info}/WHEEL +0 -0
  40. {lionagi-0.17.5.dist-info → lionagi-0.17.6.dist-info}/licenses/LICENSE +0 -0
@@ -6,9 +6,6 @@ from typing import TYPE_CHECKING, Any, Literal
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
- from lionagi.ln.fuzzy._fuzzy_validate import fuzzy_validate_mapping
10
- from lionagi.utils import breakdown_pydantic_annotation
11
-
12
9
  if TYPE_CHECKING:
13
10
  from lionagi.session.branch import Branch
14
11
 
@@ -34,6 +31,11 @@ async def parse(
34
31
  suppress_conversion_errors: bool = False,
35
32
  response_format=None,
36
33
  ):
34
+ from lionagi.libs.schema.breakdown_pydantic_annotation import (
35
+ breakdown_pydantic_annotation,
36
+ )
37
+ from lionagi.ln.fuzzy._fuzzy_validate import fuzzy_validate_mapping
38
+
37
39
  if operative is not None:
38
40
  max_retries = operative.max_retries
39
41
  response_format = operative.request_type or response_format
@@ -21,8 +21,7 @@ from pydantic import (
21
21
  from lionagi import ln
22
22
  from lionagi._class_registry import get_class
23
23
  from lionagi._errors import IDError
24
- from lionagi.settings import Settings
25
- from lionagi.utils import import_module, time, to_dict
24
+ from lionagi.utils import import_module, to_dict
26
25
 
27
26
  from .._concepts import Collective, Observable, Ordering
28
27
 
@@ -156,9 +155,7 @@ class Element(BaseModel, Observable):
156
155
  frozen=True,
157
156
  )
158
157
  created_at: float = Field(
159
- default_factory=lambda: time(
160
- tz=Settings.Config.TIMEZONE, type_="timestamp"
161
- ),
158
+ default_factory=lambda: ln.now_utc().timestamp(),
162
159
  title="Creation Timestamp",
163
160
  description="Timestamp of element creation.",
164
161
  frozen=True,
@@ -205,7 +202,7 @@ class Element(BaseModel, Observable):
205
202
  ValueError: If `val` cannot be converted to a float timestamp.
206
203
  """
207
204
  if val is None:
208
- return time(tz=Settings.Config.TIMEZONE, type_="timestamp")
205
+ return ln.now_utc().timestamp()
209
206
  if isinstance(val, float):
210
207
  return val
211
208
  if isinstance(val, dt.datetime):
@@ -138,7 +138,7 @@ class Event(Element):
138
138
  """
139
139
 
140
140
  execution: Execution = Field(default_factory=Execution)
141
- streaming: bool = False
141
+ streaming: bool = Field(False, exclude=True)
142
142
 
143
143
  @field_serializer("execution")
144
144
  def _serialize_execution(self, val: Execution) -> dict:
@@ -5,8 +5,8 @@
5
5
  from enum import Enum
6
6
  from typing import Any
7
7
 
8
+ from lionagi.ln import now_utc
8
9
  from lionagi.protocols.generic.element import ID, IDType
9
- from lionagi.utils import time
10
10
 
11
11
  from .._concepts import Communicatable, Observable
12
12
 
@@ -93,7 +93,7 @@ class Package(Observable):
93
93
  ):
94
94
  super().__init__()
95
95
  self.id = IDType.create()
96
- self.created_at = time(type_="timestamp")
96
+ self.created_at = now_utc().timestamp()
97
97
  self.category = validate_category(category)
98
98
  self.item = item
99
99
  self.request_source = request_source
@@ -7,7 +7,7 @@ from typing import Any, Literal
7
7
  from pydantic import BaseModel, JsonValue, field_serializer
8
8
  from typing_extensions import override
9
9
 
10
- from lionagi.utils import UNDEFINED, breakdown_pydantic_annotation, copy
10
+ from lionagi.utils import UNDEFINED, copy
11
11
 
12
12
  from .base import MessageRole
13
13
  from .message import RoledMessage, SenderRecipient
@@ -256,6 +256,10 @@ def prepare_instruction_content(
256
256
  Raises:
257
257
  ValueError: If request_fields and request_model are both given.
258
258
  """
259
+ from lionagi.libs.schema.breakdown_pydantic_annotation import (
260
+ breakdown_pydantic_annotation,
261
+ )
262
+
259
263
  if request_fields and request_model:
260
264
  raise ValueError(
261
265
  "only one of request_fields or request_model can be provided"
@@ -476,6 +480,10 @@ class Instruction(RoledMessage):
476
480
 
477
481
  @response_format.setter
478
482
  def response_format(self, model: type[BaseModel]) -> None:
483
+ from lionagi.libs.schema.breakdown_pydantic_annotation import (
484
+ breakdown_pydantic_annotation,
485
+ )
486
+
479
487
  if isinstance(model, BaseModel):
480
488
  self.content["request_model"] = type(model)
481
489
  else:
@@ -7,9 +7,10 @@ from typing import Any
7
7
  from pydantic import BaseModel
8
8
  from pydantic.fields import FieldInfo
9
9
 
10
+ from lionagi.ln import extract_json
10
11
  from lionagi.ln.fuzzy._fuzzy_match import fuzzy_match_keys
11
12
  from lionagi.models import FieldModel, ModelParams, OperableModel
12
- from lionagi.utils import UNDEFINED, to_json
13
+ from lionagi.utils import UNDEFINED
13
14
 
14
15
 
15
16
  class Operative:
@@ -145,7 +146,7 @@ class Operative:
145
146
  Raises:
146
147
  Exception: If the validation fails.
147
148
  """
148
- d_ = to_json(text, fuzzy_parse=True)
149
+ d_ = extract_json(text, fuzzy_parse=True)
149
150
  if isinstance(d_, list | tuple) and len(d_) == 1:
150
151
  d_ = d_[0]
151
152
  try:
@@ -167,7 +168,7 @@ class Operative:
167
168
  """
168
169
  d_ = text
169
170
  try:
170
- d_ = to_json(text, fuzzy_parse=True)
171
+ d_ = extract_json(text, fuzzy_parse=True)
171
172
  if isinstance(d_, list | tuple) and len(d_) == 1:
172
173
  d_ = d_[0]
173
174
  d_ = fuzzy_match_keys(
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from collections.abc import Callable
5
+ from typing import Any, ClassVar
6
+
7
+ from lionagi.ln.concurrency.utils import is_coro_func
8
+ from lionagi.protocols.generic.event import Event
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ __all__ = ("Broadcaster",)
13
+
14
+
15
+ class Broadcaster:
16
+ """Real-time event broadcasting system for hook events. Should subclass to implement specific event types."""
17
+
18
+ _instance: ClassVar[Broadcaster | None] = None
19
+ _subscribers: ClassVar[list[Callable[[Any], None]]] = []
20
+ _event_type: ClassVar[type[Event]]
21
+
22
+ def __new__(cls):
23
+ if cls._instance is None:
24
+ cls._instance = super().__new__(cls)
25
+ return cls._instance
26
+
27
+ @classmethod
28
+ def subscribe(cls, callback: Callable[[Any], None]) -> None:
29
+ """Subscribe to hook events with sync callback."""
30
+ if callback not in cls._subscribers:
31
+ cls._subscribers.append(callback)
32
+
33
+ @classmethod
34
+ def unsubscribe(cls, callback: Callable[[Any], None]) -> None:
35
+ """Unsubscribe from hook events."""
36
+ if callback in cls._subscribers:
37
+ cls._subscribers.remove(callback)
38
+
39
+ @classmethod
40
+ async def broadcast(cls, event) -> None:
41
+ """Broadcast event to all subscribers."""
42
+ if not isinstance(event, cls._event_type):
43
+ raise ValueError(
44
+ f"Event must be of type {cls._event_type.__name__}"
45
+ )
46
+
47
+ for callback in cls._subscribers:
48
+ try:
49
+ if is_coro_func(callback):
50
+ await callback(event)
51
+ else:
52
+ callback(event)
53
+ except Exception as e:
54
+ logger.error(
55
+ f"Error in subscriber callback: {e}", exc_info=True
56
+ )
57
+
58
+ @classmethod
59
+ def get_subscriber_count(cls) -> int:
60
+ """Get total number of subscribers."""
61
+ return len(cls._subscribers)
@@ -2,31 +2,18 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- import asyncio
6
5
  import logging
7
6
 
8
- from anyio import get_cancelled_exc_class
9
- from pydantic import Field, PrivateAttr, model_validator
7
+ from pydantic import Field, model_validator
10
8
  from typing_extensions import Self
11
9
 
12
- from lionagi.protocols.generic.event import Event, EventStatus
13
- from lionagi.protocols.types import Log
14
- from lionagi.service.hooks import HookEvent, HookEventTypes, global_hook_logger
15
-
10
+ from ..hooks.hooked_event import HookedEvent
16
11
  from .endpoint import Endpoint
17
12
 
18
-
19
- # Lazy import for TokenCalculator
20
- def _get_token_calculator():
21
- from lionagi.service.token_calculator import TokenCalculator
22
-
23
- return TokenCalculator
24
-
25
-
26
13
  logger = logging.getLogger(__name__)
27
14
 
28
15
 
29
- class APICalling(Event):
16
+ class APICalling(HookedEvent):
30
17
  """Handles asynchronous API calls with automatic token usage tracking.
31
18
 
32
19
  This class manages API calls through endpoints, handling both regular
@@ -61,9 +48,6 @@ class APICalling(Event):
61
48
  exclude=True,
62
49
  )
63
50
 
64
- _pre_invoke_hook_event: HookEvent = PrivateAttr(None)
65
- _post_invoke_hook_event: HookEvent = PrivateAttr(None)
66
-
67
51
  @model_validator(mode="after")
68
52
  def _validate_streaming(self) -> Self:
69
53
  """Validate streaming configuration and add token usage if requested."""
@@ -127,12 +111,14 @@ class APICalling(Event):
127
111
  @property
128
112
  def required_tokens(self) -> int | None:
129
113
  """Calculate the number of tokens required for this request."""
114
+ from lionagi.service.token_calculator import TokenCalculator
115
+
130
116
  if not self.endpoint.config.requires_tokens:
131
117
  return None
132
118
 
133
119
  # Handle chat completions format
134
120
  if "messages" in self.payload:
135
- return _get_token_calculator().calculate_message_tokens(
121
+ return TokenCalculator.calculate_message_tokens(
136
122
  self.payload["messages"], **self.payload
137
123
  )
138
124
  # Handle responses API format
@@ -153,95 +139,29 @@ class APICalling(Event):
153
139
  messages.append(item)
154
140
  else:
155
141
  return None
156
- return _get_token_calculator().calculate_message_tokens(
142
+ return TokenCalculator.calculate_message_tokens(
157
143
  messages, **self.payload
158
144
  )
159
145
  # Handle embeddings endpoint
160
146
  elif "embed" in self.endpoint.config.endpoint:
161
- return _get_token_calculator().calculate_embed_token(
162
- **self.payload
163
- )
147
+ return TokenCalculator.calculate_embed_token(**self.payload)
164
148
 
165
149
  return None
166
150
 
167
- async def invoke(self) -> None:
168
- """Execute the API call through the endpoint.
169
-
170
- Updates execution status and stores the response or error.
171
- """
172
- start = asyncio.get_event_loop().time()
173
-
174
- try:
175
- self.execution.status = EventStatus.PROCESSING
176
- if h_ev := self._pre_invoke_hook_event:
177
- await h_ev.invoke()
178
- if h_ev._should_exit:
179
- raise h_ev._exit_cause or RuntimeError(
180
- "Pre-invocation hook requested exit without a cause"
181
- )
182
- await global_hook_logger.alog(Log.create(h_ev))
183
-
184
- # Make the API call with skip_payload_creation=True since payload is already prepared
185
- response = await self.endpoint.call(
186
- request=self.payload,
187
- cache_control=self.cache_control,
188
- skip_payload_creation=True,
189
- extra_headers=self.headers if self.headers else None,
190
- )
191
-
192
- if h_ev := self._post_invoke_hook_event:
193
- await h_ev.invoke()
194
- if h_ev._should_exit:
195
- raise h_ev._exit_cause or RuntimeError(
196
- "Post-invocation hook requested exit without a cause"
197
- )
198
- await global_hook_logger.alog(Log.create(h_ev))
199
-
200
- self.execution.response = response
201
- self.execution.status = EventStatus.COMPLETED
202
-
203
- except get_cancelled_exc_class():
204
- self.execution.error = "API call cancelled"
205
- self.execution.status = EventStatus.CANCELLED
206
- raise
207
-
208
- except Exception as e:
209
- self.execution.error = str(e)
210
- self.execution.status = EventStatus.FAILED
211
- logger.error(f"API call failed: {e}")
212
-
213
- finally:
214
- self.execution.duration = asyncio.get_event_loop().time() - start
215
-
216
- async def stream(self):
217
- """Stream the API response through the endpoint.
218
-
219
- Yields:
220
- Streaming chunks from the API.
221
- """
222
- start = asyncio.get_event_loop().time()
223
- response = []
224
-
225
- try:
226
- self.execution.status = EventStatus.PROCESSING
227
-
228
- async for chunk in self.endpoint.stream(
229
- request=self.payload,
230
- extra_headers=self.headers if self.headers else None,
231
- ):
232
- response.append(chunk)
233
- yield chunk
234
-
235
- self.execution.response = response
236
- self.execution.status = EventStatus.COMPLETED
237
-
238
- except Exception as e:
239
- self.execution.error = str(e)
240
- self.execution.status = EventStatus.FAILED
241
- logger.error(f"Streaming failed: {e}")
151
+ async def _invoke(self):
152
+ return await self.endpoint.call(
153
+ request=self.payload,
154
+ cache_control=self.cache_control,
155
+ skip_payload_creation=True,
156
+ extra_headers=self.headers if self.headers else None,
157
+ )
242
158
 
243
- finally:
244
- self.execution.duration = asyncio.get_event_loop().time() - start
159
+ async def _stream(self):
160
+ async for i in self.endpoint.stream(
161
+ request=self.payload,
162
+ extra_headers=self.headers if self.headers else None,
163
+ ):
164
+ yield i
245
165
 
246
166
  @property
247
167
  def request(self) -> dict:
@@ -249,42 +169,3 @@ class APICalling(Event):
249
169
  return {
250
170
  "required_tokens": self.required_tokens,
251
171
  }
252
-
253
- @property
254
- def response(self):
255
- """Get the response from the execution."""
256
- return self.execution.response if self.execution else None
257
-
258
- def create_pre_invoke_hook(
259
- self,
260
- hook_registry,
261
- exit_hook: bool = None,
262
- hook_timeout: float = 30.0,
263
- hook_params: dict = None,
264
- ):
265
- h_ev = HookEvent(
266
- hook_type=HookEventTypes.PreInvokation,
267
- event_like=self,
268
- registry=hook_registry,
269
- exit=exit_hook,
270
- timeout=hook_timeout,
271
- params=hook_params or {},
272
- )
273
- self._pre_invoke_hook_event = h_ev
274
-
275
- def create_post_invoke_hook(
276
- self,
277
- hook_registry,
278
- exit_hook: bool = None,
279
- hook_timeout: float = 30.0,
280
- hook_params: dict = None,
281
- ):
282
- h_ev = HookEvent(
283
- hook_type=HookEventTypes.PostInvokation,
284
- event_like=self,
285
- registry=hook_registry,
286
- exit=exit_hook,
287
- timeout=hook_timeout,
288
- params=hook_params or {},
289
- )
290
- self._post_invoke_hook_event = h_ev
@@ -1,19 +1,10 @@
1
1
  # Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
- from lionagi.protocols.types import DataLogger
5
-
6
4
  from ._types import AssosiatedEventInfo, HookDict, HookEventTypes
7
5
  from .hook_event import HookEvent
8
6
  from .hook_registry import HookRegistry
9
-
10
- global_hook_logger = DataLogger(
11
- persist_dir="./data/logs",
12
- subfolder="hooks",
13
- file_prefix="hook",
14
- capacity=1000,
15
- )
16
-
7
+ from .hooked_event import HookedEvent, global_hook_logger
17
8
 
18
9
  __all__ = (
19
10
  "HookEventTypes",
@@ -22,4 +13,5 @@ __all__ = (
22
13
  "HookEvent",
23
14
  "HookRegistry",
24
15
  "global_hook_logger",
16
+ "HookedEvent",
25
17
  )
@@ -37,6 +37,7 @@ class HookDict(TypedDict):
37
37
 
38
38
 
39
39
  StreamHandlers = dict[str, Callable[[SC], Awaitable[None]]]
40
+ """Mapping of chunk type names to their respective asynchronous handler functions."""
40
41
 
41
42
 
42
43
  class AssosiatedEventInfo(TypedDict, total=False):
@@ -0,0 +1,142 @@
1
+ # Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import anyio
6
+ from pydantic import PrivateAttr
7
+
8
+ from lionagi.ln import get_cancelled_exc_class
9
+ from lionagi.protocols.types import DataLogger, Event, EventStatus, Log
10
+ from lionagi.service.hooks import HookEvent, HookEventTypes
11
+
12
+ global_hook_logger = DataLogger(
13
+ persist_dir="./data/logs",
14
+ subfolder="hooks",
15
+ file_prefix="hook",
16
+ capacity=100,
17
+ )
18
+
19
+
20
+ class HookedEvent(Event):
21
+ """Handles asynchronous API calls with automatic token usage tracking.
22
+
23
+ This class manages API calls through endpoints, handling both regular
24
+ and streaming responses with optional token usage tracking.
25
+ """
26
+
27
+ _pre_invoke_hook_event: HookEvent = PrivateAttr(None)
28
+ _post_invoke_hook_event: HookEvent = PrivateAttr(None)
29
+
30
+ async def _stream(self):
31
+ raise NotImplementedError
32
+
33
+ async def _invoke(self):
34
+ raise NotImplementedError
35
+
36
+ async def invoke(self) -> None:
37
+ """Execute the API call through the endpoint.
38
+
39
+ Updates execution status and stores the response or error.
40
+ """
41
+ start = anyio.current_time()
42
+
43
+ try:
44
+ self.execution.status = EventStatus.PROCESSING
45
+ if h_ev := self._pre_invoke_hook_event:
46
+ await h_ev.invoke()
47
+ if h_ev._should_exit:
48
+ raise h_ev._exit_cause or RuntimeError(
49
+ "Pre-invocation hook requested exit without a cause"
50
+ )
51
+ await global_hook_logger.alog(Log.create(h_ev))
52
+
53
+ response = await self._invoke()
54
+
55
+ if h_ev := self._post_invoke_hook_event:
56
+ await h_ev.invoke()
57
+ if h_ev._should_exit:
58
+ raise h_ev._exit_cause or RuntimeError(
59
+ "Post-invocation hook requested exit without a cause"
60
+ )
61
+ await global_hook_logger.alog(Log.create(h_ev))
62
+
63
+ self.execution.response = response
64
+ self.execution.status = EventStatus.COMPLETED
65
+
66
+ except get_cancelled_exc_class():
67
+ self.execution.error = "Invocation cancelled"
68
+ self.execution.status = EventStatus.CANCELLED
69
+ raise
70
+
71
+ except Exception as e:
72
+ self.execution.error = str(e)
73
+ self.execution.status = EventStatus.FAILED
74
+
75
+ finally:
76
+ self.execution.duration = anyio.current_time() - start
77
+
78
+ async def stream(self):
79
+ """Stream the API response through the endpoint.
80
+
81
+ Yields:
82
+ Streaming chunks from the API.
83
+ """
84
+ start = anyio.current_time()
85
+
86
+ response = []
87
+
88
+ try:
89
+ self.execution.status = EventStatus.PROCESSING
90
+
91
+ async for chunk in self._stream():
92
+ response.append(chunk)
93
+ yield chunk
94
+
95
+ self.execution.response = response
96
+ self.execution.status = EventStatus.COMPLETED
97
+
98
+ except get_cancelled_exc_class():
99
+ self.execution.error = "Streaming cancelled"
100
+ self.execution.status = EventStatus.CANCELLED
101
+ raise
102
+
103
+ except Exception as e:
104
+ self.execution.error = str(e)
105
+ self.execution.status = EventStatus.FAILED
106
+
107
+ finally:
108
+ self.execution.duration = anyio.current_time() - start
109
+
110
+ def create_pre_invoke_hook(
111
+ self,
112
+ hook_registry,
113
+ exit_hook: bool = None,
114
+ hook_timeout: float = 30.0,
115
+ hook_params: dict = None,
116
+ ):
117
+ h_ev = HookEvent(
118
+ hook_type=HookEventTypes.PreInvokation,
119
+ event_like=self,
120
+ registry=hook_registry,
121
+ exit=exit_hook,
122
+ timeout=hook_timeout,
123
+ params=hook_params or {},
124
+ )
125
+ self._pre_invoke_hook_event = h_ev
126
+
127
+ def create_post_invoke_hook(
128
+ self,
129
+ hook_registry,
130
+ exit_hook: bool = None,
131
+ hook_timeout: float = 30.0,
132
+ hook_params: dict = None,
133
+ ):
134
+ h_ev = HookEvent(
135
+ hook_type=HookEventTypes.PostInvokation,
136
+ event_like=self,
137
+ registry=hook_registry,
138
+ exit=exit_hook,
139
+ timeout=hook_timeout,
140
+ params=hook_params or {},
141
+ )
142
+ self._post_invoke_hook_event = h_ev
lionagi/service/imodel.py CHANGED
@@ -7,10 +7,10 @@ from collections.abc import AsyncGenerator, Callable
7
7
 
8
8
  from pydantic import BaseModel
9
9
 
10
+ from lionagi.ln import is_coro_func, now_utc
10
11
  from lionagi.protocols.generic.log import Log
11
12
  from lionagi.protocols.types import ID, Event, EventStatus, IDType
12
13
  from lionagi.service.hooks.hook_event import HookEventTypes
13
- from lionagi.utils import is_coro_func, time
14
14
 
15
15
  from .connections.api_calling import APICalling
16
16
  from .connections.endpoint import Endpoint
@@ -106,7 +106,7 @@ class iModel:
106
106
  raise ValueError("created_at must be a float timestamp.")
107
107
  self.created_at = created_at
108
108
  else:
109
- self.created_at = time()
109
+ self.created_at = now_utc().timestamp()
110
110
 
111
111
  # 2. Configure Endpoint ---------------------------------------------
112
112
  model = kwargs.get("model", None)