aidial_adapter_bedrock/llm/model/claude/v3/tools.py (93 lines of code) (raw):
import json
from typing import assert_never
from aidial_sdk.chat_completion import FunctionCall, ToolCall
from anthropic.types import ToolUseBlock
from aidial_adapter_bedrock.llm.consumer import Consumer
from aidial_adapter_bedrock.llm.errors import ValidationError
from aidial_adapter_bedrock.llm.message import (
AIFunctionCallMessage,
AIRegularMessage,
AIToolCallMessage,
BaseMessage,
HumanFunctionResultMessage,
HumanRegularMessage,
HumanToolResultMessage,
SystemMessage,
ToolMessage,
)
from aidial_adapter_bedrock.llm.tools.tools_config import ToolsMode
from aidial_adapter_bedrock.utils.log_config import bedrock_logger as log
def to_dial_function_call(block: ToolUseBlock) -> FunctionCall:
return FunctionCall(name=block.name, arguments=json.dumps(block.input))
def to_dial_tool_call(block: ToolUseBlock) -> ToolCall:
return ToolCall(
index=None,
id=block.id,
type="function",
function=to_dial_function_call(block),
)
def process_tools_block(
consumer: Consumer, block: ToolUseBlock, tools_mode: ToolsMode | None
):
match tools_mode:
case ToolsMode.TOOLS:
consumer.create_function_tool_call(to_dial_tool_call(block))
case ToolsMode.FUNCTIONS:
if consumer.has_function_call:
log.warning(
"The model generated more than one tool call. "
"Only the first one will be taken in to account."
)
else:
consumer.create_function_call(to_dial_function_call(block))
case None:
raise ValidationError(
"A model has called a tool, but no tools were given to the model in the first place."
)
case _:
assert_never(tools_mode)
def process_with_tools(
message: BaseMessage | ToolMessage, tools_mode: ToolsMode | None
) -> BaseMessage | HumanToolResultMessage | AIToolCallMessage:
"""
1. Validates, that no Functions or Tools messages are used without config
2. Validates, that client don't use Functions messages with tools config
3. Validates, that client don't use Tools messages with functions config
4. Convert Functions messages to Tools messages (Claude supports only Tools).
For tool id we just use function name
"""
if tools_mode is None:
if not isinstance(message, BaseMessage):
raise ValidationError(
"You cannot use messages with functions or tools without config. Please change your messages."
)
return message
elif tools_mode == ToolsMode.TOOLS:
if isinstance(
message, (HumanFunctionResultMessage, AIFunctionCallMessage)
):
raise ValidationError(
"You cannot use function messages with tools config."
)
return message
elif tools_mode == ToolsMode.FUNCTIONS:
match message:
case SystemMessage() | HumanRegularMessage() | AIRegularMessage():
return message
case HumanToolResultMessage() | AIToolCallMessage():
raise ValidationError(
"You cannot use tools messages with functions config."
)
case AIFunctionCallMessage():
return AIToolCallMessage(
content=message.content,
calls=[
ToolCall(
index=None,
id=message.call.name,
type="function",
function=message.call,
)
],
)
case HumanFunctionResultMessage():
return HumanToolResultMessage(
id=message.name, content=message.content
)
case _:
assert_never(message)
else:
assert_never(tools_mode)