xpander-sdk 2.0.332__py3-none-any.whl → 2.0.334__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.
xpander_sdk/__init__.py CHANGED
@@ -15,7 +15,7 @@ Main Components:
15
15
  For more information, visit: https://xpander.ai
16
16
  """
17
17
 
18
- __version__ = "2.0.332"
18
+ __version__ = "2.0.334"
19
19
 
20
20
  # Backend-related imports
21
21
  from .modules.backend.backend_module import Backend
@@ -259,6 +259,11 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
259
259
  default_factory=set, init=False, repr=False
260
260
  )
261
261
 
262
+ _last_emitted_status_pct: Optional[int] = field(
263
+ default=None, init=False, repr=False
264
+ )
265
+ _compacting: bool = field(default=False, init=False, repr=False)
266
+
262
267
  # In-memory cache + write queue for Layer 1 offloaded blobs. The optimizer
263
268
  # writes encrypted bytes here synchronously and the cache spawns the
264
269
  # actual workspace POST in the background; the agno tool hook short-
@@ -544,6 +549,8 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
544
549
  # Layer 1: always runs — offload large tool results to workspace
545
550
  await self.layer_1_microcompact(messages)
546
551
 
552
+ await self._publish_context_status(messages)
553
+
547
554
  # Layer 3: agent-requested manual compaction (takes priority)
548
555
  if self.compact_requested:
549
556
  focus = self.compact_focus
@@ -1046,6 +1053,78 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
1046
1053
  ),
1047
1054
  )
1048
1055
 
1056
+ async def apublish_context_status(
1057
+ self, messages: List[Message]
1058
+ ) -> "Optional[Any]":
1059
+ """Force-emit a context_status snapshot and return the payload; for mid-loop hooks (tool-call completions, sub-execution boundaries) where the caller wants to refresh the indicator without changing the compacting flag."""
1060
+ if not messages:
1061
+ return None
1062
+ return await self._publish_context_status(messages, force=True)
1063
+
1064
+ async def apublish_final_context_status(
1065
+ self, messages: List[Message]
1066
+ ) -> "Optional[Any]":
1067
+ """Force-emit a final context_status with compacting cleared; called at agno run end (RunCompleted/Cancelled/Error) so the indicator settles on post-final-message state."""
1068
+ if not messages:
1069
+ return None
1070
+ self._compacting = False
1071
+ return await self._publish_context_status(messages, force=True)
1072
+
1073
+ async def _publish_context_status(
1074
+ self,
1075
+ messages: List[Message],
1076
+ *,
1077
+ force: bool = False,
1078
+ ) -> "Optional[Any]":
1079
+ """Emit a context_status snapshot for the chat UI indicator; returns the payload (or None if unconfigured / estimate failed)."""
1080
+ if not self.agent or not self.task:
1081
+ return None
1082
+
1083
+ from xpander_sdk.models.context_status import ContextStatus
1084
+ from xpander_sdk.models.events import TaskUpdateEventType
1085
+
1086
+ try:
1087
+ estimated = self._estimate_tokens(messages)
1088
+ except Exception as exc:
1089
+ logger.debug(f"[context-optimizer] context_status estimate failed: {exc}")
1090
+ return None
1091
+
1092
+ window = max(1, int(self.context_window))
1093
+ pct = max(0.0, min(100.0, (estimated / window) * 100.0))
1094
+ self._last_emitted_status_pct = int(pct)
1095
+
1096
+ payload = ContextStatus(
1097
+ estimated_tokens=estimated,
1098
+ context_window=window,
1099
+ percent=round(pct, 1),
1100
+ auto_compact_threshold=self._auto_compact_threshold,
1101
+ emergency_threshold=self._emergency_compact_threshold,
1102
+ compacting=self._compacting,
1103
+ )
1104
+
1105
+ coro = self._push_activity_event(
1106
+ event_type=TaskUpdateEventType.ContextStatus,
1107
+ data=payload,
1108
+ )
1109
+ try:
1110
+ task = asyncio.create_task(coro)
1111
+ except RuntimeError as exc:
1112
+ logger.debug(f"[context-optimizer] context_status schedule failed: {exc}")
1113
+ coro.close()
1114
+ return payload
1115
+
1116
+ def _log_status_task_exception(t: "asyncio.Task[Any]") -> None:
1117
+ if t.cancelled():
1118
+ return
1119
+ exc = t.exception()
1120
+ if exc is not None:
1121
+ logger.debug(
1122
+ f"[context-optimizer] context_status publish failed: {exc}"
1123
+ )
1124
+
1125
+ task.add_done_callback(_log_status_task_exception)
1126
+ return payload
1127
+
1049
1128
  # Progress-emission rate-limiter state. Reset at the start of every
1050
1129
  # ``layer_2_auto_compact`` call by ``_reset_progress_state``.
1051
1130
  _progress_last_percent: float = field(default=-1.0, init=False, repr=False)
@@ -1379,6 +1458,9 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
1379
1458
  f"threshold={self._auto_compact_threshold:,})"
1380
1459
  )
1381
1460
 
1461
+ self._compacting = True
1462
+ await self._publish_context_status(messages, force=True)
1463
+
1382
1464
  # Publish start event (fire-and-forget)
1383
1465
  await self._publish_compaction_start(
1384
1466
  trigger=trigger,
@@ -1574,6 +1656,9 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
1574
1656
  reduce_phase_seconds=telemetry.get("reduce_phase_seconds"),
1575
1657
  )
1576
1658
 
1659
+ self._compacting = False
1660
+ await self._publish_context_status(messages, force=True)
1661
+
1577
1662
  ratio = (
1578
1663
  f"{(1 - post_tokens / pre_tokens) * 100:.1f}%"
1579
1664
  if pre_tokens > 0
@@ -1665,9 +1750,9 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
1665
1750
  f"(trigger={trigger}, consecutive_failures={self._auto_compact_consecutive_failures}/"
1666
1751
  f"{MAX_CONSECUTIVE_COMPACT_FAILURES}): {exc}"
1667
1752
  )
1668
- # Publish error event (fire-and-forget)
1669
1753
  await self._publish_compaction_error(trigger=trigger, error=str(exc))
1670
- # Messages are NOT modified on failure — agent continues with full context
1754
+ self._compacting = False
1755
+ await self._publish_context_status(messages, force=True)
1671
1756
 
1672
1757
  # ================================================================== #
1673
1758
  # Pre-retry helpers
@@ -0,0 +1,31 @@
1
+ from pydantic import Field
2
+
3
+ from xpander_sdk.models.shared import XPanderSharedModel
4
+
5
+
6
+ class ContextStatus(XPanderSharedModel):
7
+ """Per-turn snapshot of the LLM context window for the chat UI indicator."""
8
+
9
+ estimated_tokens: int = Field(
10
+ ...,
11
+ description="Rough token count of the current message list (post-Layer-1 microcompaction).",
12
+ )
13
+ context_window: int = Field(
14
+ ..., description="Model context window size in tokens."
15
+ )
16
+ percent: float = Field(
17
+ ...,
18
+ description="estimated_tokens / context_window * 100, clamped to [0, 100].",
19
+ )
20
+ auto_compact_threshold: int = Field(
21
+ ...,
22
+ description="Token level at which Layer 2 auto-compaction fires.",
23
+ )
24
+ emergency_threshold: int = Field(
25
+ ...,
26
+ description="Token level at which the emergency safety net (88%) fires.",
27
+ )
28
+ compacting: bool = Field(
29
+ default=False,
30
+ description="True while a Layer 2 compaction is in flight on the current turn.",
31
+ )
@@ -91,6 +91,9 @@ class TaskUpdateEventType(str, Enum):
91
91
  # task compaction
92
92
  TaskCompactization = "task_compactization"
93
93
 
94
+ # context window status (per-turn snapshot, delta-gated)
95
+ ContextStatus = "context_status"
96
+
94
97
  # agent gateway
95
98
  AgentGatewayDecision = "agent_gateway_decision"
96
99
 
@@ -604,7 +604,7 @@ async def build_agent_args(
604
604
  {
605
605
  "name": xpander_agent.name,
606
606
  "model": model,
607
- "description": xpander_agent.instructions.description,
607
+ "description": xpander_agent.description if xpander_agent.description and xpander_agent.description is not None and len(xpander_agent.description) != 0 else xpander_agent.instructions.description,
608
608
  "instructions": (
609
609
  task.instructions_override
610
610
  if task.instructions_override
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xpander-sdk
3
- Version: 2.0.332
3
+ Version: 2.0.334
4
4
  Summary: xpander.ai Backend-as-a-service for AI Agents - SDK
5
5
  Home-page: https://www.xpander.ai
6
6
  Author: xpanderAI
@@ -1,4 +1,4 @@
1
- xpander_sdk/__init__.py,sha256=dq6zgIyyOVFGKAtBvRQDC9_HrQ_l8OniBV4AGtsuGN8,3262
1
+ xpander_sdk/__init__.py,sha256=jk-W2v05q-llpoU37bZkiv-XHMf7OLVUTNj3zOPOMx4,3262
2
2
  xpander_sdk/consts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  xpander_sdk/consts/api_routes.py,sha256=n6kC105wVvycmSWfPYTAmUreHSJhf8v2rY7QvB-yhc4,2997
4
4
  xpander_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -10,7 +10,7 @@ xpander_sdk/core/context_optimizer/action_ledger.py,sha256=tEMZUHWFmqXlFv1DqwnHY
10
10
  xpander_sdk/core/context_optimizer/compact_retry_result.py,sha256=2vVvZ0NTiQS2HHobzlAqOicGbk6CYKnoSX23zrqkk_Q,553
11
11
  xpander_sdk/core/context_optimizer/completion_evidence.py,sha256=V4J0CZP7HeVlseBqaHryt5IO2UGpywPKGJGIe8elmJk,7844
12
12
  xpander_sdk/core/context_optimizer/constants.py,sha256=szWwUHbq2ol27xDFNOEza5qb1fVASkYLvrAJlAmB7p4,8579
13
- xpander_sdk/core/context_optimizer/context_optimizer.py,sha256=ypUFvmBGqfmMRXHQEp9Ar_28pXOU_pNAv8KZ_kTrj5U,94225
13
+ xpander_sdk/core/context_optimizer/context_optimizer.py,sha256=enncSRm0DlEMf6iU9qsuthiLxjU7BTfCgNEcklzaR3U,97493
14
14
  xpander_sdk/core/context_optimizer/encryption.py,sha256=CHIHGi_jjhFykZg5U84-tCqPDpmv4oQ1CHtfa_Gwa88,1914
15
15
  xpander_sdk/core/context_optimizer/error_patterns.py,sha256=BCp17HbZKgc-RvaUfLWX0npAeewgBX2RqSAmwpKcSlc,2679
16
16
  xpander_sdk/core/context_optimizer/finalize_mode.py,sha256=IztEicji7GJ6xRZjktisSnYOd8uOXz1TXajV86C1fuw,11595
@@ -31,8 +31,9 @@ xpander_sdk/models/action_ledger.py,sha256=qPnPj6_LzUN6Q5vVhWrjbGXDDOhQndKvwCH0p
31
31
  xpander_sdk/models/activity.py,sha256=pRqvJFHLLPxSA2x7M-S3h2evE0rt5Y2ueB7Q-zWrQZg,4646
32
32
  xpander_sdk/models/compactization.py,sha256=JmlAv_0uvPUqRKVuN63icKLVofZWjCp3hQvqMyofFtc,8002
33
33
  xpander_sdk/models/configuration.py,sha256=5vOgkioyj4IGFf7HRdalnkP65r6ey3r8rBUt3iFhU2U,3266
34
+ xpander_sdk/models/context_status.py,sha256=qwgAwy0PefVTsZmdI2HzY1rxH-twenWYp72aO3bk1hw,1022
34
35
  xpander_sdk/models/deep_planning.py,sha256=opB30Akv9LZ2QLyPbkCG37569eL5_QyZpQlGiGduHkQ,558
35
- xpander_sdk/models/events.py,sha256=yTnEIxyDpbJDIQrnYAEQF0PctzAJbL3CyhTILe4P7_Q,2947
36
+ xpander_sdk/models/events.py,sha256=8f-0rG_JJFRwkQYecedTH6udrpl61v2nvl9OtPJw7uU,3046
36
37
  xpander_sdk/models/frameworks.py,sha256=pmo05YINaYiQoDiUYao48pttEGrY8HUmwAl46O05PTM,2864
37
38
  xpander_sdk/models/generic.py,sha256=E4Ud94vitMCc_xfULOJ4y8b7pFHerFS24HANzzitwjo,772
38
39
  xpander_sdk/models/notifications.py,sha256=JX8wlUhWgZb-aUfsiVXINNZAK9rCn5DAwesLkYr5YAE,4135
@@ -56,7 +57,7 @@ xpander_sdk/modules/backend/events_registry.py,sha256=arlbPOwTj7DEONkLQbp2MMb8qE
56
57
  xpander_sdk/modules/backend/decorators/__init__.py,sha256=ub9c8G0Ll6AuCvfcFB6rqR8iamMJxtcW7QjWw3WSkPU,106
57
58
  xpander_sdk/modules/backend/decorators/on_auth_event.py,sha256=FwZIYD8TVqRFxex12zH5W4GOzXZjQGf5kp4XFbA7KC8,4673
58
59
  xpander_sdk/modules/backend/frameworks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- xpander_sdk/modules/backend/frameworks/agno.py,sha256=mU-dZfnBE7YTihOvXZ_b2kyXr22xvw_0uL2-14rm5pA,126243
60
+ xpander_sdk/modules/backend/frameworks/agno.py,sha256=VE7AEIblMpChVN5vCUtuOnJ5iwOL3Yngs7eSkv_lLik,126385
60
61
  xpander_sdk/modules/backend/frameworks/dispatch.py,sha256=eC35T4krqzk_rRNIGFc68TBs7URcRdKdnwUZ1nNYe4o,1957
61
62
  xpander_sdk/modules/backend/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
63
  xpander_sdk/modules/backend/utils/extra_headers.py,sha256=eYXIsHAMhBYw7RMrA_wzdxL5qAosEe-wUUkAuor0i-0,1634
@@ -113,8 +114,8 @@ xpander_sdk/utils/event_loop.py,sha256=CDiybs1SCguuynlwlYOA38-68CCqy6T3xIfjPuhzO
113
114
  xpander_sdk/utils/generic.py,sha256=OSlcWDUjRtyYYBWeg6S4uxkcINKP0_z4pt9D8efcZW8,128
114
115
  xpander_sdk/utils/tools.py,sha256=XZN5239Kid6Q1LeT7rYzFjrdDsNy6K_uhi5vYfNUUyE,1299
115
116
  xpander_sdk/utils/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
- xpander_sdk-2.0.332.dist-info/licenses/LICENSE,sha256=XfXDOGoyJbLakLShycg1ZvSvTn1yisMoGVYBkeBYhw0,1262
117
- xpander_sdk-2.0.332.dist-info/METADATA,sha256=n6NwYa6lWAFrjLmRQ4BjUrR40ABSVIP9fI--R7DxN7Y,22146
118
- xpander_sdk-2.0.332.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
119
- xpander_sdk-2.0.332.dist-info/top_level.txt,sha256=UCjnxQpsMy5Zoe7lmRuVDO6DI2V_6PgRFfm4oizRbVs,12
120
- xpander_sdk-2.0.332.dist-info/RECORD,,
117
+ xpander_sdk-2.0.334.dist-info/licenses/LICENSE,sha256=XfXDOGoyJbLakLShycg1ZvSvTn1yisMoGVYBkeBYhw0,1262
118
+ xpander_sdk-2.0.334.dist-info/METADATA,sha256=ZCF5fqUYncShVZ7sc6KDcmHHckiBsOEh_h9EeQPCuqA,22146
119
+ xpander_sdk-2.0.334.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
120
+ xpander_sdk-2.0.334.dist-info/top_level.txt,sha256=UCjnxQpsMy5Zoe7lmRuVDO6DI2V_6PgRFfm4oizRbVs,12
121
+ xpander_sdk-2.0.334.dist-info/RECORD,,