fix(langfuse): use compile() instead of .format() for prompt variable injection
Langfuse uses {{variable}} syntax in its prompt management UI, while the
hardcoded fallbacks use {variable} (Python str.format). The previous code
always called .format() which silently failed/errored when a real Langfuse
prompt was fetched.
- langfuse_client.py: add compile_prompt(template, prompt_obj, **vars)
→ uses prompt_obj.compile(**vars) when Langfuse is available
→ falls back to template.format(**vars) when using the hardcoded fallback
- agent_runner.py: replace .format() with compile_prompt() for
unified_processing (V2 local) and batch_cloud_processing (cloud agent)
- agent_setup.py: replace .format() with compile_prompt() for journey_system
deep_agent.py prompts have no variables, so no change needed there.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,10 +80,11 @@ def get_langfuse() -> Any | None:
|
||||
def get_prompt_or_fallback(name: str, fallback: str) -> tuple[str, Any]:
|
||||
"""Fetch a text prompt from Langfuse; fall back to ``fallback`` on any error.
|
||||
|
||||
Returns ``(prompt_text, prompt_obj_or_None)``.
|
||||
Returns ``(raw_template, prompt_obj_or_None)``.
|
||||
|
||||
* ``prompt_text`` — the raw template string (variables not yet substituted).
|
||||
Callers perform variable substitution with Python's ``.format()``.
|
||||
* ``raw_template`` — the uncompiled template string. Do NOT call ``.format()``
|
||||
on it directly; use :func:`compile_prompt` instead so the correct variable
|
||||
syntax is applied (``{{var}}`` for Langfuse, ``{var}`` for the fallback).
|
||||
* ``prompt_obj`` — the Langfuse prompt object, or ``None`` when Langfuse is
|
||||
unavailable / the fetch failed. Pass this to generation observations so
|
||||
Langfuse links the generation to the exact prompt version in the UI.
|
||||
@@ -102,6 +103,38 @@ def get_prompt_or_fallback(name: str, fallback: str) -> tuple[str, Any]:
|
||||
return fallback, None
|
||||
|
||||
|
||||
def compile_prompt(template: str, prompt_obj: Any, **variables: Any) -> str:
|
||||
"""Compile *template* with *variables*, choosing the right syntax.
|
||||
|
||||
* When *prompt_obj* is a real Langfuse prompt object, calls
|
||||
``prompt_obj.compile(**variables)`` which handles ``{{variable}}``
|
||||
substitution as defined in the Langfuse UI.
|
||||
* When *prompt_obj* is ``None`` (Langfuse unavailable or fetch failed),
|
||||
falls back to ``template.format(**variables)`` which handles the
|
||||
``{variable}`` syntax used in the hardcoded fallback strings.
|
||||
|
||||
This keeps callers oblivious to which syntax is in use.
|
||||
"""
|
||||
if prompt_obj is not None:
|
||||
try:
|
||||
compiled = prompt_obj.compile(**variables)
|
||||
# compile() returns a string for text prompts.
|
||||
if isinstance(compiled, str):
|
||||
return compiled
|
||||
# Chat prompts return a list of dicts — join text parts.
|
||||
if isinstance(compiled, list):
|
||||
return "\n".join(
|
||||
m.get("content", "") for m in compiled if isinstance(m, dict)
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"langfuse: compile failed for prompt %r: %s — falling back to .format()",
|
||||
getattr(prompt_obj, "name", "?"),
|
||||
exc,
|
||||
)
|
||||
return template.format(**variables)
|
||||
|
||||
|
||||
def extract_usage(response: Any) -> dict[str, int]:
|
||||
"""Extract token usage from a LangChain AI message into Langfuse format."""
|
||||
meta = getattr(response, "usage_metadata", None)
|
||||
|
||||
Reference in New Issue
Block a user