lionagi 0.17.4__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 (44) hide show
  1. lionagi/__init__.py +45 -7
  2. lionagi/config.py +26 -0
  3. lionagi/fields/action.py +5 -3
  4. lionagi/libs/file/chunk.py +3 -14
  5. lionagi/libs/file/process.py +10 -92
  6. lionagi/libs/schema/breakdown_pydantic_annotation.py +45 -0
  7. lionagi/ln/_async_call.py +19 -8
  8. lionagi/ln/_hash.py +12 -2
  9. lionagi/ln/_to_list.py +23 -12
  10. lionagi/ln/fuzzy/_fuzzy_match.py +3 -6
  11. lionagi/ln/fuzzy/_fuzzy_validate.py +9 -8
  12. lionagi/ln/fuzzy/_string_similarity.py +11 -5
  13. lionagi/ln/fuzzy/_to_dict.py +19 -19
  14. lionagi/ln/types.py +15 -0
  15. lionagi/operations/operate/operate.py +7 -11
  16. lionagi/operations/parse/parse.py +5 -3
  17. lionagi/protocols/generic/element.py +3 -6
  18. lionagi/protocols/generic/event.py +1 -1
  19. lionagi/protocols/mail/package.py +2 -2
  20. lionagi/protocols/messages/instruction.py +9 -1
  21. lionagi/protocols/operatives/operative.py +4 -3
  22. lionagi/service/broadcaster.py +61 -0
  23. lionagi/service/connections/api_calling.py +22 -145
  24. lionagi/service/connections/mcp/wrapper.py +8 -15
  25. lionagi/service/hooks/__init__.py +2 -10
  26. lionagi/service/hooks/_types.py +1 -0
  27. lionagi/service/hooks/hooked_event.py +142 -0
  28. lionagi/service/imodel.py +2 -2
  29. lionagi/session/branch.py +46 -169
  30. lionagi/session/session.py +1 -44
  31. lionagi/tools/file/reader.py +6 -4
  32. lionagi/utils.py +3 -342
  33. lionagi/version.py +1 -1
  34. {lionagi-0.17.4.dist-info → lionagi-0.17.6.dist-info}/METADATA +4 -4
  35. {lionagi-0.17.4.dist-info → lionagi-0.17.6.dist-info}/RECORD +37 -41
  36. lionagi/libs/file/_utils.py +0 -10
  37. lionagi/libs/file/concat.py +0 -121
  38. lionagi/libs/file/concat_files.py +0 -85
  39. lionagi/libs/file/file_ops.py +0 -118
  40. lionagi/libs/file/save.py +0 -103
  41. lionagi/ln/concurrency/throttle.py +0 -83
  42. lionagi/settings.py +0 -71
  43. {lionagi-0.17.4.dist-info → lionagi-0.17.6.dist-info}/WHEEL +0 -0
  44. {lionagi-0.17.4.dist-info → lionagi-0.17.6.dist-info}/licenses/LICENSE +0 -0
@@ -9,10 +9,6 @@ from typing import Any, Literal
9
9
 
10
10
  from ._fuzzy_json import fuzzy_json
11
11
 
12
- # ----------------------------
13
- # Helpers (small, tight, local)
14
- # ----------------------------
15
-
16
12
 
17
13
  def _is_na(obj: Any) -> bool:
18
14
  """None / Pydantic undefined sentinels -> treat as NA."""
@@ -67,7 +63,7 @@ def _parse_str(
67
63
  def _object_to_mapping_like(
68
64
  obj: Any,
69
65
  *,
70
- use_model_dump: bool,
66
+ prioritize_model_dump: bool = True,
71
67
  **kwargs: Any,
72
68
  ) -> Mapping | dict | Any:
73
69
  """
@@ -80,11 +76,11 @@ def _object_to_mapping_like(
80
76
  5) dict(obj)
81
77
  """
82
78
  # 1) Pydantic v2
83
- if use_model_dump and hasattr(obj, "model_dump"):
79
+ if prioritize_model_dump and hasattr(obj, "model_dump"):
84
80
  return obj.model_dump(**kwargs)
85
81
 
86
82
  # 2) Common methods
87
- for name in ("to_dict", "dict", "to_json", "json"):
83
+ for name in ("to_dict", "dict", "to_json", "json", "model_dump"):
88
84
  if hasattr(obj, name):
89
85
  res = getattr(obj, name)(**kwargs)
90
86
  return json.loads(res) if isinstance(res, str) else res
@@ -118,7 +114,7 @@ def _preprocess_recursive(
118
114
  max_depth: int,
119
115
  recursive_custom_types: bool,
120
116
  str_parse_opts: dict[str, Any],
121
- use_model_dump: bool,
117
+ prioritize_model_dump: bool,
122
118
  ) -> Any:
123
119
  """
124
120
  Recursively process nested structures:
@@ -145,7 +141,7 @@ def _preprocess_recursive(
145
141
  max_depth=max_depth,
146
142
  recursive_custom_types=recursive_custom_types,
147
143
  str_parse_opts=str_parse_opts,
148
- use_model_dump=use_model_dump,
144
+ prioritize_model_dump=prioritize_model_dump,
149
145
  )
150
146
 
151
147
  # Dict-like
@@ -158,7 +154,7 @@ def _preprocess_recursive(
158
154
  max_depth=max_depth,
159
155
  recursive_custom_types=recursive_custom_types,
160
156
  str_parse_opts=str_parse_opts,
161
- use_model_dump=use_model_dump,
157
+ prioritize_model_dump=prioritize_model_dump,
162
158
  )
163
159
  for k, v in obj.items()
164
160
  }
@@ -172,7 +168,7 @@ def _preprocess_recursive(
172
168
  max_depth=max_depth,
173
169
  recursive_custom_types=recursive_custom_types,
174
170
  str_parse_opts=str_parse_opts,
175
- use_model_dump=use_model_dump,
171
+ prioritize_model_dump=prioritize_model_dump,
176
172
  )
177
173
  for v in obj
178
174
  ]
@@ -198,7 +194,7 @@ def _preprocess_recursive(
198
194
  max_depth=max_depth,
199
195
  recursive_custom_types=recursive_custom_types,
200
196
  str_parse_opts=str_parse_opts,
201
- use_model_dump=use_model_dump,
197
+ prioritize_model_dump=prioritize_model_dump,
202
198
  )
203
199
  except Exception:
204
200
  return obj
@@ -207,7 +203,7 @@ def _preprocess_recursive(
207
203
  if recursive_custom_types:
208
204
  with contextlib.suppress(Exception):
209
205
  mapped = _object_to_mapping_like(
210
- obj, use_model_dump=use_model_dump
206
+ obj, prioritize_model_dump=prioritize_model_dump
211
207
  )
212
208
  return _preprocess_recursive(
213
209
  mapped,
@@ -215,7 +211,7 @@ def _preprocess_recursive(
215
211
  max_depth=max_depth,
216
212
  recursive_custom_types=recursive_custom_types,
217
213
  str_parse_opts=str_parse_opts,
218
- use_model_dump=use_model_dump,
214
+ prioritize_model_dump=prioritize_model_dump,
219
215
  )
220
216
 
221
217
  return obj
@@ -232,7 +228,7 @@ def _convert_top_level_to_dict(
232
228
  fuzzy_parse: bool,
233
229
  str_type: Literal["json", "xml"] | None,
234
230
  parser: Callable[[str], Any] | None,
235
- use_model_dump: bool,
231
+ prioritize_model_dump: bool,
236
232
  use_enum_values: bool,
237
233
  **kwargs: Any,
238
234
  ) -> dict[str, Any]:
@@ -273,7 +269,7 @@ def _convert_top_level_to_dict(
273
269
  # faithfully following your previous "non-Sequence -> model path" behavior.
274
270
  if not isinstance(obj, Sequence):
275
271
  converted = _object_to_mapping_like(
276
- obj, use_model_dump=use_model_dump, **kwargs
272
+ obj, prioritize_model_dump=prioritize_model_dump, **kwargs
277
273
  )
278
274
  # If conversion returned a string, try to parse JSON to mapping; else pass-through
279
275
  if isinstance(converted, str):
@@ -321,7 +317,7 @@ def to_dict(
321
317
  input_: Any,
322
318
  /,
323
319
  *,
324
- use_model_dump: bool = True,
320
+ prioritize_model_dump: bool = True,
325
321
  fuzzy_parse: bool = False,
326
322
  suppress: bool = False,
327
323
  str_type: Literal["json", "xml"] | None = "json",
@@ -330,12 +326,16 @@ def to_dict(
330
326
  max_recursive_depth: int | None = None,
331
327
  recursive_python_only: bool = True,
332
328
  use_enum_values: bool = False,
329
+ use_model_dump: bool | None = None, # deprecated
333
330
  **kwargs: Any,
334
331
  ) -> dict[str, Any]:
335
332
  """
336
333
  Convert various input types to a dictionary, with optional recursive processing.
337
334
  Semantics preserved from original implementation.
338
335
  """
336
+ if use_model_dump is not None:
337
+ prioritize_model_dump = use_model_dump
338
+
339
339
  try:
340
340
  # Clamp recursion depth (match your constraints)
341
341
  if not isinstance(max_recursive_depth, int):
@@ -368,7 +368,7 @@ def to_dict(
368
368
  max_depth=max_depth,
369
369
  recursive_custom_types=not recursive_python_only,
370
370
  str_parse_opts=str_parse_opts,
371
- use_model_dump=use_model_dump,
371
+ prioritize_model_dump=prioritize_model_dump,
372
372
  )
373
373
 
374
374
  # Final top-level conversion
@@ -377,7 +377,7 @@ def to_dict(
377
377
  fuzzy_parse=fuzzy_parse,
378
378
  str_type=str_type,
379
379
  parser=parser,
380
- use_model_dump=use_model_dump,
380
+ prioritize_model_dump=prioritize_model_dump,
381
381
  use_enum_values=use_enum_values,
382
382
  **kwargs,
383
383
  )
lionagi/ln/types.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Sequence
3
4
  from dataclasses import dataclass, field
4
5
  from enum import Enum as _Enum
5
6
  from typing import Any, ClassVar, Final, Literal, TypeVar, Union
@@ -22,6 +23,7 @@ __all__ = (
22
23
  "not_sentinel",
23
24
  "Params",
24
25
  "DataClass",
26
+ "KeysLike",
25
27
  )
26
28
 
27
29
  T = TypeVar("T")
@@ -236,6 +238,16 @@ class Params:
236
238
  data[k] = v
237
239
  return data
238
240
 
241
+ def __hash__(self) -> int:
242
+ from ._hash import hash_dict
243
+
244
+ return hash_dict(self.to_dict())
245
+
246
+ def __eq__(self, other: Any) -> bool:
247
+ if not isinstance(other, Params):
248
+ return False
249
+ return hash(self) == hash(other)
250
+
239
251
 
240
252
  @dataclass(slots=True)
241
253
  class DataClass:
@@ -297,3 +309,6 @@ class DataClass:
297
309
  if value is None and cls._none_as_sentinel:
298
310
  return True
299
311
  return is_sentinel(value)
312
+
313
+
314
+ KeysLike = Sequence[str] | KeysDict
@@ -12,6 +12,7 @@ from lionagi.models import FieldModel, ModelParams
12
12
  from lionagi.protocols.operatives.step import Operative, Step
13
13
  from lionagi.protocols.types import Instruction, Progression, SenderRecipient
14
14
  from lionagi.service.imodel import iModel
15
+ from lionagi.session.branch import AlcallParams
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from lionagi.session.branch import Branch, ToolRef
@@ -64,10 +65,8 @@ async def operate(
64
65
  return_operative: bool = False,
65
66
  actions: bool = False,
66
67
  reason: bool = False,
67
- action_kwargs: dict = None,
68
- action_strategy: Literal[
69
- "sequential", "concurrent", "batch"
70
- ] = "concurrent",
68
+ call_params: AlcallParams = None,
69
+ action_strategy: Literal["sequential", "concurrent"] = "concurrent",
71
70
  verbose_action: bool = False,
72
71
  field_models: list[FieldModel] = None,
73
72
  exclude_fields: list | dict | None = None,
@@ -191,17 +190,14 @@ async def operate(
191
190
  getattr(response_model, "action_required", None) is True
192
191
  and getattr(response_model, "action_requests", None) is not None
193
192
  ):
194
- action_kwargs = action_kwargs or {}
195
- action_kwargs["strategy"] = (
196
- instruct.action_strategy
197
- if instruct.action_strategy
198
- else action_kwargs.get("strategy", "concurrent")
193
+ action_strategy = (
194
+ action_strategy or instruct.action_strategy or "concurrent"
199
195
  )
200
-
201
196
  action_response_models = await branch.act(
202
197
  response_model.action_requests,
198
+ strategy=action_strategy,
203
199
  verbose_action=verbose_action,
204
- **action_kwargs,
200
+ call_params=call_params,
205
201
  )
206
202
  # Possibly refine the operative with the tool outputs
207
203
  operative = Step.respond_operative(
@@ -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."""
@@ -85,20 +69,16 @@ class APICalling(Event):
85
69
  TOKEN_LIMITS = {
86
70
  # OpenAI models
87
71
  "gpt-4": 128_000,
88
- "gpt-4-turbo": 128_000,
89
- "o1-mini": 128_000,
90
- "o1-preview": 128_000,
91
72
  "o1": 200_000,
92
73
  "o3": 200_000,
93
74
  "gpt-4.1": 1_000_000,
75
+ "gpt-5": 1_000_000,
94
76
  # Anthropic models
95
77
  "sonnet": 200_000,
96
78
  "haiku": 200_000,
97
79
  "opus": 200_000,
98
80
  # Google models
99
81
  "gemini": 1_000_000,
100
- # Alibaba models
101
- "qwen-turbo": 1_000_000,
102
82
  }
103
83
 
104
84
  token_msg = (
@@ -131,12 +111,14 @@ class APICalling(Event):
131
111
  @property
132
112
  def required_tokens(self) -> int | None:
133
113
  """Calculate the number of tokens required for this request."""
114
+ from lionagi.service.token_calculator import TokenCalculator
115
+
134
116
  if not self.endpoint.config.requires_tokens:
135
117
  return None
136
118
 
137
119
  # Handle chat completions format
138
120
  if "messages" in self.payload:
139
- return _get_token_calculator().calculate_message_tokens(
121
+ return TokenCalculator.calculate_message_tokens(
140
122
  self.payload["messages"], **self.payload
141
123
  )
142
124
  # Handle responses API format
@@ -157,95 +139,29 @@ class APICalling(Event):
157
139
  messages.append(item)
158
140
  else:
159
141
  return None
160
- return _get_token_calculator().calculate_message_tokens(
142
+ return TokenCalculator.calculate_message_tokens(
161
143
  messages, **self.payload
162
144
  )
163
145
  # Handle embeddings endpoint
164
146
  elif "embed" in self.endpoint.config.endpoint:
165
- return _get_token_calculator().calculate_embed_token(
166
- **self.payload
167
- )
147
+ return TokenCalculator.calculate_embed_token(**self.payload)
168
148
 
169
149
  return None
170
150
 
171
- async def invoke(self) -> None:
172
- """Execute the API call through the endpoint.
173
-
174
- Updates execution status and stores the response or error.
175
- """
176
- start = asyncio.get_event_loop().time()
177
-
178
- try:
179
- self.execution.status = EventStatus.PROCESSING
180
- if h_ev := self._pre_invoke_hook_event:
181
- await h_ev.invoke()
182
- if h_ev._should_exit:
183
- raise h_ev._exit_cause or RuntimeError(
184
- "Pre-invocation hook requested exit without a cause"
185
- )
186
- await global_hook_logger.alog(Log.create(h_ev))
187
-
188
- # Make the API call with skip_payload_creation=True since payload is already prepared
189
- response = await self.endpoint.call(
190
- request=self.payload,
191
- cache_control=self.cache_control,
192
- skip_payload_creation=True,
193
- extra_headers=self.headers if self.headers else None,
194
- )
195
-
196
- if h_ev := self._post_invoke_hook_event:
197
- await h_ev.invoke()
198
- if h_ev._should_exit:
199
- raise h_ev._exit_cause or RuntimeError(
200
- "Post-invocation hook requested exit without a cause"
201
- )
202
- await global_hook_logger.alog(Log.create(h_ev))
203
-
204
- self.execution.response = response
205
- self.execution.status = EventStatus.COMPLETED
206
-
207
- except get_cancelled_exc_class():
208
- self.execution.error = "API call cancelled"
209
- self.execution.status = EventStatus.CANCELLED
210
- raise
211
-
212
- except Exception as e:
213
- self.execution.error = str(e)
214
- self.execution.status = EventStatus.FAILED
215
- logger.error(f"API call failed: {e}")
216
-
217
- finally:
218
- self.execution.duration = asyncio.get_event_loop().time() - start
219
-
220
- async def stream(self):
221
- """Stream the API response through the endpoint.
222
-
223
- Yields:
224
- Streaming chunks from the API.
225
- """
226
- start = asyncio.get_event_loop().time()
227
- response = []
228
-
229
- try:
230
- self.execution.status = EventStatus.PROCESSING
231
-
232
- async for chunk in self.endpoint.stream(
233
- request=self.payload,
234
- extra_headers=self.headers if self.headers else None,
235
- ):
236
- response.append(chunk)
237
- yield chunk
238
-
239
- self.execution.response = response
240
- self.execution.status = EventStatus.COMPLETED
241
-
242
- except Exception as e:
243
- self.execution.error = str(e)
244
- self.execution.status = EventStatus.FAILED
245
- 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
+ )
246
158
 
247
- finally:
248
- 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
249
165
 
250
166
  @property
251
167
  def request(self) -> dict:
@@ -253,42 +169,3 @@ class APICalling(Event):
253
169
  return {
254
170
  "required_tokens": self.required_tokens,
255
171
  }
256
-
257
- @property
258
- def response(self):
259
- """Get the response from the execution."""
260
- return self.execution.response if self.execution else None
261
-
262
- def create_pre_invoke_hook(
263
- self,
264
- hook_registry,
265
- exit_hook: bool = None,
266
- hook_timeout: float = 30.0,
267
- hook_params: dict = None,
268
- ):
269
- h_ev = HookEvent(
270
- hook_type=HookEventTypes.PreInvokation,
271
- event_like=self,
272
- registry=hook_registry,
273
- exit=exit_hook,
274
- timeout=hook_timeout,
275
- params=hook_params or {},
276
- )
277
- self._pre_invoke_hook_event = h_ev
278
-
279
- def create_post_invoke_hook(
280
- self,
281
- hook_registry,
282
- exit_hook: bool = None,
283
- hook_timeout: float = 30.0,
284
- hook_params: dict = None,
285
- ):
286
- h_ev = HookEvent(
287
- hook_type=HookEventTypes.PostInvokation,
288
- event_like=self,
289
- registry=hook_registry,
290
- exit=exit_hook,
291
- timeout=hook_timeout,
292
- params=hook_params or {},
293
- )
294
- self._post_invoke_hook_event = h_ev