agent-framework-core 1.6.0__tar.gz → 1.7.0__tar.gz
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.
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/PKG-INFO +1 -1
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/__init__.py +18 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_compaction.py +116 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_feature_stage.py +124 -10
- agent_framework_core-1.7.0/agent_framework/_harness/_agent.py +349 -0
- agent_framework_core-1.7.0/agent_framework/_harness/_background_agents.py +521 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_harness/_mode.py +50 -21
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_harness/_todo.py +94 -28
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_request_info_mixin.py +369 -369
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/a2a/__init__.py +2 -1
- agent_framework_core-1.7.0/agent_framework/a2a/__init__.pyi +5 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/foundry/__init__.py +1 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/foundry/__init__.pyi +2 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/pyproject.toml +1 -1
- agent_framework_core-1.6.0/agent_framework/a2a/__init__.pyi +0 -5
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/LICENSE +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/README.md +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_agents.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_clients.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_docstrings.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_evaluation.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_harness/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_harness/_memory.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_mcp.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_middleware.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_serialization.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_sessions.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_settings.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_skills.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_telemetry.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_tools.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_types.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_agent.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_agent_executor.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_agent_utils.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_checkpoint.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_checkpoint_encoding.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_const.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_conversation_history.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_edge.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_edge_runner.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_events.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_executor.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_function_executor.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_functional.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_message_utils.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_model_utils.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_runner.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_runner_context.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_state.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_typing_utils.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_validation.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_viz.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow_builder.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow_context.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow_executor.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/ag_ui/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/ag_ui/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/amazon/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/amazon/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/anthropic/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/anthropic/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/azure/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/azure/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/chatkit/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/chatkit/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/declarative/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/declarative/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/devui/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/devui/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/exceptions.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/github/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/github/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/google/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/google/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/hyperlight/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/lab/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/mem0/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/mem0/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/microsoft/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/microsoft/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/observability.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/ollama/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/ollama/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/openai/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/openai/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/orchestrations/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/orchestrations/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/py.typed +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/redis/__init__.py +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/redis/__init__.pyi +0 -0
- {agent_framework_core-1.6.0 → agent_framework_core-1.7.0}/agent_framework/security.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-core
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Microsoft Agent Framework for building AI Agents with Python. This is the core package that has all the core abstractions and implementations.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -45,6 +45,7 @@ from ._compaction import (
|
|
|
45
45
|
CharacterEstimatorTokenizer,
|
|
46
46
|
CompactionProvider,
|
|
47
47
|
CompactionStrategy,
|
|
48
|
+
ContextWindowCompactionStrategy,
|
|
48
49
|
SelectiveToolCallCompactionStrategy,
|
|
49
50
|
SlidingWindowStrategy,
|
|
50
51
|
SummarizationStrategy,
|
|
@@ -79,6 +80,16 @@ from ._evaluation import (
|
|
|
79
80
|
tool_calls_present,
|
|
80
81
|
)
|
|
81
82
|
from ._feature_stage import ExperimentalFeature, ReleaseCandidateFeature
|
|
83
|
+
from ._harness._agent import (
|
|
84
|
+
DEFAULT_HARNESS_INSTRUCTIONS,
|
|
85
|
+
create_harness_agent,
|
|
86
|
+
)
|
|
87
|
+
from ._harness._background_agents import (
|
|
88
|
+
DEFAULT_BACKGROUND_AGENTS_SOURCE_ID,
|
|
89
|
+
BackgroundAgentsProvider,
|
|
90
|
+
BackgroundTaskInfo,
|
|
91
|
+
BackgroundTaskStatus,
|
|
92
|
+
)
|
|
82
93
|
from ._harness._memory import (
|
|
83
94
|
DEFAULT_MEMORY_SOURCE_ID,
|
|
84
95
|
MemoryContextProvider,
|
|
@@ -297,6 +308,8 @@ __all__ = [
|
|
|
297
308
|
"AGENT_FRAMEWORK_USER_AGENT",
|
|
298
309
|
"APP_INFO",
|
|
299
310
|
"COMPACTION_STATE_KEY",
|
|
311
|
+
"DEFAULT_BACKGROUND_AGENTS_SOURCE_ID",
|
|
312
|
+
"DEFAULT_HARNESS_INSTRUCTIONS",
|
|
300
313
|
"DEFAULT_MAX_ITERATIONS",
|
|
301
314
|
"DEFAULT_MEMORY_SOURCE_ID",
|
|
302
315
|
"DEFAULT_MODE_SOURCE_ID",
|
|
@@ -332,6 +345,9 @@ __all__ = [
|
|
|
332
345
|
"AgentSession",
|
|
333
346
|
"AggregatingSkillsSource",
|
|
334
347
|
"Annotation",
|
|
348
|
+
"BackgroundAgentsProvider",
|
|
349
|
+
"BackgroundTaskInfo",
|
|
350
|
+
"BackgroundTaskStatus",
|
|
335
351
|
"BaseAgent",
|
|
336
352
|
"BaseChatClient",
|
|
337
353
|
"BaseEmbeddingClient",
|
|
@@ -352,6 +368,7 @@ __all__ = [
|
|
|
352
368
|
"CompactionStrategy",
|
|
353
369
|
"Content",
|
|
354
370
|
"ContextProvider",
|
|
371
|
+
"ContextWindowCompactionStrategy",
|
|
355
372
|
"ContinuationToken",
|
|
356
373
|
"ConversationSplit",
|
|
357
374
|
"ConversationSplitter",
|
|
@@ -499,6 +516,7 @@ __all__ = [
|
|
|
499
516
|
"apply_compaction",
|
|
500
517
|
"chat_middleware",
|
|
501
518
|
"create_edge_runner",
|
|
519
|
+
"create_harness_agent",
|
|
502
520
|
"detect_media_type_from_base64",
|
|
503
521
|
"evaluate_agent",
|
|
504
522
|
"evaluate_workflow",
|
|
@@ -1277,6 +1277,121 @@ class CompactionProvider(ContextProvider):
|
|
|
1277
1277
|
# whether excluded messages are loaded on the next turn.
|
|
1278
1278
|
|
|
1279
1279
|
|
|
1280
|
+
class ContextWindowCompactionStrategy:
|
|
1281
|
+
"""Token-budget compaction derived from a model's context window size.
|
|
1282
|
+
|
|
1283
|
+
Computes an input budget from the model's context window and output token
|
|
1284
|
+
limits, then applies a two-phase compaction pipeline:
|
|
1285
|
+
|
|
1286
|
+
1. **Tool result eviction** — collapses older tool-call groups into summaries
|
|
1287
|
+
when included tokens exceed ``tool_eviction_threshold`` of the input budget.
|
|
1288
|
+
2. **Truncation** — removes oldest non-system groups when included tokens
|
|
1289
|
+
exceed ``truncation_threshold`` of the input budget.
|
|
1290
|
+
|
|
1291
|
+
The class uses two independent :class:`TokenBudgetComposedStrategy`
|
|
1292
|
+
instances — one per phase — so each fires only when its own threshold
|
|
1293
|
+
is exceeded.
|
|
1294
|
+
|
|
1295
|
+
Examples:
|
|
1296
|
+
.. code-block:: python
|
|
1297
|
+
|
|
1298
|
+
from agent_framework import ContextWindowCompactionStrategy, CompactionProvider
|
|
1299
|
+
|
|
1300
|
+
strategy = ContextWindowCompactionStrategy(
|
|
1301
|
+
max_context_window_tokens=128_000,
|
|
1302
|
+
max_output_tokens=16_384,
|
|
1303
|
+
)
|
|
1304
|
+
provider = CompactionProvider(before_strategy=strategy)
|
|
1305
|
+
"""
|
|
1306
|
+
|
|
1307
|
+
DEFAULT_TOOL_EVICTION_THRESHOLD: float = 0.5
|
|
1308
|
+
"""Default fraction of input budget at which tool result eviction triggers."""
|
|
1309
|
+
|
|
1310
|
+
DEFAULT_TRUNCATION_THRESHOLD: float = 0.8
|
|
1311
|
+
"""Default fraction of input budget at which truncation triggers."""
|
|
1312
|
+
|
|
1313
|
+
def __init__(
|
|
1314
|
+
self,
|
|
1315
|
+
*,
|
|
1316
|
+
max_context_window_tokens: int,
|
|
1317
|
+
max_output_tokens: int,
|
|
1318
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
1319
|
+
tool_eviction_threshold: float = DEFAULT_TOOL_EVICTION_THRESHOLD,
|
|
1320
|
+
truncation_threshold: float = DEFAULT_TRUNCATION_THRESHOLD,
|
|
1321
|
+
keep_last_tool_call_groups: int = 4,
|
|
1322
|
+
) -> None:
|
|
1323
|
+
"""Create a context-window compaction strategy.
|
|
1324
|
+
|
|
1325
|
+
Keyword Args:
|
|
1326
|
+
max_context_window_tokens: The model's maximum context window size
|
|
1327
|
+
in tokens (e.g. 128,000).
|
|
1328
|
+
max_output_tokens: The model's maximum output tokens per response
|
|
1329
|
+
(e.g. 16,384).
|
|
1330
|
+
tokenizer: Token counter for measuring message sizes. Defaults to
|
|
1331
|
+
:class:`CharacterEstimatorTokenizer` (4 chars/token heuristic).
|
|
1332
|
+
tool_eviction_threshold: Fraction of input budget (0.0, 1.0] at
|
|
1333
|
+
which tool result eviction triggers. Defaults to 0.5.
|
|
1334
|
+
truncation_threshold: Fraction of input budget (0.0, 1.0] at which
|
|
1335
|
+
truncation triggers. Must be ≥ ``tool_eviction_threshold``.
|
|
1336
|
+
Defaults to 0.8.
|
|
1337
|
+
keep_last_tool_call_groups: Number of most recent tool-call groups
|
|
1338
|
+
to retain verbatim during tool eviction. Older groups are
|
|
1339
|
+
collapsed into summaries. Defaults to 4.
|
|
1340
|
+
|
|
1341
|
+
Raises:
|
|
1342
|
+
ValueError: If thresholds are out of range or inconsistent.
|
|
1343
|
+
"""
|
|
1344
|
+
if max_context_window_tokens <= 0:
|
|
1345
|
+
raise ValueError("max_context_window_tokens must be positive.")
|
|
1346
|
+
if max_output_tokens < 0 or max_output_tokens >= max_context_window_tokens:
|
|
1347
|
+
raise ValueError("max_output_tokens must be >= 0 and < max_context_window_tokens.")
|
|
1348
|
+
if not (0.0 < tool_eviction_threshold <= 1.0):
|
|
1349
|
+
raise ValueError("tool_eviction_threshold must be in (0.0, 1.0].")
|
|
1350
|
+
if not (0.0 < truncation_threshold <= 1.0):
|
|
1351
|
+
raise ValueError("truncation_threshold must be in (0.0, 1.0].")
|
|
1352
|
+
if truncation_threshold < tool_eviction_threshold:
|
|
1353
|
+
raise ValueError("truncation_threshold must be >= tool_eviction_threshold.")
|
|
1354
|
+
|
|
1355
|
+
resolved_tokenizer = tokenizer or CharacterEstimatorTokenizer()
|
|
1356
|
+
input_budget = max_context_window_tokens - max_output_tokens
|
|
1357
|
+
tool_eviction_tokens = int(input_budget * tool_eviction_threshold)
|
|
1358
|
+
truncation_tokens = int(input_budget * truncation_threshold)
|
|
1359
|
+
|
|
1360
|
+
self.max_context_window_tokens = max_context_window_tokens
|
|
1361
|
+
self.max_output_tokens = max_output_tokens
|
|
1362
|
+
self.input_budget_tokens = input_budget
|
|
1363
|
+
self.tool_eviction_threshold = tool_eviction_threshold
|
|
1364
|
+
self.truncation_threshold = truncation_threshold
|
|
1365
|
+
|
|
1366
|
+
self._tool_eviction = TokenBudgetComposedStrategy(
|
|
1367
|
+
token_budget=tool_eviction_tokens,
|
|
1368
|
+
tokenizer=resolved_tokenizer,
|
|
1369
|
+
strategies=[
|
|
1370
|
+
ToolResultCompactionStrategy(keep_last_tool_call_groups=keep_last_tool_call_groups),
|
|
1371
|
+
],
|
|
1372
|
+
)
|
|
1373
|
+
self._truncation = TokenBudgetComposedStrategy(
|
|
1374
|
+
token_budget=truncation_tokens,
|
|
1375
|
+
tokenizer=resolved_tokenizer,
|
|
1376
|
+
strategies=[
|
|
1377
|
+
TruncationStrategy(
|
|
1378
|
+
max_n=truncation_tokens,
|
|
1379
|
+
compact_to=tool_eviction_tokens,
|
|
1380
|
+
tokenizer=resolved_tokenizer,
|
|
1381
|
+
),
|
|
1382
|
+
],
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
async def __call__(self, messages: list[Message]) -> bool:
|
|
1386
|
+
"""Apply the two-phase compaction pipeline.
|
|
1387
|
+
|
|
1388
|
+
Returns:
|
|
1389
|
+
True if compaction changed message inclusion; otherwise False.
|
|
1390
|
+
"""
|
|
1391
|
+
changed = await self._tool_eviction(messages)
|
|
1392
|
+
return (await self._truncation(messages)) or changed
|
|
1393
|
+
|
|
1394
|
+
|
|
1280
1395
|
__all__ = [
|
|
1281
1396
|
"COMPACTION_STATE_KEY",
|
|
1282
1397
|
"EXCLUDED_KEY",
|
|
@@ -1293,6 +1408,7 @@ __all__ = [
|
|
|
1293
1408
|
"CharacterEstimatorTokenizer",
|
|
1294
1409
|
"CompactionProvider",
|
|
1295
1410
|
"CompactionStrategy",
|
|
1411
|
+
"ContextWindowCompactionStrategy",
|
|
1296
1412
|
"GroupKind",
|
|
1297
1413
|
"SelectiveToolCallCompactionStrategy",
|
|
1298
1414
|
"SlidingWindowStrategy",
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import abc
|
|
5
6
|
import asyncio.coroutines
|
|
7
|
+
import contextlib
|
|
6
8
|
import functools
|
|
7
9
|
import inspect
|
|
10
|
+
import os
|
|
8
11
|
import sys
|
|
12
|
+
import typing
|
|
9
13
|
import warnings
|
|
10
14
|
from collections.abc import Callable
|
|
11
15
|
from enum import Enum
|
|
@@ -54,6 +58,7 @@ class ExperimentalFeature(str, Enum):
|
|
|
54
58
|
FUNCTIONAL_WORKFLOWS = "FUNCTIONAL_WORKFLOWS"
|
|
55
59
|
HARNESS = "HARNESS"
|
|
56
60
|
SKILLS = "SKILLS"
|
|
61
|
+
TO_PROMPT_AGENT = "TO_PROMPT_AGENT"
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
class ReleaseCandidateFeature(str, Enum):
|
|
@@ -75,6 +80,51 @@ class ExperimentalWarning(FeatureStageWarning):
|
|
|
75
80
|
"""Warning emitted when an experimental API is used."""
|
|
76
81
|
|
|
77
82
|
|
|
83
|
+
# Sentinel attribute used to detect (and reuse) a formatter we've already
|
|
84
|
+
# installed. This lets the install be idempotent across re-imports / reloads
|
|
85
|
+
# and keeps a stable reference to the previous formatter for testing or
|
|
86
|
+
# external restoration via ``warnings.formatwarning = original``.
|
|
87
|
+
_FEATURE_STAGE_FORMATTER_MARKER = "__feature_stage_formatter__"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _install_feature_stage_formatter() -> None:
|
|
91
|
+
"""Install a single-line formatter for FeatureStageWarning categories.
|
|
92
|
+
|
|
93
|
+
The stdlib default formatter emits two lines (header + source snippet)
|
|
94
|
+
which is noisy for our warnings — the offending class/function name is
|
|
95
|
+
already in the message, so a one-line ``file:lineno: Category: message``
|
|
96
|
+
is enough. Other warning categories are delegated to the previous
|
|
97
|
+
formatter so we never change behaviour for unrelated warnings.
|
|
98
|
+
|
|
99
|
+
The install is idempotent: if a formatter installed by this module is
|
|
100
|
+
already in place, we leave it alone so re-imports (and any third-party
|
|
101
|
+
formatter wrapped on top of ours) don't get wrapped multiple times.
|
|
102
|
+
"""
|
|
103
|
+
current = warnings.formatwarning
|
|
104
|
+
if getattr(current, _FEATURE_STAGE_FORMATTER_MARKER, False):
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
def _formatwarning(
|
|
108
|
+
message: Warning | str,
|
|
109
|
+
category: type[Warning],
|
|
110
|
+
filename: str,
|
|
111
|
+
lineno: int,
|
|
112
|
+
line: str | None = None,
|
|
113
|
+
) -> str:
|
|
114
|
+
if issubclass(category, FeatureStageWarning):
|
|
115
|
+
return f"{filename}:{lineno}: {category.__name__}: {message}\n"
|
|
116
|
+
return current(message, category, filename, lineno, line)
|
|
117
|
+
|
|
118
|
+
setattr(_formatwarning, _FEATURE_STAGE_FORMATTER_MARKER, True)
|
|
119
|
+
# Keep a reference to the wrapped formatter so callers (tests, embedders)
|
|
120
|
+
# can restore the previous behaviour if they need to.
|
|
121
|
+
_formatwarning.__wrapped__ = current # type: ignore[attr-defined]
|
|
122
|
+
warnings.formatwarning = _formatwarning
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
_install_feature_stage_formatter()
|
|
126
|
+
|
|
127
|
+
|
|
78
128
|
def _normalize_feature_id(feature_id: str | Enum) -> str:
|
|
79
129
|
return str(feature_id.value if isinstance(feature_id, Enum) else feature_id)
|
|
80
130
|
|
|
@@ -109,23 +159,91 @@ def _set_feature_stage_metadata(obj: Any, *, stage: FeatureStageName, feature_id
|
|
|
109
159
|
setattr(obj, _FEATURE_ID_ATTR, feature_id)
|
|
110
160
|
|
|
111
161
|
|
|
162
|
+
_INTERNAL_FRAME_FILE = os.path.normcase(__file__)
|
|
163
|
+
# Module names whose frames we never want to surface as the caller. ``abc`` is
|
|
164
|
+
# the big one (its ``__new__`` shows up as ``<frozen abc>:106`` for ABC-driven
|
|
165
|
+
# subclass creation on modern CPython, so we cannot rely on filename matching).
|
|
166
|
+
# ``functools``/``typing``/``contextlib`` are added because they often wrap our
|
|
167
|
+
# decorators or appear in the metaclass call path.
|
|
168
|
+
_INTERNAL_FRAME_MODULES: frozenset[str] = frozenset({
|
|
169
|
+
abc.__name__,
|
|
170
|
+
functools.__name__,
|
|
171
|
+
typing.__name__,
|
|
172
|
+
contextlib.__name__,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _is_internal_frame(frame: Any) -> bool:
|
|
177
|
+
if os.path.normcase(frame.f_code.co_filename) == _INTERNAL_FRAME_FILE:
|
|
178
|
+
return True
|
|
179
|
+
module_name = frame.f_globals.get("__name__", "")
|
|
180
|
+
if module_name in _INTERNAL_FRAME_MODULES:
|
|
181
|
+
return True
|
|
182
|
+
# Submodules of the skipped stdlib packages (``typing.ext``, ``functools``
|
|
183
|
+
# wrappers under ``concurrent.futures._base``, etc.) are also wrappers we
|
|
184
|
+
# don't want to surface.
|
|
185
|
+
return any(module_name.startswith(prefix + ".") for prefix in _INTERNAL_FRAME_MODULES)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _resolve_user_frame() -> tuple[str, int, str] | None:
|
|
189
|
+
"""Resolve the user frame that triggered an experimental warning.
|
|
190
|
+
|
|
191
|
+
Walk the stack and return ``(filename, lineno, module_name)`` for the first
|
|
192
|
+
frame outside this module and the wrapping/metaclass machinery.
|
|
193
|
+
|
|
194
|
+
Returns ``None`` if no such frame is found; callers fall back to plain
|
|
195
|
+
``warnings.warn`` with a fixed stacklevel.
|
|
196
|
+
"""
|
|
197
|
+
# Frame objects participate in reference cycles (``frame -> f_locals ->
|
|
198
|
+
# frame``) and can delay GC if held implicitly. Capture the user frame's
|
|
199
|
+
# data into plain values inside the try, and explicitly delete the frame
|
|
200
|
+
# references in finally so we never leak frames across this call. This
|
|
201
|
+
# follows CPython's own guidance for code that uses ``inspect.currentframe``.
|
|
202
|
+
frame = inspect.currentframe()
|
|
203
|
+
candidate: Any = None
|
|
204
|
+
try:
|
|
205
|
+
if frame is None:
|
|
206
|
+
return None
|
|
207
|
+
# Skip _resolve_user_frame itself + the warn helper that called it.
|
|
208
|
+
candidate = frame.f_back.f_back if frame.f_back and frame.f_back.f_back else None
|
|
209
|
+
while candidate is not None:
|
|
210
|
+
if not _is_internal_frame(candidate):
|
|
211
|
+
return (
|
|
212
|
+
candidate.f_code.co_filename,
|
|
213
|
+
candidate.f_lineno,
|
|
214
|
+
candidate.f_globals.get("__name__", "<unknown>"),
|
|
215
|
+
)
|
|
216
|
+
candidate = candidate.f_back
|
|
217
|
+
return None
|
|
218
|
+
finally:
|
|
219
|
+
del frame, candidate
|
|
220
|
+
|
|
221
|
+
|
|
112
222
|
def _warn_on_feature_use(
|
|
113
223
|
*,
|
|
114
224
|
stage: FeatureStageName,
|
|
115
225
|
feature_id: str,
|
|
116
226
|
object_name: str,
|
|
117
227
|
category: type[Warning],
|
|
118
|
-
stacklevel: int,
|
|
119
228
|
) -> None:
|
|
120
229
|
warning_key = (category, feature_id)
|
|
121
230
|
if warning_key in _WARNED_FEATURES:
|
|
122
231
|
return
|
|
123
232
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
233
|
+
message = _build_stage_warning_message(stage=stage, feature_id=feature_id, object_name=object_name)
|
|
234
|
+
user_frame = _resolve_user_frame()
|
|
235
|
+
if user_frame is None:
|
|
236
|
+
# Last-resort fallback: emit at the immediate caller of this helper.
|
|
237
|
+
warnings.warn(message, category=category, stacklevel=2)
|
|
238
|
+
else:
|
|
239
|
+
filename, lineno, module = user_frame
|
|
240
|
+
warnings.warn_explicit(
|
|
241
|
+
message,
|
|
242
|
+
category=category,
|
|
243
|
+
filename=filename,
|
|
244
|
+
lineno=lineno,
|
|
245
|
+
module=module,
|
|
246
|
+
)
|
|
129
247
|
_WARNED_FEATURES.add(warning_key)
|
|
130
248
|
|
|
131
249
|
|
|
@@ -150,7 +268,6 @@ def _add_runtime_warning(
|
|
|
150
268
|
feature_id=feature_id,
|
|
151
269
|
object_name=object_name,
|
|
152
270
|
category=category,
|
|
153
|
-
stacklevel=3,
|
|
154
271
|
)
|
|
155
272
|
if original_new is not object.__new__:
|
|
156
273
|
return original_new(cls, *args, **kwargs)
|
|
@@ -171,7 +288,6 @@ def _add_runtime_warning(
|
|
|
171
288
|
feature_id=feature_id,
|
|
172
289
|
object_name=object_name,
|
|
173
290
|
category=category,
|
|
174
|
-
stacklevel=3,
|
|
175
291
|
)
|
|
176
292
|
return original_init_subclass_func(*args, **kwargs)
|
|
177
293
|
|
|
@@ -185,7 +301,6 @@ def _add_runtime_warning(
|
|
|
185
301
|
feature_id=feature_id,
|
|
186
302
|
object_name=object_name,
|
|
187
303
|
category=category,
|
|
188
|
-
stacklevel=3,
|
|
189
304
|
)
|
|
190
305
|
return original_init_subclass(*args, **kwargs)
|
|
191
306
|
|
|
@@ -200,7 +315,6 @@ def _add_runtime_warning(
|
|
|
200
315
|
feature_id=feature_id,
|
|
201
316
|
object_name=object_name,
|
|
202
317
|
category=category,
|
|
203
|
-
stacklevel=3,
|
|
204
318
|
)
|
|
205
319
|
return obj(*args, **kwargs)
|
|
206
320
|
|