Skip to content

Commit 41b643e

Browse files
authored
Merge branch 'main' into fix/bedrock-stream-task-leak
2 parents 412f152 + 1232230 commit 41b643e

27 files changed

Lines changed: 1443 additions & 204 deletions

AGENTS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,11 @@ strands-agents/
130130
│ │
131131
│ ├── plugins/ # Plugin system
132132
│ │ ├── plugin.py # Plugin base class
133+
│ │ ├── multiagent_plugin.py # MultiAgentPlugin base class
133134
│ │ ├── decorator.py # @hook decorator
134-
│ │ └── registry.py # PluginRegistry for tracking plugins
135+
│ │ ├── registry.py # PluginRegistry for tracking agent plugins
136+
│ │ ├── multiagent_registry.py # Registry for tracking orchestrator plugins
137+
│ │ └── _discovery.py # Shared hook/tool discovery utilities
135138
│ │
136139
│ ├── handlers/ # Event handlers
137140
│ │ └── callback_handler.py # Callback handling

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<a href="https://github.com/strands-agents/sdk-python/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/strands-agents/sdk-python"/></a>
2121
<a href="https://pypi.org/project/strands-agents/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/strands-agents"/></a>
2222
<a href="https://python.org"><img alt="Python versions" src="https://img.shields.io/pypi/pyversions/strands-agents"/></a>
23+
<a href="https://discord.gg/strands"><img alt="Strands Discord" src="https://img.shields.io/badge/Discord-Strands-5865F2?logo=discord&logoColor=white"/></a>
2324
</div>
2425

2526
<p>
@@ -316,6 +317,9 @@ We welcome contributions! See our [Contributing Guide](CONTRIBUTING.md) for deta
316317
- Code of Conduct
317318
- Reporting of security issues
318319

320+
## Stay in touch with the team
321+
Come meet the Strands team and other users on [**Discord**](https://discord.com/invite/strands)
322+
319323
## License
320324

321325
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ a2a = [
6868
"a2a-sdk[sql]>=0.3.0,<0.4.0",
6969
"uvicorn>=0.34.2,<1.0.0",
7070
"httpx>=0.28.1,<1.0.0",
71-
"fastapi>=0.115.12,<1.0.0",
72-
"starlette>=0.46.2,<1.0.0",
71+
"fastapi>=0.133.0,<1.0.0",
72+
"starlette>=1.0.0,<2.0.0",
7373
]
7474

7575
bidi = [

src/strands/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .agent.agent import Agent
55
from .agent.base import AgentBase
66
from .event_loop._retry import ModelRetryStrategy
7-
from .plugins import Plugin
7+
from .plugins import MultiAgentPlugin, Plugin
88
from .tools.decorator import tool
99
from .types._snapshot import Snapshot
1010
from .types.tools import ToolContext
@@ -17,6 +17,7 @@
1717
"agent",
1818
"models",
1919
"ModelRetryStrategy",
20+
"MultiAgentPlugin",
2021
"Plugin",
2122
"Skill",
2223
"Snapshot",

src/strands/models/anthropic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ class AnthropicConfig(BaseModelConfig, total=False):
5858
params: Additional model parameters (e.g., temperature).
5959
For a complete list of supported parameters, see https://docs.anthropic.com/en/api/messages.
6060
use_native_token_count: Whether to use the native Anthropic count_tokens API.
61-
When True (default), count_tokens() calls the Anthropic API for accurate counts.
62-
When False, skips the API call and uses the local estimator.
61+
When True, count_tokens() calls the Anthropic API for accurate counts.
62+
When False (default), skips the API call and uses the local estimator.
6363
"""
6464

6565
max_tokens: Required[int]
@@ -398,7 +398,7 @@ async def count_tokens(
398398
Returns:
399399
Total input token count.
400400
"""
401-
if self.config.get("use_native_token_count") is False:
401+
if self.config.get("use_native_token_count") is not True:
402402
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
403403

404404
try:

src/strands/models/bedrock.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@
5555
"anthropic.claude",
5656
]
5757

58-
# Cache of model IDs that do not support the CountTokens API.
59-
_UNSUPPORTED_COUNT_TOKENS_MODELS: set[str] = set()
58+
# Cache of model IDs for which CountTokens API calls should be skipped.
59+
_SKIP_COUNT_TOKENS_MODELS: set[str] = set()
6060

6161

62-
def _clear_unsupported_count_tokens_cache() -> None:
63-
"""Clear the cache of model IDs that do not support the CountTokens API."""
64-
_UNSUPPORTED_COUNT_TOKENS_MODELS.clear()
62+
def _clear_skip_count_tokens_cache() -> None:
63+
"""Clear the cache of model IDs for which CountTokens API calls should be skipped."""
64+
_SKIP_COUNT_TOKENS_MODELS.clear()
6565

6666

6767
def _suppress_task_exception(task: "asyncio.Task[None]") -> None:
@@ -124,8 +124,8 @@ class BedrockConfig(BaseModelConfig, total=False):
124124
temperature: Controls randomness in generation (higher = more random)
125125
top_p: Controls diversity via nucleus sampling (alternative to temperature)
126126
use_native_token_count: Whether to use the native Bedrock CountTokens API.
127-
When True (default), count_tokens() calls the Bedrock API for accurate counts.
128-
When False, skips the API call and uses the local estimator.
127+
When True, count_tokens() calls the Bedrock API for accurate counts.
128+
When False (default), skips the API call and uses the local estimator.
129129
"""
130130

131131
additional_args: dict[str, Any] | None
@@ -804,12 +804,12 @@ async def count_tokens(
804804
Returns:
805805
Total input token count.
806806
"""
807-
if self.config.get("use_native_token_count") is False:
807+
if self.config.get("use_native_token_count") is not True:
808808
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
809809

810810
model_id: str = self.config["model_id"]
811811

812-
if model_id in _UNSUPPORTED_COUNT_TOKENS_MODELS:
812+
if model_id in _SKIP_COUNT_TOKENS_MODELS:
813813
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
814814

815815
try:
@@ -839,6 +839,17 @@ async def count_tokens(
839839
return total_tokens
840840
except Exception as e:
841841
if (
842+
isinstance(e, ClientError)
843+
and e.response.get("Error", {}).get("Code") == "AccessDeniedException"
844+
):
845+
logger.warning(
846+
"model_id=<%s> | bedrock:CountTokens permission denied,"
847+
" falling back to heuristic estimation: %s",
848+
model_id,
849+
e,
850+
)
851+
_SKIP_COUNT_TOKENS_MODELS.add(model_id)
852+
elif (
842853
isinstance(e, ClientError)
843854
and e.response.get("Error", {}).get("Code") == "ValidationException"
844855
and "doesn't support counting tokens" in str(e)
@@ -848,7 +859,7 @@ async def count_tokens(
848859
" falling back to estimation",
849860
model_id,
850861
)
851-
_UNSUPPORTED_COUNT_TOKENS_MODELS.add(model_id)
862+
_SKIP_COUNT_TOKENS_MODELS.add(model_id)
852863
else:
853864
logger.debug(
854865
"model_id=<%s>, error=<%s> | native token counting failed, falling back to estimation",

src/strands/models/gemini.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ class GeminiConfig(BaseModelConfig, total=False):
5050
For a complete list of supported tools, see
5151
https://ai.google.dev/api/caching#Tool
5252
use_native_token_count: Whether to use the native Gemini count_tokens API.
53-
When True (default), count_tokens() calls the Gemini API for accurate counts.
54-
When False, skips the API call and uses the local estimator.
53+
When True, count_tokens() calls the Gemini API for accurate counts.
54+
When False (default), skips the API call and uses the local estimator.
5555
"""
5656

5757
model_id: Required[str]
@@ -461,7 +461,7 @@ async def count_tokens(
461461
Returns:
462462
Total input token count.
463463
"""
464-
if self.config.get("use_native_token_count") is False:
464+
if self.config.get("use_native_token_count") is not True:
465465
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
466466

467467
try:

src/strands/models/llamacpp.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ class LlamaCppConfig(BaseModelConfig, total=False):
126126
- slot_id: Slot ID for parallel inference
127127
- samplers: Custom sampler order
128128
use_native_token_count: Whether to use the native llama.cpp /tokenize endpoint.
129-
When True (default), count_tokens() calls the server's tokenize endpoint for accurate counts.
130-
When False, skips the API call and uses the local estimator.
129+
When True, count_tokens() calls the server's tokenize endpoint for accurate counts.
130+
When False (default), skips the API call and uses the local estimator.
131131
"""
132132

133133
model_id: str
@@ -537,7 +537,7 @@ async def count_tokens(
537537
Returns:
538538
Total input token count.
539539
"""
540-
if self.config.get("use_native_token_count") is False:
540+
if self.config.get("use_native_token_count") is not True:
541541
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
542542

543543
try:

src/strands/models/ollama.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def format_chunk(self, event: dict[str, Any]) -> StreamEvent:
280280
"totalTokens": event["data"].eval_count + event["data"].prompt_eval_count,
281281
},
282282
"metrics": {
283-
"latencyMs": event["data"].total_duration / 1e6,
283+
"latencyMs": int(event["data"].total_duration / 1e6),
284284
},
285285
},
286286
}

src/strands/models/openai_responses.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ class OpenAIResponsesConfig(BaseModelConfig, total=False):
137137
When True, the server stores conversation history and the client does not need to
138138
send the full message history with each request. Defaults to False.
139139
use_native_token_count: Whether to use the native OpenAI input_tokens.count API.
140-
When True (default), count_tokens() calls the OpenAI API for accurate counts.
141-
When False, skips the API call and uses the local estimator.
140+
When True, count_tokens() calls the OpenAI API for accurate counts.
141+
When False (default), skips the API call and uses the local estimator.
142142
"""
143143

144144
model_id: str
@@ -242,7 +242,7 @@ async def count_tokens(
242242
Returns:
243243
Total input token count.
244244
"""
245-
if self.config.get("use_native_token_count") is False:
245+
if self.config.get("use_native_token_count") is not True:
246246
return await super().count_tokens(messages, tool_specs, system_prompt, system_prompt_content)
247247

248248
try:

0 commit comments

Comments
 (0)