coco-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- coco_openai/tools/custom_tool.py +111 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""Context editing middleware.
|
|
2
|
+
|
|
3
|
+
Mirrors Anthropic's context editing capabilities by clearing older tool results once the
|
|
4
|
+
conversation grows beyond a configurable token threshold.
|
|
5
|
+
|
|
6
|
+
The implementation is intentionally model-agnostic so it can be used with any Coco
|
|
7
|
+
chat model.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
|
13
|
+
from copy import deepcopy
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
from coco_core.messages import (
|
|
18
|
+
AIMessage,
|
|
19
|
+
AnyMessage,
|
|
20
|
+
BaseMessage,
|
|
21
|
+
ToolMessage,
|
|
22
|
+
)
|
|
23
|
+
from coco_core.messages.utils import count_tokens_approximately
|
|
24
|
+
from typing_extensions import Protocol
|
|
25
|
+
|
|
26
|
+
from coco.agents.middleware.types import (
|
|
27
|
+
AgentMiddleware,
|
|
28
|
+
AgentState,
|
|
29
|
+
ContextT,
|
|
30
|
+
ModelRequest,
|
|
31
|
+
ModelResponse,
|
|
32
|
+
ResponseT,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
DEFAULT_TOOL_PLACEHOLDER = "[cleared]"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
TokenCounter = Callable[
|
|
39
|
+
[Sequence[BaseMessage]],
|
|
40
|
+
int,
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ContextEdit(Protocol):
|
|
45
|
+
"""Protocol describing a context editing strategy."""
|
|
46
|
+
|
|
47
|
+
def apply(
|
|
48
|
+
self,
|
|
49
|
+
messages: list[AnyMessage],
|
|
50
|
+
*,
|
|
51
|
+
count_tokens: TokenCounter,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Apply an edit to the message list in place."""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(slots=True)
|
|
58
|
+
class ClearToolUsesEdit(ContextEdit):
|
|
59
|
+
"""Configuration for clearing tool outputs when token limits are exceeded."""
|
|
60
|
+
|
|
61
|
+
trigger: int = 100_000
|
|
62
|
+
"""Token count that triggers the edit."""
|
|
63
|
+
|
|
64
|
+
clear_at_least: int = 0
|
|
65
|
+
"""Minimum number of tokens to reclaim when the edit runs."""
|
|
66
|
+
|
|
67
|
+
keep: int = 3
|
|
68
|
+
"""Number of most recent tool results that must be preserved."""
|
|
69
|
+
|
|
70
|
+
clear_tool_inputs: bool = False
|
|
71
|
+
"""Whether to clear the originating tool call parameters on the AI message."""
|
|
72
|
+
|
|
73
|
+
exclude_tools: Sequence[str] = ()
|
|
74
|
+
"""List of tool names to exclude from clearing."""
|
|
75
|
+
|
|
76
|
+
placeholder: str = DEFAULT_TOOL_PLACEHOLDER
|
|
77
|
+
"""Placeholder text inserted for cleared tool outputs."""
|
|
78
|
+
|
|
79
|
+
def apply(
|
|
80
|
+
self,
|
|
81
|
+
messages: list[AnyMessage],
|
|
82
|
+
*,
|
|
83
|
+
count_tokens: TokenCounter,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Apply the clear-tool-uses strategy."""
|
|
86
|
+
tokens = count_tokens(messages)
|
|
87
|
+
|
|
88
|
+
if tokens <= self.trigger:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
candidates = [
|
|
92
|
+
(idx, msg) for idx, msg in enumerate(messages) if isinstance(msg, ToolMessage)
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
if self.keep >= len(candidates):
|
|
96
|
+
candidates = []
|
|
97
|
+
elif self.keep:
|
|
98
|
+
candidates = candidates[: -self.keep]
|
|
99
|
+
|
|
100
|
+
cleared_tokens = 0
|
|
101
|
+
excluded_tools = set(self.exclude_tools)
|
|
102
|
+
|
|
103
|
+
for idx, tool_message in candidates:
|
|
104
|
+
if tool_message.response_metadata.get("context_editing", {}).get("cleared"):
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
ai_message = next(
|
|
108
|
+
(m for m in reversed(messages[:idx]) if isinstance(m, AIMessage)), None
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if ai_message is None:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
tool_call = next(
|
|
115
|
+
(
|
|
116
|
+
call
|
|
117
|
+
for call in ai_message.tool_calls
|
|
118
|
+
if call.get("id") == tool_message.tool_call_id
|
|
119
|
+
),
|
|
120
|
+
None,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if tool_call is None:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
if (tool_message.name or tool_call["name"]) in excluded_tools:
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
messages[idx] = tool_message.model_copy(
|
|
130
|
+
update={
|
|
131
|
+
"artifact": None,
|
|
132
|
+
"content": self.placeholder,
|
|
133
|
+
"response_metadata": {
|
|
134
|
+
**tool_message.response_metadata,
|
|
135
|
+
"context_editing": {
|
|
136
|
+
"cleared": True,
|
|
137
|
+
"strategy": "clear_tool_uses",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if self.clear_tool_inputs:
|
|
144
|
+
messages[messages.index(ai_message)] = self._build_cleared_tool_input_message(
|
|
145
|
+
ai_message,
|
|
146
|
+
tool_message.tool_call_id,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if self.clear_at_least > 0:
|
|
150
|
+
new_token_count = count_tokens(messages)
|
|
151
|
+
cleared_tokens = max(0, tokens - new_token_count)
|
|
152
|
+
if cleared_tokens >= self.clear_at_least:
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _build_cleared_tool_input_message(
|
|
159
|
+
message: AIMessage,
|
|
160
|
+
tool_call_id: str,
|
|
161
|
+
) -> AIMessage:
|
|
162
|
+
updated_tool_calls = []
|
|
163
|
+
cleared_any = False
|
|
164
|
+
for tool_call in message.tool_calls:
|
|
165
|
+
updated_call = dict(tool_call)
|
|
166
|
+
if updated_call.get("id") == tool_call_id:
|
|
167
|
+
updated_call["args"] = {}
|
|
168
|
+
cleared_any = True
|
|
169
|
+
updated_tool_calls.append(updated_call)
|
|
170
|
+
|
|
171
|
+
metadata = dict(getattr(message, "response_metadata", {}))
|
|
172
|
+
context_entry = dict(metadata.get("context_editing", {}))
|
|
173
|
+
if cleared_any:
|
|
174
|
+
cleared_ids = set(context_entry.get("cleared_tool_inputs", []))
|
|
175
|
+
cleared_ids.add(tool_call_id)
|
|
176
|
+
context_entry["cleared_tool_inputs"] = sorted(cleared_ids)
|
|
177
|
+
metadata["context_editing"] = context_entry
|
|
178
|
+
|
|
179
|
+
return message.model_copy(
|
|
180
|
+
update={
|
|
181
|
+
"tool_calls": updated_tool_calls,
|
|
182
|
+
"response_metadata": metadata,
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ContextEditingMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
|
|
188
|
+
"""Automatically prune tool results to manage context size.
|
|
189
|
+
|
|
190
|
+
The middleware applies a sequence of edits when the total input token count exceeds
|
|
191
|
+
configured thresholds.
|
|
192
|
+
|
|
193
|
+
Currently the `ClearToolUsesEdit` strategy is supported, aligning with Anthropic's
|
|
194
|
+
`clear_tool_uses_20250919` behavior [(read more)](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool).
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
edits: list[ContextEdit]
|
|
198
|
+
token_count_method: Literal["approximate", "model"]
|
|
199
|
+
token_counter: TokenCounter | None
|
|
200
|
+
|
|
201
|
+
def __init__(
|
|
202
|
+
self,
|
|
203
|
+
*,
|
|
204
|
+
edits: Iterable[ContextEdit] | None = None,
|
|
205
|
+
token_count_method: Literal["approximate", "model"] = "approximate", # noqa: S107
|
|
206
|
+
token_counter: TokenCounter | None = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Initialize an instance of context editing middleware.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
edits: Sequence of edit strategies to apply.
|
|
212
|
+
|
|
213
|
+
Defaults to a single `ClearToolUsesEdit` mirroring Anthropic defaults.
|
|
214
|
+
token_count_method: Whether to use approximate token counting
|
|
215
|
+
(faster, less accurate) or exact counting implemented by the
|
|
216
|
+
chat model (potentially slower, more accurate).
|
|
217
|
+
|
|
218
|
+
Ignored when `token_counter` is provided.
|
|
219
|
+
token_counter: Optional custom function counting tokens for a
|
|
220
|
+
sequence of messages. Takes precedence over
|
|
221
|
+
`token_count_method` when provided, mirroring
|
|
222
|
+
`SummarizationMiddleware`. Useful when the built-in
|
|
223
|
+
approximation is inaccurate for a workload (e.g. CJK-heavy
|
|
224
|
+
conversations) or when a provider-specific tokenizer should
|
|
225
|
+
be used without implementing
|
|
226
|
+
`get_num_tokens_from_messages`.
|
|
227
|
+
"""
|
|
228
|
+
super().__init__()
|
|
229
|
+
self.edits = list(edits or (ClearToolUsesEdit(),))
|
|
230
|
+
self.token_count_method = token_count_method
|
|
231
|
+
self.token_counter = token_counter
|
|
232
|
+
|
|
233
|
+
def _resolve_token_counter(self, request: ModelRequest[ContextT]) -> TokenCounter:
|
|
234
|
+
"""Select the token counter used to evaluate edit triggers."""
|
|
235
|
+
if self.token_counter is not None:
|
|
236
|
+
return self.token_counter
|
|
237
|
+
|
|
238
|
+
if self.token_count_method == "approximate": # noqa: S105
|
|
239
|
+
return count_tokens_approximately
|
|
240
|
+
|
|
241
|
+
system_msg = [request.system_message] if request.system_message else []
|
|
242
|
+
|
|
243
|
+
def count_tokens(messages: Sequence[BaseMessage]) -> int:
|
|
244
|
+
return request.model.get_num_tokens_from_messages(
|
|
245
|
+
system_msg + list(messages), request.tools
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return count_tokens
|
|
249
|
+
|
|
250
|
+
def wrap_model_call(
|
|
251
|
+
self,
|
|
252
|
+
request: ModelRequest[ContextT],
|
|
253
|
+
handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
|
|
254
|
+
) -> ModelResponse[ResponseT] | AIMessage:
|
|
255
|
+
"""Apply context edits before invoking the model via handler.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
request: Model request to execute (includes state and runtime).
|
|
259
|
+
handler: Async callback that executes the model request and returns
|
|
260
|
+
`ModelResponse`.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
The result of invoking the handler with potentially edited messages.
|
|
264
|
+
"""
|
|
265
|
+
if not request.messages:
|
|
266
|
+
return handler(request)
|
|
267
|
+
|
|
268
|
+
count_tokens = self._resolve_token_counter(request)
|
|
269
|
+
|
|
270
|
+
edited_messages = deepcopy(list(request.messages))
|
|
271
|
+
for edit in self.edits:
|
|
272
|
+
edit.apply(edited_messages, count_tokens=count_tokens)
|
|
273
|
+
|
|
274
|
+
return handler(request.override(messages=edited_messages))
|
|
275
|
+
|
|
276
|
+
async def awrap_model_call(
|
|
277
|
+
self,
|
|
278
|
+
request: ModelRequest[ContextT],
|
|
279
|
+
handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
|
|
280
|
+
) -> ModelResponse[ResponseT] | AIMessage:
|
|
281
|
+
"""Apply context edits before invoking the model via handler.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
request: Model request to execute (includes state and runtime).
|
|
285
|
+
handler: Async callback that executes the model request and returns
|
|
286
|
+
`ModelResponse`.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
The result of invoking the handler with potentially edited messages.
|
|
290
|
+
"""
|
|
291
|
+
if not request.messages:
|
|
292
|
+
return await handler(request)
|
|
293
|
+
|
|
294
|
+
count_tokens = self._resolve_token_counter(request)
|
|
295
|
+
|
|
296
|
+
edited_messages = deepcopy(list(request.messages))
|
|
297
|
+
for edit in self.edits:
|
|
298
|
+
edit.apply(edited_messages, count_tokens=count_tokens)
|
|
299
|
+
|
|
300
|
+
return await handler(request.override(messages=edited_messages))
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
__all__ = [
|
|
304
|
+
"ClearToolUsesEdit",
|
|
305
|
+
"ContextEditingMiddleware",
|
|
306
|
+
]
|