agent-framework-core 1.16.0__tar.gz → 1.17.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.16.0 → agent_framework_core-1.17.0}/PKG-INFO +1 -3
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_agent_hooks.py +92 -16
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_agents.py +19 -19
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_clients.py +5 -1
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_compaction.py +183 -34
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_agent.py +4 -6
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_agent.pyi +1 -1
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_mcp.py +83 -21
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_middleware.py +184 -26
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_tools.py +140 -22
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_types.py +65 -3
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_agent.py +69 -18
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_agent_executor.py +61 -28
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_executor.py +4 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_request_info_mixin.py +369 -369
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_runner_context.py +38 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_workflow.py +95 -7
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_workflow_context.py +5 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_workflow_executor.py +12 -1
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/observability.py +4 -4
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/security.py +34 -19
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/pyproject.toml +1 -6
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/LICENSE +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/README.md +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_docstrings.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_evaluation.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_feature_stage.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_filesystem.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_background_agents.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_file_access.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_file_memory.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_loop.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_memory.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_mode.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_todo.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_tool_approval.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_serialization.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_sessions.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_settings.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_skills.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_telemetry.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_agent_utils.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_checkpoint.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_checkpoint_encoding.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_const.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_conversation_history.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_edge.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_edge_runner.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_events.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_function_executor.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_functional.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_message_utils.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_model_utils.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_runner.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_state.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_typing_utils.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_validation.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_viz.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_workflows/_workflow_builder.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/a2a/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/a2a/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/ag_ui/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/ag_ui/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/amazon/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/amazon/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/anthropic/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/anthropic/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/azure/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/azure/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/chatkit/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/chatkit/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/declarative/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/declarative/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/devui/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/devui/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/exceptions.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/foundry/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/foundry/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/gemini/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/gemini/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/github/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/github/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/google/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/google/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/hyperlight/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/lab/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/mem0/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/mem0/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/microsoft/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/microsoft/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/mistral/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/mistral/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/monty/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/monty/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/ollama/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/ollama/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/openai/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/openai/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/orchestrations/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/orchestrations/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/py.typed +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/redis/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/redis/__init__.pyi +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/tools/__init__.py +0 -0
- {agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/tools/__init__.pyi +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-core
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.17.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
|
|
@@ -21,7 +21,6 @@ Requires-Dist: typing-extensions>=4.15.0,<5
|
|
|
21
21
|
Requires-Dist: pydantic>=2,<3
|
|
22
22
|
Requires-Dist: python-dotenv>=1,<2
|
|
23
23
|
Requires-Dist: opentelemetry-api>=1.39.0,<2
|
|
24
|
-
Requires-Dist: agent-hooks-sdk>=0.1.0a4,<0.2 ; extra == "agent-hooks"
|
|
25
24
|
Requires-Dist: mcp>=1.24.0,<2 ; extra == "all"
|
|
26
25
|
Requires-Dist: agent-framework-a2a ; extra == "all"
|
|
27
26
|
Requires-Dist: agent-framework-ag-ui ; extra == "all"
|
|
@@ -57,7 +56,6 @@ Project-URL: homepage, https://aka.ms/agent-framework
|
|
|
57
56
|
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
58
57
|
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
|
59
58
|
Project-URL: source, https://github.com/microsoft/agent-framework/tree/main/python
|
|
60
|
-
Provides-Extra: agent-hooks
|
|
61
59
|
Provides-Extra: all
|
|
62
60
|
|
|
63
61
|
# Get Started with Microsoft Agent Framework
|
|
@@ -93,7 +93,7 @@ per-run points and the host owns the session boundaries.
|
|
|
93
93
|
|
|
94
94
|
The ``agent-hooks-sdk`` dependency is optional: importing this module (and the lazy root
|
|
95
95
|
exports) works without it, and the factories raise a descriptive ``ModuleNotFoundError``
|
|
96
|
-
when the SDK is missing. Install it via ``pip install agent-
|
|
96
|
+
when the SDK is missing. Install it via ``pip install agent-hooks-sdk``.
|
|
97
97
|
"""
|
|
98
98
|
|
|
99
99
|
from __future__ import annotations
|
|
@@ -162,7 +162,7 @@ _DEFAULT_TIMEOUT = 5.0
|
|
|
162
162
|
|
|
163
163
|
_SDK_MISSING_MESSAGE = (
|
|
164
164
|
"The agent-hooks middleware requires the optional `agent-hooks-sdk` package. "
|
|
165
|
-
"Please install `
|
|
165
|
+
"Please install it with `pip install agent-hooks-sdk`."
|
|
166
166
|
)
|
|
167
167
|
|
|
168
168
|
_TRIO_REQUIRED_MESSAGE = (
|
|
@@ -191,7 +191,7 @@ def _require_sdk() -> None:
|
|
|
191
191
|
"""Import the SDK surface this module uses at runtime, with a helpful install hint.
|
|
192
192
|
|
|
193
193
|
Only a genuinely missing ``agent_hooks`` package is translated into the
|
|
194
|
-
|
|
194
|
+
SDK installation message; anything else (a broken installation, an incompatible
|
|
195
195
|
SDK version missing symbols, a failing transitive import) propagates unchanged so
|
|
196
196
|
real breakage is not masked as a missing extra.
|
|
197
197
|
"""
|
|
@@ -862,23 +862,99 @@ def _agent_updates_from_response(response: AgentResponse[Any]) -> list[AgentResp
|
|
|
862
862
|
return updates
|
|
863
863
|
|
|
864
864
|
|
|
865
|
-
def
|
|
866
|
-
"""
|
|
867
|
-
|
|
865
|
+
def _normalized_tools(tools: Any, *, point: str) -> list[Any]:
|
|
866
|
+
"""Normalize a tools value for projection; empty (with a warning) when it cannot be.
|
|
867
|
+
|
|
868
|
+
Everything — including the emptiness check inside ``normalize_tools`` — runs under
|
|
869
|
+
the guard, so a tools container whose ``__bool__``/``__len__``/``__iter__`` raises
|
|
870
|
+
degrades to omitting the projection instead of aborting the emission mid-run.
|
|
871
|
+
"""
|
|
872
|
+
from ._tools import normalize_tools
|
|
868
873
|
|
|
869
|
-
|
|
870
|
-
|
|
874
|
+
try:
|
|
875
|
+
return list(normalize_tools(tools))
|
|
876
|
+
except Exception:
|
|
877
|
+
logger.warning("agent-hooks could not normalize the tools for the %s projection.", point)
|
|
871
878
|
return []
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def _projected_tool_name(item: Any) -> str:
|
|
882
|
+
"""Project a tool's display name; hosted-tool mappings fall back to their ``type``."""
|
|
883
|
+
from ._tools import _get_tool_name # type: ignore[reportPrivateUsage]
|
|
884
|
+
|
|
885
|
+
fallback = type(item).__name__
|
|
886
|
+
name = _get_tool_name(item)
|
|
887
|
+
if name:
|
|
888
|
+
return name
|
|
889
|
+
if isinstance(item, Mapping):
|
|
890
|
+
# Hosted-tool mappings carry top-level fields (no nested "function"), e.g.
|
|
891
|
+
# {"type": "web_search"} or {"type": "web_search_20250305", "name": "web_search"}.
|
|
892
|
+
mapping = cast("Mapping[str, Any]", item)
|
|
893
|
+
for key in ("name", "type"):
|
|
894
|
+
value = mapping.get(key)
|
|
895
|
+
if isinstance(value, str) and value:
|
|
896
|
+
return value
|
|
897
|
+
return fallback
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _tool_description(item: Any) -> str | None:
|
|
901
|
+
"""Extract a tool description from a tool object or dict tool definition."""
|
|
902
|
+
if isinstance(item, Mapping):
|
|
903
|
+
mapping = cast("Mapping[str, Any]", item)
|
|
904
|
+
function = mapping.get("function")
|
|
905
|
+
# Function-tool dicts nest the description; hosted-tool mappings keep it top-level.
|
|
906
|
+
description = (
|
|
907
|
+
cast("Mapping[str, Any]", function).get("description")
|
|
908
|
+
if isinstance(function, Mapping)
|
|
909
|
+
else mapping.get("description")
|
|
910
|
+
)
|
|
911
|
+
return description if isinstance(description, str) else None
|
|
912
|
+
description = getattr(item, "description", None)
|
|
913
|
+
return description if isinstance(description, str) else None
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def _tool_names(context: AgentContext) -> list[str]:
|
|
917
|
+
"""Project the run-start tool names for ``agent_startup`` (spec ``tools_registered``).
|
|
918
|
+
|
|
919
|
+
The framework owns the run-start resolution — ``AgentContext._resolve_run_start_tools``
|
|
920
|
+
returns the agent-declared tools plus this invocation's run-level tools (the named
|
|
921
|
+
``tools`` parameter takes precedence over a ``tools`` entry in the options mapping),
|
|
922
|
+
already normalized — so this helper is projection only. This is deliberately the
|
|
923
|
+
run-start snapshot: tools registered dynamically during the run (for example by
|
|
924
|
+
context providers during run preparation, or by MCP servers whose functions expand
|
|
925
|
+
at connect time) cannot be known at ``agent_startup`` time; they surface in each
|
|
926
|
+
``pre_model_call`` emission's ``tools`` projection (the completed per-call set) and
|
|
927
|
+
are bracketed by ``pre_tool_call``/``post_tool_call`` like any other tool when
|
|
928
|
+
invoked. An unresolvable set degrades to a warning and an empty snapshot rather
|
|
929
|
+
than aborting the emission.
|
|
930
|
+
"""
|
|
872
931
|
try:
|
|
873
|
-
|
|
932
|
+
resolved = context._resolve_run_start_tools() # pyright: ignore[reportPrivateUsage]
|
|
874
933
|
except Exception:
|
|
875
|
-
logger.warning("agent-hooks could not
|
|
934
|
+
logger.warning("agent-hooks could not resolve the run-start tools for the agent_startup projection.")
|
|
876
935
|
return []
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
936
|
+
return [_projected_tool_name(item) for item in resolved]
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _pre_model_call_tools(options: Mapping[str, Any]) -> list[dict[str, Any]] | None:
|
|
940
|
+
"""Project the per-call effective tool set for ``pre_model_call`` (spec ``tools``).
|
|
941
|
+
|
|
942
|
+
This is the completed set for this model call — the run-start tools plus anything
|
|
943
|
+
registered during the run (context-provider tools, connected MCP-server functions,
|
|
944
|
+
progressive tool exposure) — whereas ``agent_startup``'s ``tools_registered`` is the
|
|
945
|
+
run-start snapshot. Entries carry ``{"name", "description"?}`` (description omitted
|
|
946
|
+
when the tool has none). Returns ``None`` — the spec's optional field is omitted —
|
|
947
|
+
when the call offers no tools or the set cannot be projected, so an unprojectable
|
|
948
|
+
set is never misreported as "no tools".
|
|
949
|
+
"""
|
|
950
|
+
projected: list[dict[str, Any]] = []
|
|
951
|
+
for item in _normalized_tools(options.get("tools"), point="pre_model_call"):
|
|
952
|
+
entry: dict[str, Any] = {"name": _projected_tool_name(item)}
|
|
953
|
+
description = _tool_description(item)
|
|
954
|
+
if description:
|
|
955
|
+
entry["description"] = description
|
|
956
|
+
projected.append(entry)
|
|
957
|
+
return projected or None
|
|
882
958
|
|
|
883
959
|
|
|
884
960
|
def _is_host_error(record: InterceptionRecord) -> bool:
|
|
@@ -1269,7 +1345,7 @@ class _AgentHooksChatMiddleware(_AgentHooksMiddlewareBase, ChatMiddleware):
|
|
|
1269
1345
|
model_id = str(options.get("model") or type(context.client).__name__)
|
|
1270
1346
|
before = _ModelRequestCodec.to_wire(context.messages)
|
|
1271
1347
|
outcome: EmitOutcome = await state.emitter.emit(
|
|
1272
|
-
state.builder.pre_model_call(model_id=model_id, messages=before)
|
|
1348
|
+
state.builder.pre_model_call(model_id=model_id, messages=before, tools=_pre_model_call_tools(options))
|
|
1273
1349
|
)
|
|
1274
1350
|
transformed_messages = _ModelRequestCodec.write_back(context.messages, before, outcome.target)
|
|
1275
1351
|
if transformed_messages is not None:
|
|
@@ -31,6 +31,8 @@ from ._middleware import (
|
|
|
31
31
|
FunctionInvocationContext,
|
|
32
32
|
MiddlewareTypes,
|
|
33
33
|
_as_middleware_list, # pyright: ignore[reportPrivateUsage]
|
|
34
|
+
_copy_middleware_sequence, # pyright: ignore[reportPrivateUsage]
|
|
35
|
+
_select_run_level_tools, # pyright: ignore[reportPrivateUsage]
|
|
34
36
|
categorize_middleware,
|
|
35
37
|
)
|
|
36
38
|
from ._serialization import SerializationMixin
|
|
@@ -439,7 +441,7 @@ class BaseAgent(SerializationMixin):
|
|
|
439
441
|
name: str | None = None,
|
|
440
442
|
description: str | None = None,
|
|
441
443
|
context_providers: Sequence[ContextProvider] | None = None,
|
|
442
|
-
middleware:
|
|
444
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
443
445
|
additional_properties: MutableMapping[str, Any] | None = None,
|
|
444
446
|
) -> None:
|
|
445
447
|
"""Initialize a BaseAgent instance.
|
|
@@ -450,10 +452,8 @@ class BaseAgent(SerializationMixin):
|
|
|
450
452
|
name: The name of the agent, can be None.
|
|
451
453
|
description: The description of the agent.
|
|
452
454
|
context_providers: Context providers to include during agent invocation.
|
|
453
|
-
middleware: List of middleware
|
|
454
|
-
|
|
455
|
-
constructor copies the sequence; assign to or mutate the
|
|
456
|
-
``middleware`` attribute for post-construction changes.
|
|
455
|
+
middleware: List of middleware. The constructor copies the sequence; assign
|
|
456
|
+
to or mutate the ``middleware`` attribute for post-construction changes.
|
|
457
457
|
additional_properties: Additional properties set on the agent.
|
|
458
458
|
"""
|
|
459
459
|
if id is None:
|
|
@@ -462,11 +462,8 @@ class BaseAgent(SerializationMixin):
|
|
|
462
462
|
self.name = name
|
|
463
463
|
self.description = description
|
|
464
464
|
self.context_providers: list[ContextProvider] = list(context_providers or [])
|
|
465
|
-
# Canonicalize storage: the bare-source rule (a single middleware object or a
|
|
466
|
-
# MiddlewareBundle is one element) is owned by _as_middleware_list; storing a
|
|
467
|
-
# normalized list keeps the declared attribute type honest.
|
|
468
465
|
self.middleware: list[MiddlewareTypes] | None = (
|
|
469
|
-
|
|
466
|
+
_copy_middleware_sequence(middleware) if middleware is not None else None
|
|
470
467
|
)
|
|
471
468
|
self.additional_properties: dict[str, Any] = cast(dict[str, Any], additional_properties or {})
|
|
472
469
|
|
|
@@ -819,7 +816,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
819
816
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
820
817
|
default_options: OptionsCoT | None = None,
|
|
821
818
|
context_providers: Sequence[ContextProvider] | None = None,
|
|
822
|
-
middleware:
|
|
819
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
823
820
|
require_per_service_call_history_persistence: bool = False,
|
|
824
821
|
compaction_strategy: CompactionStrategy | None = None,
|
|
825
822
|
tokenizer: TokenizerProtocol | None = None,
|
|
@@ -838,8 +835,6 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
838
835
|
description: A brief description of the agent's purpose.
|
|
839
836
|
context_providers: Context providers to include during agent invocation.
|
|
840
837
|
middleware: List of middleware to intercept agent and function invocations.
|
|
841
|
-
A single middleware object (including a ``MiddlewareBundle``) is
|
|
842
|
-
treated as a one-element list.
|
|
843
838
|
require_per_service_call_history_persistence: When True (and a HistoryProvider is
|
|
844
839
|
present), the provider always persists history via per-service-call middleware,
|
|
845
840
|
regardless of whether the client stores history server-side. If the client does
|
|
@@ -1365,8 +1360,13 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1365
1360
|
opts = dict(options) if options else {}
|
|
1366
1361
|
existing_additional_args: dict[str, Any] = opts.pop("additional_function_arguments", None) or {}
|
|
1367
1362
|
|
|
1368
|
-
#
|
|
1369
|
-
|
|
1363
|
+
# Run-level tools: the named parameter takes precedence over an options entry
|
|
1364
|
+
# (_select_run_level_tools is the framework's single statement of that rule,
|
|
1365
|
+
# shared with the middleware layer's run-start resolution). The options entry
|
|
1366
|
+
# is consumed either way, so a losing options["tools"] can never ride the
|
|
1367
|
+
# remaining options into the request and silently override the resolved list.
|
|
1368
|
+
tools_ = _select_run_level_tools(tools, opts)
|
|
1369
|
+
opts.pop("tools", None)
|
|
1370
1370
|
|
|
1371
1371
|
input_messages = normalize_messages(messages)
|
|
1372
1372
|
|
|
@@ -1808,7 +1808,7 @@ class Agent(
|
|
|
1808
1808
|
*,
|
|
1809
1809
|
stream: Literal[False] = ...,
|
|
1810
1810
|
session: AgentSession | None = None,
|
|
1811
|
-
middleware:
|
|
1811
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
1812
1812
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1813
1813
|
options: ChatOptions[ResponseModelBoundT],
|
|
1814
1814
|
compaction_strategy: CompactionStrategy | None = None,
|
|
@@ -1824,7 +1824,7 @@ class Agent(
|
|
|
1824
1824
|
*,
|
|
1825
1825
|
stream: Literal[False] = ...,
|
|
1826
1826
|
session: AgentSession | None = None,
|
|
1827
|
-
middleware:
|
|
1827
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
1828
1828
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1829
1829
|
options: OptionsCoT | ChatOptions[None] | None = None,
|
|
1830
1830
|
compaction_strategy: CompactionStrategy | None = None,
|
|
@@ -1840,7 +1840,7 @@ class Agent(
|
|
|
1840
1840
|
*,
|
|
1841
1841
|
stream: Literal[True],
|
|
1842
1842
|
session: AgentSession | None = None,
|
|
1843
|
-
middleware:
|
|
1843
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
1844
1844
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1845
1845
|
options: OptionsCoT | ChatOptions[Any] | None = None,
|
|
1846
1846
|
compaction_strategy: CompactionStrategy | None = None,
|
|
@@ -1855,7 +1855,7 @@ class Agent(
|
|
|
1855
1855
|
*,
|
|
1856
1856
|
stream: bool = False,
|
|
1857
1857
|
session: AgentSession | None = None,
|
|
1858
|
-
middleware:
|
|
1858
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
1859
1859
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1860
1860
|
options: OptionsCoT | ChatOptions[Any] | None = None,
|
|
1861
1861
|
compaction_strategy: CompactionStrategy | None = None,
|
|
@@ -1893,7 +1893,7 @@ class Agent(
|
|
|
1893
1893
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1894
1894
|
default_options: OptionsCoT | None = None,
|
|
1895
1895
|
context_providers: Sequence[ContextProvider] | None = None,
|
|
1896
|
-
middleware:
|
|
1896
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
1897
1897
|
require_per_service_call_history_persistence: bool = False,
|
|
1898
1898
|
compaction_strategy: CompactionStrategy | None = None,
|
|
1899
1899
|
tokenizer: TokenizerProtocol | None = None,
|
|
@@ -96,6 +96,10 @@ class SupportsChatGetResponse(Protocol[OptionsContraT]):
|
|
|
96
96
|
Protocols use structural subtyping (duck typing). Classes don't need
|
|
97
97
|
to explicitly inherit from this protocol to be considered compatible.
|
|
98
98
|
|
|
99
|
+
Implementing this protocol does not by itself guarantee that an instance
|
|
100
|
+
can be reused across concurrent calls. Check the concrete client's
|
|
101
|
+
concurrency contract before sharing it.
|
|
102
|
+
|
|
99
103
|
Examples:
|
|
100
104
|
.. code-block:: python
|
|
101
105
|
|
|
@@ -578,7 +582,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
|
|
578
582
|
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
579
583
|
default_options: OptionsCoT | Mapping[str, Any] | None = None,
|
|
580
584
|
context_providers: Sequence[Any] | None = None,
|
|
581
|
-
middleware:
|
|
585
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
582
586
|
require_per_service_call_history_persistence: bool = False,
|
|
583
587
|
compaction_strategy: CompactionStrategy | None = None,
|
|
584
588
|
tokenizer: TokenizerProtocol | None = None,
|
|
@@ -375,6 +375,76 @@ def _set_group_summarized_by_summary_id(message: Message, summary_id: str) -> No
|
|
|
375
375
|
annotation[SUMMARIZED_BY_SUMMARY_ID_KEY] = summary_id
|
|
376
376
|
|
|
377
377
|
|
|
378
|
+
def _reconcile_compaction_summaries( # pyright: ignore[reportUnusedFunction]
|
|
379
|
+
source_messages: list[Message],
|
|
380
|
+
working_messages: Sequence[Message],
|
|
381
|
+
previous_message_ids: set[int],
|
|
382
|
+
) -> None:
|
|
383
|
+
"""Reconcile summaries of source messages without persisting unrelated rewrites."""
|
|
384
|
+
source_message_ids = {message.message_id for message in source_messages if message.message_id}
|
|
385
|
+
candidates: list[tuple[Message, set[str]]] = []
|
|
386
|
+
for message in working_messages:
|
|
387
|
+
if id(message) in previous_message_ids:
|
|
388
|
+
continue
|
|
389
|
+
|
|
390
|
+
annotation = _read_group_annotation_raw(message)
|
|
391
|
+
if annotation is None:
|
|
392
|
+
continue
|
|
393
|
+
summarized_message_ids: Any = annotation.get(SUMMARY_OF_MESSAGE_IDS_KEY)
|
|
394
|
+
if (
|
|
395
|
+
message.message_id
|
|
396
|
+
and isinstance(summarized_message_ids, list)
|
|
397
|
+
and summarized_message_ids
|
|
398
|
+
and all(isinstance(value, str) for value in cast("list[Any]", summarized_message_ids))
|
|
399
|
+
):
|
|
400
|
+
candidates.append((message, set(cast("list[str]", summarized_message_ids))))
|
|
401
|
+
|
|
402
|
+
dependencies = {message.message_id: summary_ids for message, summary_ids in candidates if message.message_id}
|
|
403
|
+
supported_ids = set(source_message_ids)
|
|
404
|
+
pending_ids = set(dependencies)
|
|
405
|
+
while pending_ids:
|
|
406
|
+
newly_supported = {summary_id for summary_id in pending_ids if dependencies[summary_id].issubset(supported_ids)}
|
|
407
|
+
if not newly_supported:
|
|
408
|
+
break
|
|
409
|
+
supported_ids.update(newly_supported)
|
|
410
|
+
pending_ids.difference_update(newly_supported)
|
|
411
|
+
|
|
412
|
+
accepted_ids = set(dependencies).difference(pending_ids)
|
|
413
|
+
for message in [*source_messages, *(candidate for candidate, _ in candidates)]:
|
|
414
|
+
annotation = _read_group_annotation_raw(message)
|
|
415
|
+
if annotation is None:
|
|
416
|
+
continue
|
|
417
|
+
summary_id = annotation.get(SUMMARIZED_BY_SUMMARY_ID_KEY)
|
|
418
|
+
if not isinstance(summary_id, str) or summary_id in accepted_ids or summary_id in source_message_ids:
|
|
419
|
+
continue
|
|
420
|
+
annotation.pop(SUMMARIZED_BY_SUMMARY_ID_KEY, None)
|
|
421
|
+
message.additional_properties.pop(EXCLUDED_KEY, None)
|
|
422
|
+
message.additional_properties.pop(EXCLUDE_REASON_KEY, None)
|
|
423
|
+
|
|
424
|
+
def source_dependencies(summary_id: str) -> set[str]:
|
|
425
|
+
expanded: set[str] = set()
|
|
426
|
+
for dependency_id in dependencies[summary_id]:
|
|
427
|
+
if dependency_id in source_message_ids:
|
|
428
|
+
expanded.add(dependency_id)
|
|
429
|
+
elif dependency_id in accepted_ids:
|
|
430
|
+
expanded.update(source_dependencies(dependency_id))
|
|
431
|
+
return expanded
|
|
432
|
+
|
|
433
|
+
for message, _ in candidates:
|
|
434
|
+
if message.message_id not in accepted_ids:
|
|
435
|
+
continue
|
|
436
|
+
summarized_source_ids = source_dependencies(message.message_id)
|
|
437
|
+
insertion_index = min(
|
|
438
|
+
(
|
|
439
|
+
index
|
|
440
|
+
for index, source_message in enumerate(source_messages)
|
|
441
|
+
if source_message.message_id in summarized_source_ids
|
|
442
|
+
),
|
|
443
|
+
default=len(source_messages),
|
|
444
|
+
)
|
|
445
|
+
source_messages.insert(insertion_index, message)
|
|
446
|
+
|
|
447
|
+
|
|
378
448
|
def _write_group_annotation(
|
|
379
449
|
message: Message,
|
|
380
450
|
*,
|
|
@@ -802,8 +872,8 @@ class TruncationStrategy:
|
|
|
802
872
|
- token count when ``tokenizer`` is provided
|
|
803
873
|
- included message count when ``tokenizer`` is not provided
|
|
804
874
|
Compaction triggers when the metric exceeds ``max_n`` and trims toward
|
|
805
|
-
``compact_to``.
|
|
806
|
-
|
|
875
|
+
``compact_to``. Protected groups are never excluded, so the result may
|
|
876
|
+
remain above ``compact_to`` when those groups alone exceed it.
|
|
807
877
|
"""
|
|
808
878
|
|
|
809
879
|
def __init__(
|
|
@@ -813,6 +883,7 @@ class TruncationStrategy:
|
|
|
813
883
|
compact_to: int,
|
|
814
884
|
tokenizer: TokenizerProtocol | None = None,
|
|
815
885
|
preserve_system: bool = True,
|
|
886
|
+
preserve_first_user_group: bool = False,
|
|
816
887
|
) -> None:
|
|
817
888
|
"""Create a truncation strategy.
|
|
818
889
|
|
|
@@ -824,6 +895,8 @@ class TruncationStrategy:
|
|
|
824
895
|
tokenizer: Optional tokenizer used for token-based truncation.
|
|
825
896
|
preserve_system: When True, system groups remain included and only
|
|
826
897
|
non-system groups are eligible for exclusion.
|
|
898
|
+
preserve_first_user_group: When True, the earliest user group
|
|
899
|
+
remains included along with the minimum retained group.
|
|
827
900
|
"""
|
|
828
901
|
if max_n <= 0:
|
|
829
902
|
raise ValueError("max_n must be greater than 0.")
|
|
@@ -835,6 +908,7 @@ class TruncationStrategy:
|
|
|
835
908
|
self.compact_to = compact_to
|
|
836
909
|
self.tokenizer = tokenizer
|
|
837
910
|
self.preserve_system = preserve_system
|
|
911
|
+
self.preserve_first_user_group = preserve_first_user_group
|
|
838
912
|
|
|
839
913
|
async def __call__(self, messages: list[Message]) -> bool:
|
|
840
914
|
ordered_group_ids = _ordered_group_ids_from_annotations(messages)
|
|
@@ -850,6 +924,13 @@ class TruncationStrategy:
|
|
|
850
924
|
protected_ids: set[str] = set()
|
|
851
925
|
if self.preserve_system:
|
|
852
926
|
protected_ids = {group_id for group_id in ordered_group_ids if kinds.get(group_id) == "system"}
|
|
927
|
+
if self.preserve_first_user_group:
|
|
928
|
+
first_user_group_id = next(
|
|
929
|
+
(group_id for group_id in ordered_group_ids if kinds.get(group_id) == "user"),
|
|
930
|
+
None,
|
|
931
|
+
)
|
|
932
|
+
if first_user_group_id is not None:
|
|
933
|
+
protected_ids.add(first_user_group_id)
|
|
853
934
|
protected_ids.update(_minimum_retained_group_ids(messages, ordered_group_ids, kinds))
|
|
854
935
|
|
|
855
936
|
changed = False
|
|
@@ -1472,6 +1553,50 @@ class TokenBudgetComposedStrategy:
|
|
|
1472
1553
|
return changed
|
|
1473
1554
|
|
|
1474
1555
|
|
|
1556
|
+
async def _run_compaction_strategy(
|
|
1557
|
+
messages: list[Message],
|
|
1558
|
+
*,
|
|
1559
|
+
strategy: CompactionStrategy,
|
|
1560
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
1561
|
+
phase: str,
|
|
1562
|
+
) -> bool:
|
|
1563
|
+
"""Apply a strategy and log aggregate metrics when it changes context."""
|
|
1564
|
+
resolved_tokenizer = tokenizer
|
|
1565
|
+
if resolved_tokenizer is None:
|
|
1566
|
+
strategy_tokenizer = getattr(strategy, "tokenizer", None)
|
|
1567
|
+
if isinstance(strategy_tokenizer, TokenizerProtocol):
|
|
1568
|
+
resolved_tokenizer = strategy_tokenizer
|
|
1569
|
+
|
|
1570
|
+
annotate_message_groups(messages)
|
|
1571
|
+
if resolved_tokenizer is not None:
|
|
1572
|
+
annotate_token_counts(messages, tokenizer=resolved_tokenizer)
|
|
1573
|
+
before_message_count = _count_included_messages(messages)
|
|
1574
|
+
before_token_count = included_token_count(messages) if resolved_tokenizer is not None else None
|
|
1575
|
+
|
|
1576
|
+
changed = await strategy(messages)
|
|
1577
|
+
if not changed:
|
|
1578
|
+
return False
|
|
1579
|
+
|
|
1580
|
+
annotate_message_groups(messages)
|
|
1581
|
+
if resolved_tokenizer is not None:
|
|
1582
|
+
annotate_token_counts(messages, tokenizer=resolved_tokenizer)
|
|
1583
|
+
after_message_count = _count_included_messages(messages)
|
|
1584
|
+
after_token_count = included_token_count(messages) if resolved_tokenizer is not None else None
|
|
1585
|
+
strategy_name = getattr(strategy, "__name__", type(strategy).__name__)
|
|
1586
|
+
logger.info(
|
|
1587
|
+
"Compaction applied",
|
|
1588
|
+
extra={
|
|
1589
|
+
"compaction_phase": phase,
|
|
1590
|
+
"compaction_strategy": strategy_name,
|
|
1591
|
+
"compaction_included_messages_before": before_message_count,
|
|
1592
|
+
"compaction_included_messages_after": after_message_count,
|
|
1593
|
+
"compaction_included_tokens_before": before_token_count,
|
|
1594
|
+
"compaction_included_tokens_after": after_token_count,
|
|
1595
|
+
},
|
|
1596
|
+
)
|
|
1597
|
+
return True
|
|
1598
|
+
|
|
1599
|
+
|
|
1475
1600
|
async def apply_compaction(
|
|
1476
1601
|
messages: list[Message],
|
|
1477
1602
|
*,
|
|
@@ -1481,10 +1606,12 @@ async def apply_compaction(
|
|
|
1481
1606
|
"""Apply configured compaction and return projected model-input messages."""
|
|
1482
1607
|
if strategy is None:
|
|
1483
1608
|
return messages
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1609
|
+
await _run_compaction_strategy(
|
|
1610
|
+
messages,
|
|
1611
|
+
strategy=strategy,
|
|
1612
|
+
tokenizer=tokenizer,
|
|
1613
|
+
phase="in_run",
|
|
1614
|
+
)
|
|
1488
1615
|
return project_included_messages(messages)
|
|
1489
1616
|
|
|
1490
1617
|
|
|
@@ -1579,10 +1706,12 @@ class CompactionProvider(ContextProvider):
|
|
|
1579
1706
|
if not all_messages:
|
|
1580
1707
|
return
|
|
1581
1708
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1709
|
+
await _run_compaction_strategy(
|
|
1710
|
+
all_messages,
|
|
1711
|
+
strategy=self.before_strategy,
|
|
1712
|
+
tokenizer=self.tokenizer,
|
|
1713
|
+
phase="before_run",
|
|
1714
|
+
)
|
|
1586
1715
|
|
|
1587
1716
|
projected = project_included_messages(all_messages)
|
|
1588
1717
|
projected_set = {id(m) for m in projected}
|
|
@@ -1611,10 +1740,12 @@ class CompactionProvider(ContextProvider):
|
|
|
1611
1740
|
return
|
|
1612
1741
|
stored_messages: list[Message] = raw_messages # type: ignore[assignment]
|
|
1613
1742
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1743
|
+
await _run_compaction_strategy(
|
|
1744
|
+
stored_messages,
|
|
1745
|
+
strategy=self.after_strategy,
|
|
1746
|
+
tokenizer=self.tokenizer,
|
|
1747
|
+
phase="after_run",
|
|
1748
|
+
)
|
|
1618
1749
|
|
|
1619
1750
|
# Keep all messages (including excluded) in storage so annotations are
|
|
1620
1751
|
# preserved. The history provider's ``skip_excluded`` flag controls
|
|
@@ -1632,9 +1763,10 @@ class ContextWindowCompactionStrategy:
|
|
|
1632
1763
|
2. **Truncation** — removes oldest non-system groups when included tokens
|
|
1633
1764
|
exceed ``truncation_threshold`` of the input budget.
|
|
1634
1765
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
is
|
|
1766
|
+
Each phase checks its threshold explicitly. Token counts are refreshed
|
|
1767
|
+
after tool-result eviction before deciding whether destructive truncation
|
|
1768
|
+
is necessary. If protected groups still exceed the input budget after
|
|
1769
|
+
truncation, the strategy preserves them and emits a structured warning.
|
|
1638
1770
|
|
|
1639
1771
|
Examples:
|
|
1640
1772
|
.. code-block:: python
|
|
@@ -1663,6 +1795,7 @@ class ContextWindowCompactionStrategy:
|
|
|
1663
1795
|
tool_eviction_threshold: float = DEFAULT_TOOL_EVICTION_THRESHOLD,
|
|
1664
1796
|
truncation_threshold: float = DEFAULT_TRUNCATION_THRESHOLD,
|
|
1665
1797
|
keep_last_tool_call_groups: int = 4,
|
|
1798
|
+
preserve_first_user_group: bool = False,
|
|
1666
1799
|
) -> None:
|
|
1667
1800
|
"""Create a context-window compaction strategy.
|
|
1668
1801
|
|
|
@@ -1681,6 +1814,8 @@ class ContextWindowCompactionStrategy:
|
|
|
1681
1814
|
keep_last_tool_call_groups: Number of most recent tool-call groups
|
|
1682
1815
|
to retain verbatim during tool eviction. Older groups are
|
|
1683
1816
|
collapsed into summaries. Defaults to 4.
|
|
1817
|
+
preserve_first_user_group: Whether destructive truncation preserves
|
|
1818
|
+
the earliest user group. Defaults to False.
|
|
1684
1819
|
|
|
1685
1820
|
Raises:
|
|
1686
1821
|
ValueError: If thresholds are out of range or inconsistent.
|
|
@@ -1706,24 +1841,18 @@ class ContextWindowCompactionStrategy:
|
|
|
1706
1841
|
self.input_budget_tokens = input_budget
|
|
1707
1842
|
self.tool_eviction_threshold = tool_eviction_threshold
|
|
1708
1843
|
self.truncation_threshold = truncation_threshold
|
|
1844
|
+
self.tokenizer = resolved_tokenizer
|
|
1845
|
+
self._tool_eviction_tokens = tool_eviction_tokens
|
|
1846
|
+
self._truncation_tokens = truncation_tokens
|
|
1709
1847
|
|
|
1710
|
-
self._tool_eviction =
|
|
1711
|
-
|
|
1712
|
-
tokenizer=resolved_tokenizer,
|
|
1713
|
-
strategies=[
|
|
1714
|
-
ToolResultCompactionStrategy(keep_last_tool_call_groups=keep_last_tool_call_groups),
|
|
1715
|
-
],
|
|
1848
|
+
self._tool_eviction = ToolResultCompactionStrategy(
|
|
1849
|
+
keep_last_tool_call_groups=keep_last_tool_call_groups,
|
|
1716
1850
|
)
|
|
1717
|
-
self._truncation =
|
|
1718
|
-
|
|
1851
|
+
self._truncation = TruncationStrategy(
|
|
1852
|
+
max_n=truncation_tokens,
|
|
1853
|
+
compact_to=tool_eviction_tokens,
|
|
1719
1854
|
tokenizer=resolved_tokenizer,
|
|
1720
|
-
|
|
1721
|
-
TruncationStrategy(
|
|
1722
|
-
max_n=truncation_tokens,
|
|
1723
|
-
compact_to=tool_eviction_tokens,
|
|
1724
|
-
tokenizer=resolved_tokenizer,
|
|
1725
|
-
),
|
|
1726
|
-
],
|
|
1855
|
+
preserve_first_user_group=preserve_first_user_group,
|
|
1727
1856
|
)
|
|
1728
1857
|
|
|
1729
1858
|
async def __call__(self, messages: list[Message]) -> bool:
|
|
@@ -1732,8 +1861,28 @@ class ContextWindowCompactionStrategy:
|
|
|
1732
1861
|
Returns:
|
|
1733
1862
|
True if compaction changed message inclusion; otherwise False.
|
|
1734
1863
|
"""
|
|
1735
|
-
|
|
1736
|
-
|
|
1864
|
+
annotate_message_groups(messages)
|
|
1865
|
+
annotate_token_counts(messages, tokenizer=self.tokenizer)
|
|
1866
|
+
|
|
1867
|
+
changed = False
|
|
1868
|
+
if included_token_count(messages) > self._tool_eviction_tokens:
|
|
1869
|
+
changed = await self._tool_eviction(messages)
|
|
1870
|
+
annotate_message_groups(messages)
|
|
1871
|
+
annotate_token_counts(messages, tokenizer=self.tokenizer)
|
|
1872
|
+
|
|
1873
|
+
if included_token_count(messages) > self._truncation_tokens:
|
|
1874
|
+
changed = (await self._truncation(messages)) or changed
|
|
1875
|
+
remaining_tokens = included_token_count(messages)
|
|
1876
|
+
if remaining_tokens > self.input_budget_tokens:
|
|
1877
|
+
logger.warning(
|
|
1878
|
+
"Compaction could not fit protected messages within the input budget",
|
|
1879
|
+
extra={
|
|
1880
|
+
"compaction_strategy": type(self).__name__,
|
|
1881
|
+
"compaction_included_tokens_after": remaining_tokens,
|
|
1882
|
+
"compaction_input_budget_tokens": self.input_budget_tokens,
|
|
1883
|
+
},
|
|
1884
|
+
)
|
|
1885
|
+
return changed
|
|
1737
1886
|
|
|
1738
1887
|
|
|
1739
1888
|
__all__ = [
|
{agent_framework_core-1.16.0 → agent_framework_core-1.17.0}/agent_framework/_harness/_agent.py
RENAMED
|
@@ -20,6 +20,7 @@ from .._agents import Agent, SupportsAgentRun
|
|
|
20
20
|
from .._clients import SupportsShellTool, SupportsWebSearchTool
|
|
21
21
|
from .._compaction import CompactionProvider, ContextWindowCompactionStrategy
|
|
22
22
|
from .._feature_stage import ExperimentalFeature, warn_experimental_feature
|
|
23
|
+
from .._middleware import _copy_middleware_sequence # pyright: ignore[reportPrivateUsage]
|
|
23
24
|
from .._sessions import ContextProvider, HistoryProvider, InMemoryHistoryProvider, MessageInjectionMiddleware
|
|
24
25
|
from .._skills import SkillsProvider
|
|
25
26
|
from .._telemetry import FeatureIndex, mark_feature_used
|
|
@@ -347,7 +348,7 @@ def create_harness_agent(
|
|
|
347
348
|
loop_max_iterations: int | None = DEFAULT_MAX_ITERATIONS,
|
|
348
349
|
otel_provider_name: str | None = None,
|
|
349
350
|
context_providers: Sequence[ContextProvider] | None = None,
|
|
350
|
-
middleware:
|
|
351
|
+
middleware: Sequence[MiddlewareTypes] | None = None,
|
|
351
352
|
default_options: Mapping[str, Any] | None = None,
|
|
352
353
|
) -> Agent[OptionsCoT]:
|
|
353
354
|
"""Create a pre-configured agent with batteries included.
|
|
@@ -669,11 +670,8 @@ def create_harness_agent(
|
|
|
669
670
|
# Message injection is always on. It is a no-op when no messages are queued for the session,
|
|
670
671
|
# so there is no opt-out.
|
|
671
672
|
assembled_middleware.append(MessageInjectionMiddleware())
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
from .._middleware import _as_middleware_list # pyright: ignore[reportPrivateUsage]
|
|
675
|
-
|
|
676
|
-
assembled_middleware.extend(_as_middleware_list(middleware))
|
|
673
|
+
if middleware is not None:
|
|
674
|
+
assembled_middleware.extend(_copy_middleware_sequence(middleware))
|
|
677
675
|
|
|
678
676
|
agent = Agent(
|
|
679
677
|
client,
|