Merge pull request #1904 from pipecat-ai/mb/aws-bedrock-no-op-tool
Add no_op tool to AWSBedrockLLMService
This commit is contained in:
@@ -13,6 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
`DailyTransport.stop_transcription()` to be able to start and stop Daily
|
`DailyTransport.stop_transcription()` to be able to start and stop Daily
|
||||||
transcription dynamically (maybe with different settings).
|
transcription dynamically (maybe with different settings).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- In `AWSBedrockLLMService`, worked around a possible bug in AWS Bedrock where
|
||||||
|
a `toolConfig` is required if there has been previous tool use in the
|
||||||
|
messages array. This workaround includes a no_op factory function call is
|
||||||
|
used to satisfy the requirement.
|
||||||
|
|
||||||
## [0.0.68] - 2025-05-28
|
## [0.0.68] - 2025-05-28
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -606,6 +606,21 @@ class AWSBedrockLLMService(LLMService):
|
|||||||
assistant = AWSBedrockAssistantContextAggregator(context, params=assistant_params)
|
assistant = AWSBedrockAssistantContextAggregator(context, params=assistant_params)
|
||||||
return AWSBedrockContextAggregatorPair(_user=user, _assistant=assistant)
|
return AWSBedrockContextAggregatorPair(_user=user, _assistant=assistant)
|
||||||
|
|
||||||
|
def _create_no_op_tool(self):
|
||||||
|
"""Create a no-operation tool for AWS Bedrock when tool content exists but no tools are defined.
|
||||||
|
|
||||||
|
This is required because AWS Bedrock doesn't allow empty tool configurations after tools were
|
||||||
|
previously set. Other LLM vendors allow NOT_GIVEN or empty tool configurations,
|
||||||
|
but AWS Bedrock requires at least one tool to be defined.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"toolSpec": {
|
||||||
|
"name": "no_operation",
|
||||||
|
"description": "Internal placeholder function. Do not call this function.",
|
||||||
|
"inputSchema": {"json": {"type": "object", "properties": {}, "required": []}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@traced_llm
|
@traced_llm
|
||||||
async def _process_context(self, context: AWSBedrockLLMContext):
|
async def _process_context(self, context: AWSBedrockLLMContext):
|
||||||
# Usage tracking
|
# Usage tracking
|
||||||
@@ -616,6 +631,8 @@ class AWSBedrockLLMService(LLMService):
|
|||||||
cache_creation_input_tokens = 0
|
cache_creation_input_tokens = 0
|
||||||
use_completion_tokens_estimate = False
|
use_completion_tokens_estimate = False
|
||||||
|
|
||||||
|
using_noop_tool = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.push_frame(LLMFullResponseStartFrame())
|
await self.push_frame(LLMFullResponseStartFrame())
|
||||||
await self.start_processing_metrics()
|
await self.start_processing_metrics()
|
||||||
@@ -640,12 +657,28 @@ class AWSBedrockLLMService(LLMService):
|
|||||||
# Add system message
|
# Add system message
|
||||||
request_params["system"] = context.system
|
request_params["system"] = context.system
|
||||||
|
|
||||||
# Add tools if present
|
# Check if messages contain tool use or tool result content blocks
|
||||||
if context.tools:
|
has_tool_content = False
|
||||||
tool_config = {"tools": context.tools}
|
for message in context.messages:
|
||||||
|
if isinstance(message.get("content"), list):
|
||||||
|
for content_item in message["content"]:
|
||||||
|
if "toolUse" in content_item or "toolResult" in content_item:
|
||||||
|
has_tool_content = True
|
||||||
|
break
|
||||||
|
if has_tool_content:
|
||||||
|
break
|
||||||
|
|
||||||
# Add tool_choice if specified
|
# Handle tools: use current tools, or no-op if tool content exists but no current tools
|
||||||
if context.tool_choice:
|
tools = context.tools or []
|
||||||
|
if has_tool_content and not tools:
|
||||||
|
tools = [self._create_no_op_tool()]
|
||||||
|
using_noop_tool = True
|
||||||
|
|
||||||
|
if tools:
|
||||||
|
tool_config = {"tools": tools}
|
||||||
|
|
||||||
|
# Only add tool_choice if we have real tools (not just no-op)
|
||||||
|
if not using_noop_tool and context.tool_choice:
|
||||||
if context.tool_choice == "auto":
|
if context.tool_choice == "auto":
|
||||||
tool_config["toolChoice"] = {"auto": {}}
|
tool_config["toolChoice"] = {"auto": {}}
|
||||||
elif context.tool_choice == "none":
|
elif context.tool_choice == "none":
|
||||||
@@ -704,12 +737,17 @@ class AWSBedrockLLMService(LLMService):
|
|||||||
if event["messageStop"]["stopReason"] == "tool_use" and tool_use_block:
|
if event["messageStop"]["stopReason"] == "tool_use" and tool_use_block:
|
||||||
try:
|
try:
|
||||||
arguments = json.loads(json_accumulator) if json_accumulator else {}
|
arguments = json.loads(json_accumulator) if json_accumulator else {}
|
||||||
await self.call_function(
|
|
||||||
context=context,
|
# Only call function if it's not the no_operation tool
|
||||||
tool_call_id=tool_use_block["id"],
|
if not using_noop_tool:
|
||||||
function_name=tool_use_block["name"],
|
await self.call_function(
|
||||||
arguments=arguments,
|
context=context,
|
||||||
)
|
tool_call_id=tool_use_block["id"],
|
||||||
|
function_name=tool_use_block["name"],
|
||||||
|
arguments=arguments,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug("Ignoring no_operation tool call")
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.error(f"Failed to parse tool arguments: {json_accumulator}")
|
logger.error(f"Failed to parse tool arguments: {json_accumulator}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user