From bde8b9b55422d7e4da81b9964438c01a418297f1 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Thu, 29 Oct 2026 20:12:13 +0300 Subject: [PATCH] fix(prompts): accept fetched chat messages in create_prompt and fallback ChatPromptClient exposes chat messages with type="message", and create_prompt is typed to accept that shape through ChatMessageWithPlaceholdersDict. The generated API models only allow "chatmessage" for that field, so passing a fetched prompt's .prompt to create_prompt raised a ValidationError for CreateChatPromptRequest before any request was sent. get_prompt failed the same way when such a list was given as a chat fallback, raising inside the fetch error path instead of returning the fallback. Map type="message" to "chatmessage" before building the request and the fallback Prompt_Chat. Placeholders and messages without a type key are passed through unchanged, and the caller's list is not modified. --- langfuse/_client/client.py | 17 ++++++++-- tests/unit/test_prompt.py | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index e926e4571..04ec2e4d9 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -173,6 +173,17 @@ def _serialize_evaluations(evaluations: List[Evaluation]) -> List[Dict[str, Any] ] +def _to_api_chat_messages(messages: List[Any]) -> List[Any]: + # ChatPromptClient.prompt and ChatMessageWithPlaceholdersDict use + # type="message", but the API models only accept "chatmessage". + return [ + {**message, "type": "chatmessage"} + if isinstance(message, dict) and message.get("type") == "message" + else message + for message in messages + ] + + class Langfuse: """Main client for Langfuse tracing and platform features. @@ -3872,7 +3883,9 @@ def get_prompt( fallback_client_args: Dict[str, Any] = { "name": name, - "prompt": fallback, + "prompt": _to_api_chat_messages(fallback) + if isinstance(fallback, list) + else fallback, "type": type, "version": version or 0, "config": {}, @@ -4074,7 +4087,7 @@ def create_prompt( request: Union[CreateChatPromptRequest, CreateTextPromptRequest] = ( CreateChatPromptRequest( name=name, - prompt=cast(Any, prompt), + prompt=_to_api_chat_messages(prompt), labels=labels, tags=tags, config=config or {}, diff --git a/tests/unit/test_prompt.py b/tests/unit/test_prompt.py index eadfb8221..d18570ba6 100644 --- a/tests/unit/test_prompt.py +++ b/tests/unit/test_prompt.py @@ -748,3 +748,67 @@ def test_get_fresh_prompt_when_version_changes(langfuse: Langfuse): result_call_2 = langfuse.get_prompt(prompt_name, version=2) assert mock_server_call.call_count == 2 assert result_call_2 == version_changed_prompt_client + + +def test_create_chat_prompt_from_fetched_chat_prompt(langfuse: Langfuse): + fetched = ChatPromptClient( + Prompt_Chat( + name="source", + version=1, + prompt=[ + {"type": "chatmessage", "role": "system", "content": "Hi {{name}}"}, + {"type": "placeholder", "name": "history"}, + ], + labels=[], + type="chat", + config={}, + tags=[], + ) + ) + assert fetched.prompt[0]["type"] == "message" + + mock_server_call = langfuse.api.prompts.create + mock_server_call.return_value = Prompt_Chat( + name="copy", + version=1, + prompt=[ + {"type": "chatmessage", "role": "system", "content": "Hi {{name}}"}, + {"type": "placeholder", "name": "history"}, + ], + labels=[], + type="chat", + config={}, + tags=[], + ) + + result = langfuse.create_prompt(name="copy", prompt=fetched.prompt, type="chat") + + request = mock_server_call.call_args.kwargs["request"] + assert request.dict()["prompt"] == [ + {"type": "chatmessage", "role": "system", "content": "Hi {{name}}"}, + {"type": "placeholder", "name": "history"}, + ] + assert fetched.prompt[0]["type"] == "message" + assert result.prompt == fetched.prompt + + +def test_get_chat_prompt_fallback_with_message_type(langfuse: Langfuse): + langfuse.api.prompts.get.side_effect = Exception("Prompt not found") + fallback = [ + {"type": "message", "role": "system", "content": "Hi {{name}}"}, + {"type": "placeholder", "name": "history"}, + ] + + result = langfuse.get_prompt( + "chat_fallback_with_message_type", + type="chat", + fallback=fallback, + max_retries=0, + ) + + assert result.is_fallback + assert result.prompt == fallback + assert result.compile(name="Bob", history=[{"role": "user", "content": "yo"}]) == [ + {"role": "system", "content": "Hi Bob"}, + {"role": "user", "content": "yo"}, + ]