Restore dynamic system prompt via RunnableLambda prompt parameter

Uses prompt=_build_prompt (a RunnableLambda) so LangGraph passes the
runtime config through, allowing per-request system_prompt injection.
Restores user memory loading for web and iMessage plain-text enforcement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 23:04:43 -04:00
parent 2a81ea8f76
commit 43e0de24d6
3 changed files with 26 additions and 2 deletions
+5
View File
@@ -26,6 +26,7 @@ from .logic import (
persist_agent_tool_messages,
tool_result_text,
)
from .memory import build_system_prompt_with_memories
from .models import (
Conversation,
PydConversation,
@@ -142,12 +143,14 @@ async def query():
)
with time_response(stream=False):
system_prompt = await build_system_prompt_with_memories(str(user.id))
payload = {"messages": [_build_user_message(query)]}
agent_config = {
"configurable": {
"user_id": str(user.id),
"is_admin": user.is_admin(),
"thread_id": str(conversation.id),
"system_prompt": system_prompt,
}
}
@@ -234,12 +237,14 @@ async def stream_query():
logging.error(f"Failed to load image {image_key}: {e}")
query_text = f"{query_text or ''}\n\n[An image was sent but could not be loaded.]".strip()
system_prompt = await build_system_prompt_with_memories(str(user.id))
payload = {"messages": [_build_user_message(query_text or "", image_block)]}
agent_config = {
"configurable": {
"user_id": str(user.id),
"is_admin": user.is_admin(),
"thread_id": str(conversation.id),
"system_prompt": system_prompt,
}
}
+13 -2
View File
@@ -6,7 +6,8 @@ from dotenv import load_dotenv
from langchain.chat_models import BaseChatModel
from langchain.tools import tool
from langgraph.prebuilt import create_react_agent
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import SystemMessage
from langchain_core.runnables import RunnableConfig, RunnableLambda
from langchain_openai import ChatOpenAI
from tavily import AsyncTavilyClient
@@ -1230,11 +1231,21 @@ if google_gmail_enabled:
tools.extend([gmail_search_emails, gmail_get_email])
@RunnableLambda
def _build_prompt(messages: list, config: RunnableConfig = None) -> list:
"""Inject the per-request system prompt, replacing any prior system messages."""
system_prompt = (
(config or {}).get("configurable", {}).get("system_prompt", SIMBA_SYSTEM_PROMPT)
)
non_system = [m for m in messages if not isinstance(m, SystemMessage)]
return [SystemMessage(content=system_prompt)] + non_system
def build_agent(checkpointer=None):
"""Compile and return the LangGraph agent."""
return create_react_agent(
model=model_with_fallback,
tools=tools,
prompt=SIMBA_SYSTEM_PROMPT,
prompt=_build_prompt,
checkpointer=checkpointer,
)
+8
View File
@@ -17,6 +17,7 @@ from blueprints.conversation.logic import (
)
from blueprints.conversation.models import Conversation, ConversationMessage
from quart import current_app
from blueprints.conversation.memory import build_system_prompt_with_memories
from utils.strip_markdown import extract_agent_text, strip_markdown
imessage_blueprint = Blueprint("imessage_api", __name__, url_prefix="/api/imessage")
@@ -343,11 +344,18 @@ async def _process_and_reply(
else:
user_content = content
system_prompt = await build_system_prompt_with_memories(str(user.id))
system_prompt += (
"\n\nIMPORTANT: You are replying via iMessage. Use plain text only. "
"Do not use markdown tables, headers, bullet points, bold, italics, "
"or any other markdown formatting. Write in natural prose."
)
agent_config = {
"configurable": {
"user_id": str(user.id),
"is_admin": user.is_admin(),
"thread_id": str(conversation.id),
"system_prompt": system_prompt,
}
}
logger.info(f"Invoking LangChain agent for conversation {conversation.id}")