feat(batch-agent): add Langfuse prompt management

- _get_system_prompt helper: fetches managed prompts from Langfuse
  with hardcoded fallback (same pattern as chat service)
- journey.py: journey_system prompt manageable via Langfuse
- agent_runner.py: batch_file_classifier, batch_processing,
  batch_cloud_processing prompts all manageable via Langfuse
- redis_consumer.py: link_prompt_to_trace for all three handlers
This commit is contained in:
Roberto Musso
2026-03-23 22:30:36 +01:00
parent 75a826c9d8
commit 55500cc818
3 changed files with 22 additions and 4 deletions

View File

@@ -28,6 +28,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.llm import get_llm from app.llm import get_llm
from app.ws_context import execute_on_client, set_current_user, clear_current_user from app.ws_context import execute_on_client, set_current_user, clear_current_user
import app.tracing as tracing
from shared.db import async_session from shared.db import async_session
from shared.models import AgentRunLog, CloudAgentConfig, LocalAgentConfig from shared.models import AgentRunLog, CloudAgentConfig, LocalAgentConfig
from shared.redis import redis_client, ws_out_channel from shared.redis import redis_client, ws_out_channel
@@ -166,6 +167,12 @@ and what you created.
""" """
def _get_system_prompt(langfuse_name: str, fallback: str) -> str:
"""Fetch a managed prompt from Langfuse, falling back to the hardcoded string."""
managed = tracing.get_prompt(langfuse_name, fallback=None)
return managed if managed is not None else fallback
# ── LLM tool-calling loop ───────────────────────────────────────────────── # ── LLM tool-calling loop ─────────────────────────────────────────────────
@@ -420,7 +427,7 @@ async def _classify_file(
if d in _DOMAIN_DESCRIPTIONS if d in _DOMAIN_DESCRIPTIONS
) )
system = _STEP1_SYSTEM_PROMPT.format( system = _get_system_prompt("batch_file_classifier", _STEP1_SYSTEM_PROMPT).format(
domain_definitions=domain_definitions, domain_definitions=domain_definitions,
projects_list=projects_list, projects_list=projects_list,
) )
@@ -597,7 +604,9 @@ async def run_local_agent(user_id: str, trigger_data: dict[str, Any], *, langfus
existing_context = "\n\n".join(existing_blocks) existing_context = "\n\n".join(existing_blocks)
system_prompt = _PROCESSING_SYSTEM_PROMPT.format( system_prompt = _get_system_prompt(
"batch_processing", _PROCESSING_SYSTEM_PROMPT
).format(
existing_context=existing_context, existing_context=existing_context,
project_context=project_context, project_context=project_context,
data_types=", ".join(domains), data_types=", ".join(domains),
@@ -781,7 +790,9 @@ async def run_cloud_agent(user_id: str, config_id: str, *, langfuse_handler: Any
continue continue
items_processed += 1 items_processed += 1
processing_prompt = _CLOUD_PROCESSING_PROMPT.format( processing_prompt = _get_system_prompt(
"batch_cloud_processing", _CLOUD_PROCESSING_PROMPT
).format(
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

@@ -26,6 +26,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.llm import get_llm from app.llm import get_llm
import app.tracing as tracing
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -144,7 +145,10 @@ def _build_system_prompt(
if existing_template if existing_template
else "" else ""
) )
return _SYSTEM_PROMPT_TEMPLATE.format( # Try Langfuse-managed prompt first, fall back to hardcoded template
managed = tracing.get_prompt("journey_system", fallback=None)
template = managed if managed is not None else _SYSTEM_PROMPT_TEMPLATE
return template.format(
directory=directory, directory=directory,
data_types=", ".join(data_types), data_types=", ".join(data_types),
template_start=_TEMPLATE_START, template_start=_TEMPLATE_START,

View File

@@ -46,6 +46,7 @@ async def _handle_journey_start(user_id: str, data: dict[str, Any]) -> None:
) as span: ) as span:
langfuse_handler = tracing.get_langfuse_callback() langfuse_handler = tracing.get_langfuse_callback()
reply = await handle_journey_start(user_id, data, langfuse_handler=langfuse_handler) reply = await handle_journey_start(user_id, data, langfuse_handler=langfuse_handler)
tracing.link_prompt_to_trace(span, "journey_system")
span.update(output=reply.get("message", "")[:500]) span.update(output=reply.get("message", "")[:500])
await _publish_to_user(user_id, reply) await _publish_to_user(user_id, reply)
tracing.flush() tracing.flush()
@@ -78,6 +79,7 @@ async def _handle_journey_message(user_id: str, data: dict[str, Any]) -> None:
) as span: ) as span:
langfuse_handler = tracing.get_langfuse_callback() langfuse_handler = tracing.get_langfuse_callback()
reply = await handle_journey_message(user_id, data, langfuse_handler=langfuse_handler) reply = await handle_journey_message(user_id, data, langfuse_handler=langfuse_handler)
tracing.link_prompt_to_trace(span, "journey_system")
span.update(output=reply.get("message", "")[:500]) span.update(output=reply.get("message", "")[:500])
await _publish_to_user(user_id, reply) await _publish_to_user(user_id, reply)
tracing.flush() tracing.flush()
@@ -112,6 +114,7 @@ async def _handle_agent_trigger(user_id: str, data: dict[str, Any]) -> None:
) as span: ) as span:
langfuse_handler = tracing.get_langfuse_callback() langfuse_handler = tracing.get_langfuse_callback()
await run_local_agent(user_id, data, langfuse_handler=langfuse_handler) await run_local_agent(user_id, data, langfuse_handler=langfuse_handler)
tracing.link_prompt_to_trace(span, "batch_processing")
span.update(output={"status": "completed"}) span.update(output={"status": "completed"})
tracing.flush() tracing.flush()
except Exception as exc: except Exception as exc: