diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py
index 9be8970489..21a86a9f79 100644
--- a/litellm/llms/prompt_templates/factory.py
+++ b/litellm/llms/prompt_templates/factory.py
@@ -551,6 +551,58 @@ def convert_to_anthropic_image_obj(openai_image_url: str):
)
+def convert_openai_message_to_anthropic_tool_result(message):
+ """
+ OpenAI message with a tool result looks like:
+ {
+ "tool_call_id": "tool_1",
+ "role": "tool",
+ "name": "get_current_weather",
+ "content": "function result goes here",
+ },
+ """
+
+ """
+ Anthropic tool_results look like:
+
+ [Successful results]
+
+
+ get_current_weather
+
+ function result goes here
+
+
+
+
+ [Error results]
+
+
+ error message goes here
+
+
+ """
+ name = message.get("name")
+ content = message.get("content")
+
+ # We can't determine from openai message format whether it's a successful or
+ # error call result so default to the successful result template
+ anthropic_tool_result = {
+ "role": "user",
+ "content": (
+ "\n"
+ "\n"
+ f"{name}\n"
+ "\n"
+ f"{content}\n"
+ "\n"
+ "\n"
+ ""
+ ),
+ }
+ return anthropic_tool_result
+
+
def anthropic_messages_pt(messages: list):
"""
format messages for anthropic
@@ -563,6 +615,8 @@ def anthropic_messages_pt(messages: list):
"""
## Ensure final assistant message has no trailing whitespace
last_assistant_message_idx: Optional[int] = None
+ # add role=tool support to allow function call result/error submission
+ user_message_types = {"user", "tool"}
# reformat messages to ensure user/assistant are alternating, if there's either 2 consecutive 'user' messages or 2 consecutive 'assistant' message, add a blank 'user' or 'assistant' message to ensure compatibility
new_messages = []
if len(messages) == 1:
@@ -591,6 +645,11 @@ def anthropic_messages_pt(messages: list):
{"role": messages[0]["role"], "content": messages[0]["content"]}
)
+ if new_messages[-1]["role"] == "tool": # function call result or error
+ new_messages[-1] = convert_openai_message_to_anthropic_tool_result(
+ new_messages[-1]
+ )
+
return new_messages
for i in range(len(messages) - 1): # type: ignore
@@ -616,16 +675,29 @@ def anthropic_messages_pt(messages: list):
{"role": messages[i]["role"], "content": messages[i]["content"]}
)
- if messages[i]["role"] == messages[i + 1]["role"]:
- if messages[i]["role"] == "user":
- new_messages.append({"role": "assistant", "content": ""})
- else:
- new_messages.append({"role": "user", "content": ""})
+ if new_messages[-1]["role"] == "tool": # function call result or error
+ new_messages[-1] = convert_openai_message_to_anthropic_tool_result(
+ new_messages[-1]
+ )
+
+ if (
+ messages[i]["role"] in user_message_types
+ and messages[i + 1]["role"] in user_message_types
+ ):
+ new_messages.append({"role": "assistant", "content": ""})
if messages[i]["role"] == "assistant":
+ if messages[i + 1]["role"] == "assistant":
+ new_messages.append({"role": "user", "content": ""})
+
last_assistant_message_idx = i
- new_messages.append(messages[-1])
+ new_messages.append(
+ convert_openai_message_to_anthropic_tool_result(messages[-1])
+ if messages[-1]["role"] == "tool"
+ else messages[-1]
+ )
+
if last_assistant_message_idx is not None:
new_messages[last_assistant_message_idx]["content"] = new_messages[
last_assistant_message_idx