Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/strands/event_loop/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ def handle_content_block_start(event: ContentBlockStartEvent) -> dict[str, Any]:
current_tool_use["toolUseId"] = tool_use_data["toolUseId"]
current_tool_use["name"] = tool_use_data["name"]
current_tool_use["input"] = ""
if "reasoningSignature" in tool_use_data:
current_tool_use["reasoningSignature"] = tool_use_data["reasoningSignature"]

return current_tool_use

Expand Down Expand Up @@ -286,6 +288,8 @@ def handle_content_block_stop(state: dict[str, Any]) -> dict[str, Any]:
name=tool_use_name,
input=current_tool_use["input"],
)
if "reasoningSignature" in current_tool_use:
tool_use["reasoningSignature"] = current_tool_use["reasoningSignature"]
content.append({"toolUse": tool_use})
state["current_tool_use"] = {}

Expand Down
81 changes: 56 additions & 25 deletions src/strands/models/gemini.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Docs: https://ai.google.dev/api
"""

import base64
import json
import logging
import mimetypes
Expand Down Expand Up @@ -135,7 +136,9 @@ def _get_client(self) -> genai.Client:
return genai.Client(**self.client_args)

def _format_request_content_part(
self, content: ContentBlock, tool_use_id_to_name: dict[str, str]
self,
content: ContentBlock,
tool_use_id_to_name: dict[str, str],
) -> genai.types.Part:
"""Format content block into a Gemini part instance.

Expand Down Expand Up @@ -173,7 +176,7 @@ def _format_request_content_part(
return genai.types.Part(
text=content["reasoningContent"]["reasoningText"]["text"],
thought=True,
thought_signature=thought_signature.encode("utf-8") if thought_signature else None,
thought_signature=base64.b64decode(thought_signature) if thought_signature else None,
)

if "text" in content:
Expand Down Expand Up @@ -202,14 +205,18 @@ def _format_request_content_part(
)

if "toolUse" in content:
tool_use_id_to_name[content["toolUse"]["toolUseId"]] = content["toolUse"]["name"]
tool_use_id = content["toolUse"]["toolUseId"]
tool_use_id_to_name[tool_use_id] = content["toolUse"]["name"]

reasoning_signature = content["toolUse"].get("reasoningSignature")

return genai.types.Part(
function_call=genai.types.FunctionCall(
args=content["toolUse"]["input"],
id=content["toolUse"]["toolUseId"],
id=tool_use_id,
name=content["toolUse"]["name"],
),
thought_signature=base64.b64decode(reasoning_signature) if reasoning_signature else None,
)

raise TypeError(f"content_type=<{next(iter(content))}> | unsupported type")
Expand Down Expand Up @@ -259,20 +266,27 @@ def _format_request_tools(self, tool_specs: list[ToolSpec] | None) -> list[genai
Return:
Gemini tool list.
"""
tools = [
genai.types.Tool(
function_declarations=[
genai.types.FunctionDeclaration(
description=tool_spec["description"],
name=tool_spec["name"],
parameters_json_schema=tool_spec["inputSchema"]["json"],
)
for tool_spec in tool_specs or []
],
),
]
tools = []

# Only add function declarations tool if there are tool specs
if tool_specs:
tools.append(
genai.types.Tool(
function_declarations=[
genai.types.FunctionDeclaration(
description=tool_spec["description"],
name=tool_spec["name"],
parameters_json_schema=tool_spec["inputSchema"]["json"],
)
for tool_spec in tool_specs
],
),
)

# Add any Gemini-specific tools
if self.config.get("gemini_tools"):
tools.extend(self.config["gemini_tools"])

return tools

def _format_request_config(
Expand All @@ -293,11 +307,19 @@ def _format_request_config(
Returns:
Gemini request config.
"""
return genai.types.GenerateContentConfig(
system_instruction=system_prompt,
tools=self._format_request_tools(tool_specs),
tools = self._format_request_tools(tool_specs)

# Build config kwargs, only including tools if there are any
config_kwargs = {
"system_instruction": system_prompt,
**(params or {}),
)
}

# Only include tools parameter if there are actual tools to pass
if tools:
config_kwargs["tools"] = tools

return genai.types.GenerateContentConfig(**config_kwargs)

def _format_request(
self,
Expand Down Expand Up @@ -349,13 +371,18 @@ def _format_chunk(self, event: dict[str, Any]) -> StreamEvent:
# Use Gemini's provided ID or generate one if missing
tool_use_id = function_call.id or f"tooluse_{secrets.token_urlsafe(16)}"

tool_use_start: dict[str, Any] = {
"name": function_call.name,
"toolUseId": tool_use_id,
}
if event["data"].thought_signature:
tool_use_start["reasoningSignature"] = base64.b64encode(
event["data"].thought_signature
).decode("ascii")
return {
"contentBlockStart": {
"start": {
"toolUse": {
"name": function_call.name,
"toolUseId": tool_use_id,
},
"toolUse": tool_use_start, # type: ignore[typeddict-item]
},
},
}
Expand All @@ -379,7 +406,11 @@ def _format_chunk(self, event: dict[str, Any]) -> StreamEvent:
"reasoningContent": {
"text": event["data"].text,
**(
{"signature": event["data"].thought_signature.decode("utf-8")}
{
"signature": base64.b64encode(event["data"].thought_signature).decode(
"ascii"
)
}
if event["data"].thought_signature
else {}
),
Expand Down
4 changes: 3 additions & 1 deletion src/strands/types/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from typing import Literal

from typing_extensions import TypedDict
from typing_extensions import NotRequired, TypedDict

from .citations import CitationsContentBlock
from .media import DocumentContent, ImageContent, VideoContent
Expand Down Expand Up @@ -129,10 +129,12 @@ class ContentBlockStartToolUse(TypedDict):
Attributes:
name: The name of the tool that the model is requesting to use.
toolUseId: The ID for the tool request.
reasoningSignature: Token that ties the model's reasoning to this tool call.
"""

name: str
toolUseId: str
reasoningSignature: NotRequired[str]


class ContentBlockStart(TypedDict, total=False):
Expand Down
2 changes: 2 additions & 0 deletions src/strands/types/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ class ToolUse(TypedDict):
Can be any JSON-serializable type.
name: The name of the tool to invoke.
toolUseId: A unique identifier for this specific tool use request.
reasoningSignature: Token that ties the model's reasoning to this tool call.
"""

input: Any
name: str
toolUseId: str
reasoningSignature: NotRequired[str]


class ToolResultContent(TypedDict, total=False):
Expand Down
Loading
Loading