@@ -302,9 +302,8 @@ def get_langchain_prompt(self, **kwargs) -> str:
302302class ChatPromptClient (BasePromptClient ):
303303 def __init__ (self , prompt : Prompt_Chat , is_fallback : bool = False ):
304304 super ().__init__ (prompt , is_fallback )
305-
306- # Convert and store the prompt directly
307305 self .prompt = []
306+
308307 for p in prompt .prompt :
309308 # Handle objects with attributes (normal case)
310309 if hasattr (p , "type" ) and hasattr (p , "name" ) and p .type == "placeholder" :
@@ -340,15 +339,17 @@ def __init__(self, prompt: Prompt_Chat, is_fallback: bool = False):
340339 ),
341340 )
342341
343- def compile (self , ** kwargs ) -> List [ChatMessageDict ]:
342+ def compile (
343+ self , ** kwargs
344+ ) -> Sequence [Union [ChatMessageDict , ChatMessageWithPlaceholdersDict_Placeholder ]]:
344345 """Compile the prompt with placeholders and variables.
345346
346347 Args:
347348 **kwargs: Can contain both placeholder values (list of chat messages) and variable values.
348349 Placeholders are resolved first, then variables are substituted.
349350
350351 Returns:
351- List of compiled chat messages as plain dictionaries.
352+ List of compiled chat messages as plain dictionaries, with unresolved placeholders kept as-is .
352353 """
353354 compiled_messages = []
354355 unresolved_placeholders = []
@@ -363,47 +364,48 @@ def compile(self, **kwargs) -> List[ChatMessageDict]:
363364 chat_message ["content" ],
364365 kwargs ,
365366 ),
366- }
367+ },
367368 )
368369 elif chat_message ["type" ] == "placeholder" :
369- # Check if placeholder value is provided in kwargs
370370 placeholder_name = chat_message ["name" ]
371371 if placeholder_name in kwargs :
372372 placeholder_value = kwargs [placeholder_name ]
373373 if isinstance (placeholder_value , list ):
374- # Add all messages from the placeholder
375374 for msg in placeholder_value :
376375 if (
377376 isinstance (msg , dict )
378377 and "role" in msg
379378 and "content" in msg
380379 ):
381- # Compile variables in placeholder messages too
382380 compiled_messages .append (
383381 {
384382 "role" : msg ["role" ],
385383 "content" : TemplateParser .compile_template (
386384 msg ["content" ],
387385 kwargs ,
388386 ),
389- }
387+ },
390388 )
391389 else :
392- raise ValueError (
393- f"Placeholder ' { placeholder_name } ' must contain a list of chat messages with 'role' and 'content' fields"
390+ compiled_messages . append (
391+ str ( placeholder_value ),
394392 )
393+ no_role_content_in_placeholder = f"Placeholder '{ placeholder_name } ' should contain a list of chat messages with 'role' and 'content' fields. Appended as string."
394+ langfuse_logger .warning (no_role_content_in_placeholder )
395395 else :
396- raise ValueError (
397- f"Placeholder ' { placeholder_name } ' must contain a list of chat messages, got { type (placeholder_value )} "
396+ compiled_messages . append (
397+ str (placeholder_value ),
398398 )
399+ placeholder_not_a_list = f"Placeholder '{ placeholder_name } ' must contain a list of chat messages, got { type (placeholder_value )} "
400+ langfuse_logger .warning (placeholder_not_a_list )
399401 else :
400- # Placeholder not resolved - track it
402+ # Keep unresolved placeholder in the compiled messages
403+ compiled_messages .append (chat_message )
401404 unresolved_placeholders .append (placeholder_name )
402405
403- # Warn about unresolved placeholders
404406 if unresolved_placeholders :
405- warning_msg = f"Placeholders { unresolved_placeholders } have not been resolved. Pass them as keyword arguments to compile()."
406- langfuse_logger .warning (warning_msg )
407+ unresolved_placeholders = f"Placeholders { unresolved_placeholders } have not been resolved. Pass them as keyword arguments to compile()."
408+ langfuse_logger .warning (unresolved_placeholders )
407409
408410 return compiled_messages
409411
@@ -415,7 +417,7 @@ def variables(self) -> List[str]:
415417 for chat_message in self .prompt :
416418 if chat_message ["type" ] == "message" :
417419 variables .extend (
418- TemplateParser .find_variable_names (chat_message ["content" ])
420+ TemplateParser .find_variable_names (chat_message ["content" ]),
419421 )
420422 return variables
421423
@@ -452,26 +454,40 @@ def get_langchain_prompt(self, **kwargs):
452454
453455 It specifically adapts the mustache-style double curly braces {{variable}} used in Langfuse
454456 to the single curly brace {variable} format expected by Langchain.
457+ Placeholders are filled-in from kwargs and unresolved placeholders are returned as Langchain MessagesPlaceholder.
455458
456459 kwargs: Optional keyword arguments to precompile the template string. Variables that match
457460 the provided keyword arguments will be precompiled. Remaining variables must then be
458461 handled by Langchain's prompt template.
459- Can also contain placeholders (list of chat messages) which will be expanded.
462+ Can also contain placeholders (list of chat messages) which will be resolved prior to variable
463+ compilation.
460464
461465 Returns:
462- List of messages in the format expected by Langchain's ChatPromptTemplate: (role, content) tuple.
466+ List of messages in the format expected by Langchain's ChatPromptTemplate:
467+ (role, content) tuples for regular messages or MessagesPlaceholder objects for unresolved placeholders.
463468 """
464- # First compile with placeholders and variables to get full message list
465469 compiled_messages = self .compile (** kwargs )
470+ langchain_messages = []
466471
467- # Then convert to Langchain format
468- return [
469- (
470- msg ["role" ],
471- self ._get_langchain_prompt_string (msg ["content" ]),
472- )
473- for msg in compiled_messages
474- ]
472+ for msg in compiled_messages :
473+ if "type" in msg and msg ["type" ] == "placeholder" :
474+ # unresolved placeholder -> add LC MessagesPlaceholder
475+ placeholder_name = msg ["name" ]
476+ try :
477+ from langchain_core .prompts .chat import MessagesPlaceholder # noqa: PLC0415, I001
478+
479+ langchain_messages .append (
480+ MessagesPlaceholder (variable_name = placeholder_name ),
481+ )
482+ except ImportError as e :
483+ import_error = "langchain_core is required to use get_langchain_prompt() with unresolved placeholders."
484+ raise ImportError (import_error ) from e
485+ else :
486+ langchain_messages .append (
487+ (msg ["role" ], self ._get_langchain_prompt_string (msg ["content" ])),
488+ )
489+
490+ return langchain_messages
475491
476492
477493PromptClient = Union [TextPromptClient , ChatPromptClient ]
0 commit comments