diff --git a/langfuse/model.py b/langfuse/model.py index d1b5a80cf..75803d215 100644 --- a/langfuse/model.py +++ b/langfuse/model.py @@ -165,7 +165,13 @@ def compile( self, **kwargs: Union[str, Any] ) -> Union[ str, - Sequence[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]], + Sequence[ + Union[ + Dict[str, Any], + ChatMessageDict, + ChatMessageWithPlaceholdersDict_Placeholder, + ] + ], ]: pass @@ -327,7 +333,11 @@ def __init__(self, prompt: Prompt_Chat, is_fallback: bool = False): def compile( self, **kwargs: Union[str, Any], - ) -> Sequence[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]]: + ) -> Sequence[ + Union[ + Dict[str, Any], ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder + ] + ]: """Compile the prompt with placeholders and variables. Args: @@ -338,7 +348,11 @@ def compile( List of compiled chat messages as plain dictionaries, with unresolved placeholders kept as-is. """ compiled_messages: List[ - Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder] + Union[ + Dict[str, Any], + ChatMessageDict, + ChatMessageWithPlaceholdersDict_Placeholder, + ] ] = [] unresolved_placeholders: List[ChatMessageWithPlaceholdersDict_Placeholder] = [] @@ -361,20 +375,18 @@ def compile( placeholder_value = kwargs[placeholder_name] if isinstance(placeholder_value, list): for msg in placeholder_value: - if ( - isinstance(msg, dict) - and "role" in msg - and "content" in msg - ): - compiled_messages.append( - ChatMessageDict( - role=msg["role"], # type: ignore - content=TemplateParser.compile_template( - msg["content"], # type: ignore - kwargs, - ), - ), + if isinstance(msg, dict): + # Preserve all fields from the original message, such as tool calls + compiled_msg = dict(msg) # type: ignore + # Ensure role and content are always present + compiled_msg["role"] = msg.get("role", "NOT_GIVEN") + compiled_msg["content"] = ( + TemplateParser.compile_template( + msg.get("content", ""), # type: ignore + kwargs, + ) ) + compiled_messages.append(compiled_msg) else: compiled_messages.append( ChatMessageDict( diff --git a/tests/test_prompt_compilation.py b/tests/test_prompt_compilation.py index c8aa789dc..10a4cd990 100644 --- a/tests/test_prompt_compilation.py +++ b/tests/test_prompt_compilation.py @@ -850,3 +850,85 @@ def test_get_langchain_prompt_with_unresolved_placeholders(self): # Third message should be the user message assert langchain_messages[2] == ("user", "Help me with coding") + + +def test_tool_calls_preservation_in_message_placeholder(): + """Test that tool calls are preserved when compiling message placeholders.""" + from langfuse.api.resources.prompts import Prompt_Chat + + chat_messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"type": "placeholder", "name": "message_history"}, + {"role": "user", "content": "Help me with {{task}}"}, + ] + + prompt_client = ChatPromptClient( + Prompt_Chat( + type="chat", + name="tool_calls_test", + version=1, + config={}, + tags=[], + labels=[], + prompt=chat_messages, + ) + ) + + # Message history with tool calls - exactly like the bug report describes + message_history_with_tool_calls = [ + {"role": "user", "content": "What's the weather like?"}, + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"location": "San Francisco"}', + }, + } + ], + }, + { + "role": "tool", + "content": "It's sunny, 72°F", + "tool_call_id": "call_123", + "name": "get_weather", + }, + ] + + # Compile with message history and variables + compiled_messages = prompt_client.compile( + task="weather inquiry", message_history=message_history_with_tool_calls + ) + + # Should have 5 messages: system + 3 from history + user + assert len(compiled_messages) == 5 + + # System message + assert compiled_messages[0]["role"] == "system" + assert compiled_messages[0]["content"] == "You are a helpful assistant." + + # User message from history + assert compiled_messages[1]["role"] == "user" + assert compiled_messages[1]["content"] == "What's the weather like?" + + # Assistant message with TOOL CALLS + assert compiled_messages[2]["role"] == "assistant" + assert compiled_messages[2]["content"] == "" + assert "tool_calls" in compiled_messages[2] + assert len(compiled_messages[2]["tool_calls"]) == 1 + assert compiled_messages[2]["tool_calls"][0]["id"] == "call_123" + assert compiled_messages[2]["tool_calls"][0]["function"]["name"] == "get_weather" + + # TOOL CALL results message + assert compiled_messages[3]["role"] == "tool" + assert compiled_messages[3]["content"] == "It's sunny, 72°F" + assert compiled_messages[3]["tool_call_id"] == "call_123" + assert compiled_messages[3]["name"] == "get_weather" + + # Final user message with compiled variable + assert compiled_messages[4]["role"] == "user" + assert compiled_messages[4]["content"] == "Help me with weather inquiry"