Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 56 additions & 11 deletions langfuse/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,15 @@ def get_langchain_prompt(self, **kwargs: Union[str, Any]) -> str:


class ChatPromptClient(BasePromptClient):
_LANGCHAIN_MESSAGE_ROLE_MAP = {
"human": "user",
"ai": "assistant",
"system": "system",
"tool": "tool",
"function": "function",
"chat": "chat",
}

def __init__(self, prompt: Prompt_Chat, is_fallback: bool = False):
super().__init__(prompt, is_fallback)
self.prompt: List[ChatMessageWithPlaceholdersDict] = []
Expand Down Expand Up @@ -346,17 +355,12 @@ def compile(
placeholder_value = kwargs[placeholder_name]
if isinstance(placeholder_value, list):
for msg in placeholder_value:
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_msg = self._compile_placeholder_message(
msg=msg,
kwargs=kwargs,
)

if compiled_msg is not None:
compiled_messages.append(compiled_msg)
else:
compiled_messages.append(
Expand Down Expand Up @@ -387,6 +391,47 @@ def compile(

return compiled_messages # type: ignore

def _compile_placeholder_message(
self, *, msg: Any, kwargs: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
if isinstance(msg, dict):
# Preserve all fields from the original message, such as tool calls
compiled_msg = dict(msg)
# Ensure role and content are always present
compiled_msg["role"] = msg.get("role", "NOT_GIVEN")
compiled_msg["content"] = TemplateParser.compile_template(
msg.get("content", ""),
kwargs,
)
return compiled_msg

if not hasattr(msg, "content"):
return None

role = getattr(msg, "role", None)
if role is None:
role = self._LANGCHAIN_MESSAGE_ROLE_MAP.get(
str(getattr(msg, "type", "")),
)

if role is None:
return None

content = getattr(msg, "content")
compiled_msg = {
"role": role,
"content": TemplateParser.compile_template(content, kwargs)
if isinstance(content, str)
else content,
}

for key in ("name", "tool_call_id", "tool_calls", "invalid_tool_calls"):
value = getattr(msg, key, None)
if value:
Comment on lines +427 to +430

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Message metadata is dropped

When a LangChain message stores provider or subtype data outside name, tool_call_id, tool_calls, and invalid_tool_calls, this allowlist removes that data from the compiled history, causing downstream model calls to receive incomplete function or tool context.

Knowledge Base Used: Prompt retrieval, compilation, and caching

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/model.py
Line: 427-430

Comment:
**Message metadata is dropped**

When a LangChain message stores provider or subtype data outside `name`, `tool_call_id`, `tool_calls`, and `invalid_tool_calls`, this allowlist removes that data from the compiled history, causing downstream model calls to receive incomplete function or tool context.

**Knowledge Base Used:** [Prompt retrieval, compilation, and caching](https://app.greptile.com/personal-org-4986/-/custom-context/knowledge-base/langfuse/langfuse-python/-/docs/prompt-retrieval-compilation-and-cache.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

compiled_msg[key] = value

return compiled_msg

@property
def variables(self) -> List[str]:
"""Return all the variable names in the chat prompt template."""
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/test_prompt_compilation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate

from langfuse.api import ChatMessage, Prompt_Chat
Expand Down Expand Up @@ -932,3 +933,34 @@ def test_tool_calls_preservation_in_message_placeholder():
# Final user message with compiled variable
assert compiled_messages[4]["role"] == "user"
assert compiled_messages[4]["content"] == "Help me with weather inquiry"


def test_langchain_messages_in_message_placeholder_are_preserved():
prompt_client = ChatPromptClient(
Prompt_Chat(
type="chat",
name="langchain_message_placeholder_test",
version=1,
config={},
tags=[],
labels=[],
prompt=[
{"type": "placeholder", "name": "history"},
{"role": "user", "content": "Question for {{name}}"},
],
)
)

compiled_messages = prompt_client.compile(
name="Ada",
history=[
HumanMessage(content="Hello {{name}}"),
AIMessage(content="Hi {{name}}"),
],
)

assert compiled_messages == [
{"role": "user", "content": "Hello Ada"},
{"role": "assistant", "content": "Hi Ada"},
{"role": "user", "content": "Question for Ada"},
]