develop #2

Merged
roberto merged 160 commits from develop into main 2026-06-12 15:27:23 +00:00
3 changed files with 47 additions and 8 deletions
Showing only changes of commit 3aa0b36a6c - Show all commits

View File

@@ -32,7 +32,7 @@ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, Tool
from app.agents.filesystem_agent import FILESYSTEM_TOOLS from app.agents.filesystem_agent import FILESYSTEM_TOOLS
from app.config.settings import settings from app.config.settings import settings
from app.core.langfuse_client import extract_usage, get_langfuse, get_prompt_or_fallback from app.core.langfuse_client import compile_prompt, extract_usage, get_langfuse, get_prompt_or_fallback
from app.core.llm import get_llm from app.core.llm import get_llm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -160,7 +160,9 @@ def _build_system_prompt(
template, prompt_obj = get_prompt_or_fallback( template, prompt_obj = get_prompt_or_fallback(
"journey_system", _JOURNEY_SYSTEM_PROMPT "journey_system", _JOURNEY_SYSTEM_PROMPT
) )
compiled = template.format( compiled = compile_prompt(
template,
prompt_obj,
directory=directory, directory=directory,
data_types=", ".join(data_types), data_types=", ".join(data_types),
template_start=_TEMPLATE_START, template_start=_TEMPLATE_START,

View File

@@ -45,7 +45,7 @@ from app.agents.task_agent import TASK_TOOLS
from app.agents.timeline_agent import TIMELINE_TOOLS from app.agents.timeline_agent import TIMELINE_TOOLS
from app.config.settings import settings from app.config.settings import settings
from app.core.device_manager import DeviceConnectionManager from app.core.device_manager import DeviceConnectionManager
from app.core.langfuse_client import extract_usage, get_langfuse, get_prompt_or_fallback from app.core.langfuse_client import compile_prompt, extract_usage, get_langfuse, get_prompt_or_fallback
from app.core.llm import get_llm from app.core.llm import get_llm
from app.core.preprocessors import detect_content_type, preprocess from app.core.preprocessors import detect_content_type, preprocess
from app.core.ws_context import clear_client_executor, execute_on_client, set_client_executor from app.core.ws_context import clear_client_executor, execute_on_client, set_client_executor
@@ -661,7 +661,9 @@ async def run_local_agent(
) )
metadata_section = _format_metadata(preprocessed.metadata) metadata_section = _format_metadata(preprocessed.metadata)
system_prompt = unified_template.format( system_prompt = compile_prompt(
unified_template,
prompt_obj,
filename=filename, filename=filename,
metadata_section=metadata_section, metadata_section=metadata_section,
projects_list=projects_block, projects_list=projects_block,
@@ -893,7 +895,9 @@ async def run_cloud_agent(
cloud_template, cloud_prompt_obj = get_prompt_or_fallback( cloud_template, cloud_prompt_obj = get_prompt_or_fallback(
"batch_cloud_processing", _BATCH_CLOUD_PROCESSING_PROMPT "batch_cloud_processing", _BATCH_CLOUD_PROCESSING_PROMPT
) )
processing_prompt = cloud_template.format( processing_prompt = compile_prompt(
cloud_template,
cloud_prompt_obj,
data_types=", ".join(config.data_types), data_types=", ".join(config.data_types),
project_context="Determine the appropriate project from the message context.", project_context="Determine the appropriate project from the message context.",
file_list=f"Message from {config.provider} (id: {msg.id})", file_list=f"Message from {config.provider} (id: {msg.id})",

View File

@@ -80,10 +80,11 @@ def get_langfuse() -> Any | None:
def get_prompt_or_fallback(name: str, fallback: str) -> tuple[str, Any]: 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. """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). * ``raw_template`` — the uncompiled template string. Do NOT call ``.format()``
Callers perform variable substitution with Python's ``.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 * ``prompt_obj`` — the Langfuse prompt object, or ``None`` when Langfuse is
unavailable / the fetch failed. Pass this to generation observations so unavailable / the fetch failed. Pass this to generation observations so
Langfuse links the generation to the exact prompt version in the UI. 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 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]: def extract_usage(response: Any) -> dict[str, int]:
"""Extract token usage from a LangChain AI message into Langfuse format.""" """Extract token usage from a LangChain AI message into Langfuse format."""
meta = getattr(response, "usage_metadata", None) meta = getattr(response, "usage_metadata", None)