Skip to content

Commit ce29143

Browse files
authored
fix(prompt-placeholders): don't strip tool calls from placeholder messages (#1346)
* add test that tool calls aren't stripped from msg placehodlers * fix: allow arbitrary values to be inserted as placeholder fill-in * fix formatting
1 parent 621b989 commit ce29143

File tree

2 files changed

+110
-16
lines changed

2 files changed

+110
-16
lines changed

langfuse/model.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,13 @@ def compile(
165165
self, **kwargs: Union[str, Any]
166166
) -> Union[
167167
str,
168-
Sequence[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]],
168+
Sequence[
169+
Union[
170+
Dict[str, Any],
171+
ChatMessageDict,
172+
ChatMessageWithPlaceholdersDict_Placeholder,
173+
]
174+
],
169175
]:
170176
pass
171177

@@ -327,7 +333,11 @@ def __init__(self, prompt: Prompt_Chat, is_fallback: bool = False):
327333
def compile(
328334
self,
329335
**kwargs: Union[str, Any],
330-
) -> Sequence[Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]]:
336+
) -> Sequence[
337+
Union[
338+
Dict[str, Any], ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder
339+
]
340+
]:
331341
"""Compile the prompt with placeholders and variables.
332342
333343
Args:
@@ -338,7 +348,11 @@ def compile(
338348
List of compiled chat messages as plain dictionaries, with unresolved placeholders kept as-is.
339349
"""
340350
compiled_messages: List[
341-
Union[ChatMessageDict, ChatMessageWithPlaceholdersDict_Placeholder]
351+
Union[
352+
Dict[str, Any],
353+
ChatMessageDict,
354+
ChatMessageWithPlaceholdersDict_Placeholder,
355+
]
342356
] = []
343357
unresolved_placeholders: List[ChatMessageWithPlaceholdersDict_Placeholder] = []
344358

@@ -361,20 +375,18 @@ def compile(
361375
placeholder_value = kwargs[placeholder_name]
362376
if isinstance(placeholder_value, list):
363377
for msg in placeholder_value:
364-
if (
365-
isinstance(msg, dict)
366-
and "role" in msg
367-
and "content" in msg
368-
):
369-
compiled_messages.append(
370-
ChatMessageDict(
371-
role=msg["role"], # type: ignore
372-
content=TemplateParser.compile_template(
373-
msg["content"], # type: ignore
374-
kwargs,
375-
),
376-
),
378+
if isinstance(msg, dict):
379+
# Preserve all fields from the original message, such as tool calls
380+
compiled_msg = dict(msg) # type: ignore
381+
# Ensure role and content are always present
382+
compiled_msg["role"] = msg.get("role", "NOT_GIVEN")
383+
compiled_msg["content"] = (
384+
TemplateParser.compile_template(
385+
msg.get("content", ""), # type: ignore
386+
kwargs,
387+
)
377388
)
389+
compiled_messages.append(compiled_msg)
378390
else:
379391
compiled_messages.append(
380392
ChatMessageDict(

tests/test_prompt_compilation.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,3 +850,85 @@ def test_get_langchain_prompt_with_unresolved_placeholders(self):
850850

851851
# Third message should be the user message
852852
assert langchain_messages[2] == ("user", "Help me with coding")
853+
854+
855+
def test_tool_calls_preservation_in_message_placeholder():
856+
"""Test that tool calls are preserved when compiling message placeholders."""
857+
from langfuse.api.resources.prompts import Prompt_Chat
858+
859+
chat_messages = [
860+
{"role": "system", "content": "You are a helpful assistant."},
861+
{"type": "placeholder", "name": "message_history"},
862+
{"role": "user", "content": "Help me with {{task}}"},
863+
]
864+
865+
prompt_client = ChatPromptClient(
866+
Prompt_Chat(
867+
type="chat",
868+
name="tool_calls_test",
869+
version=1,
870+
config={},
871+
tags=[],
872+
labels=[],
873+
prompt=chat_messages,
874+
)
875+
)
876+
877+
# Message history with tool calls - exactly like the bug report describes
878+
message_history_with_tool_calls = [
879+
{"role": "user", "content": "What's the weather like?"},
880+
{
881+
"role": "assistant",
882+
"content": "",
883+
"tool_calls": [
884+
{
885+
"id": "call_123",
886+
"type": "function",
887+
"function": {
888+
"name": "get_weather",
889+
"arguments": '{"location": "San Francisco"}',
890+
},
891+
}
892+
],
893+
},
894+
{
895+
"role": "tool",
896+
"content": "It's sunny, 72°F",
897+
"tool_call_id": "call_123",
898+
"name": "get_weather",
899+
},
900+
]
901+
902+
# Compile with message history and variables
903+
compiled_messages = prompt_client.compile(
904+
task="weather inquiry", message_history=message_history_with_tool_calls
905+
)
906+
907+
# Should have 5 messages: system + 3 from history + user
908+
assert len(compiled_messages) == 5
909+
910+
# System message
911+
assert compiled_messages[0]["role"] == "system"
912+
assert compiled_messages[0]["content"] == "You are a helpful assistant."
913+
914+
# User message from history
915+
assert compiled_messages[1]["role"] == "user"
916+
assert compiled_messages[1]["content"] == "What's the weather like?"
917+
918+
# Assistant message with TOOL CALLS
919+
assert compiled_messages[2]["role"] == "assistant"
920+
assert compiled_messages[2]["content"] == ""
921+
assert "tool_calls" in compiled_messages[2]
922+
assert len(compiled_messages[2]["tool_calls"]) == 1
923+
assert compiled_messages[2]["tool_calls"][0]["id"] == "call_123"
924+
assert compiled_messages[2]["tool_calls"][0]["function"]["name"] == "get_weather"
925+
926+
# TOOL CALL results message
927+
assert compiled_messages[3]["role"] == "tool"
928+
assert compiled_messages[3]["content"] == "It's sunny, 72°F"
929+
assert compiled_messages[3]["tool_call_id"] == "call_123"
930+
assert compiled_messages[3]["name"] == "get_weather"
931+
932+
# Final user message with compiled variable
933+
assert compiled_messages[4]["role"] == "user"
934+
assert compiled_messages[4]["content"] == "Help me with weather inquiry"

0 commit comments

Comments
 (0)