agent-framework-core 1.4.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.4.0 → agent_framework_core-1.7.0}/PKG-INFO +1 -1
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/__init__.py +18 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_compaction.py +116 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_feature_stage.py +126 -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.4.0 → agent_framework_core-1.7.0}/agent_framework/_harness/_mode.py +50 -21
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_harness/_todo.py +94 -28
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_mcp.py +150 -24
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_skills.py +97 -9
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_agent.py +31 -16
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_agent_executor.py +2 -2
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_edge_runner.py +34 -6
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_events.py +34 -18
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_functional.py +2 -1
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_request_info_mixin.py +369 -369
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_runner_context.py +21 -1
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_validation.py +29 -3
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow.py +175 -27
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow_builder.py +217 -13
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow_context.py +28 -2
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_workflow_executor.py +17 -1
- {agent_framework_core-1.4.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.4.0 → agent_framework_core-1.7.0}/agent_framework/foundry/__init__.py +1 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/foundry/__init__.pyi +2 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/observability.py +177 -27
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/pyproject.toml +1 -1
- agent_framework_core-1.4.0/agent_framework/a2a/__init__.pyi +0 -5
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/LICENSE +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/README.md +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_agents.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_clients.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_docstrings.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_evaluation.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_harness/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_harness/_memory.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_middleware.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_serialization.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_sessions.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_settings.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_telemetry.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_tools.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_types.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_agent_utils.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_checkpoint.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_checkpoint_encoding.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_const.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_conversation_history.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_edge.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_executor.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_function_executor.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_message_utils.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_model_utils.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_runner.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_state.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_typing_utils.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/_workflows/_viz.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/ag_ui/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/ag_ui/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/amazon/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/amazon/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/anthropic/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/anthropic/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/azure/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/azure/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/chatkit/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/chatkit/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/declarative/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/declarative/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/devui/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/devui/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/exceptions.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/github/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/github/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/google/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/google/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/hyperlight/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/lab/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/mem0/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/mem0/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/microsoft/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/microsoft/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/ollama/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/ollama/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/openai/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/openai/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/orchestrations/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/orchestrations/__init__.pyi +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/py.typed +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/redis/__init__.py +0 -0
- {agent_framework_core-1.4.0 → agent_framework_core-1.7.0}/agent_framework/redis/__init__.pyi +0 -0
- {agent_framework_core-1.4.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
|
|
@@ -49,9 +53,12 @@ class ExperimentalFeature(str, Enum):
|
|
|
49
53
|
EVALS = "EVALS"
|
|
50
54
|
FILE_HISTORY = "FILE_HISTORY"
|
|
51
55
|
FIDES = "FIDES"
|
|
56
|
+
FOUNDRY_TOOLS = "FOUNDRY_TOOLS"
|
|
57
|
+
FOUNDRY_PREVIEW_TOOLS = "FOUNDRY_PREVIEW_TOOLS"
|
|
52
58
|
FUNCTIONAL_WORKFLOWS = "FUNCTIONAL_WORKFLOWS"
|
|
53
59
|
HARNESS = "HARNESS"
|
|
54
60
|
SKILLS = "SKILLS"
|
|
61
|
+
TO_PROMPT_AGENT = "TO_PROMPT_AGENT"
|
|
55
62
|
|
|
56
63
|
|
|
57
64
|
class ReleaseCandidateFeature(str, Enum):
|
|
@@ -73,6 +80,51 @@ class ExperimentalWarning(FeatureStageWarning):
|
|
|
73
80
|
"""Warning emitted when an experimental API is used."""
|
|
74
81
|
|
|
75
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
|
+
|
|
76
128
|
def _normalize_feature_id(feature_id: str | Enum) -> str:
|
|
77
129
|
return str(feature_id.value if isinstance(feature_id, Enum) else feature_id)
|
|
78
130
|
|
|
@@ -107,23 +159,91 @@ def _set_feature_stage_metadata(obj: Any, *, stage: FeatureStageName, feature_id
|
|
|
107
159
|
setattr(obj, _FEATURE_ID_ATTR, feature_id)
|
|
108
160
|
|
|
109
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
|
+
|
|
110
222
|
def _warn_on_feature_use(
|
|
111
223
|
*,
|
|
112
224
|
stage: FeatureStageName,
|
|
113
225
|
feature_id: str,
|
|
114
226
|
object_name: str,
|
|
115
227
|
category: type[Warning],
|
|
116
|
-
stacklevel: int,
|
|
117
228
|
) -> None:
|
|
118
229
|
warning_key = (category, feature_id)
|
|
119
230
|
if warning_key in _WARNED_FEATURES:
|
|
120
231
|
return
|
|
121
232
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
)
|
|
127
247
|
_WARNED_FEATURES.add(warning_key)
|
|
128
248
|
|
|
129
249
|
|
|
@@ -148,7 +268,6 @@ def _add_runtime_warning(
|
|
|
148
268
|
feature_id=feature_id,
|
|
149
269
|
object_name=object_name,
|
|
150
270
|
category=category,
|
|
151
|
-
stacklevel=3,
|
|
152
271
|
)
|
|
153
272
|
if original_new is not object.__new__:
|
|
154
273
|
return original_new(cls, *args, **kwargs)
|
|
@@ -169,7 +288,6 @@ def _add_runtime_warning(
|
|
|
169
288
|
feature_id=feature_id,
|
|
170
289
|
object_name=object_name,
|
|
171
290
|
category=category,
|
|
172
|
-
stacklevel=3,
|
|
173
291
|
)
|
|
174
292
|
return original_init_subclass_func(*args, **kwargs)
|
|
175
293
|
|
|
@@ -183,7 +301,6 @@ def _add_runtime_warning(
|
|
|
183
301
|
feature_id=feature_id,
|
|
184
302
|
object_name=object_name,
|
|
185
303
|
category=category,
|
|
186
|
-
stacklevel=3,
|
|
187
304
|
)
|
|
188
305
|
return original_init_subclass(*args, **kwargs)
|
|
189
306
|
|
|
@@ -198,7 +315,6 @@ def _add_runtime_warning(
|
|
|
198
315
|
feature_id=feature_id,
|
|
199
316
|
object_name=object_name,
|
|
200
317
|
category=category,
|
|
201
|
-
stacklevel=3,
|
|
202
318
|
)
|
|
203
319
|
return obj(*args, **kwargs)
|
|
204
320
|
|