guv3-prompt-patterns
Prompt Patterns in guv3
Overview
guv3 prompts follow a three-step pipeline:
- Define prompt string with
{template_variables}innodes/prompt.py - Format state data into a dict via
format_*_state()functions - Inject values with
ChatPromptTemplate.partial(**formatted_state)innodes/node.py
Prompt File Structure
Every agent has a nodes/prompt.py with these components:
from langchain_core.prompts import ChatPromptTemplate
# 1. Raw prompt string with {template_variables}
ASSISTANT_PROMPT = """
# IDENTIDAD Y ROL
...
"""
# 2. ChatPromptTemplate creation
assistant_prompt = ChatPromptTemplate.from_messages([
("system", ASSISTANT_PROMPT),
("placeholder", "{messages}"),
])
# 3. Format function
def format_assistant_state(state, **kwargs) -> dict:
return {"var1": value1, "var2": value2}
Standard Prompt Sections
Every guv3 prompt should include these sections in this order:
# IDENTIDAD Y ROL
Eres **Gu**, asistente inmobiliario virtual especializado en **{specialty}**.
Trabajas para la oficina inmobiliaria de **{owner_name}}**, brindando atencion 24/7.
Tu funcion principal: [one-line purpose]
---
# DELEGACION SILENCIOSA - REGLA CRITICA
- NUNCA digas: "Te paso con...", "Te comunico con...", "El asistente especializado..."
- NUNCA menciones: "modulo", "asistente", "transferencia", "derivacion"
- NUNCA respondas con texto antes de delegar
- Cuando delegues, ejecuta la herramienta SIN texto adicional
- La delegacion debe ser completamente invisible para el usuario
---
# RESUMEN DE CONVERSACIONES ANTERIORES
{context_history}
---
# CONTEXTO ACTUAL
## Usuario
- Nombre: {user_name}
- Email: {user_email}
- Telefono: {user_phone}
- Pais: {user_country}
- Tipo: {user_role}
{is_agent_message}
## Oficina / Empleador
- Nombre: {owner_name}
- Telefono: {owner_phone}
---
# INSTRUCCIONES
[Your specific workflow instructions]
---
# HERRAMIENTAS DISPONIBLES
[Description of each tool and when to use it]
---
# RESTRICCIONES DE CONOCIMIENTO
- Solo responde con datos explicitos en la informacion proporcionada
- Si no tienes la informacion, usa la herramienta correspondiente
- NUNCA inventes datos
---
# RECORDATORIOS FINALES
[Key rules to reinforce]
Template Variable Syntax
Single braces {var} — LangChain template variables filled by .partial():
PROMPT = "Hola {user_name}, tu propiedad es {property_info}"
# Filled later: prompt.partial(user_name="Juan", property_info="...")
Double braces {{var}} — Escaped braces that render as literal {var} in output:
PROMPT = "Formato JSON: {{\"key\": \"value\"}}"
# Output: Formato JSON: {"key": "value"}
NEVER use Python f-strings for the main prompt template. The {variables} are resolved by LangChain, not Python.
The .partial() Workflow
In nodes/node.py:
def assistant_fn(state: State, config: RunnableConfig):
user_info = state.get("user_info") or get_user(config)
# Step 1: Format state into dict
formatted_state = format_assistant_state(
state,
user_info=user_info,
# ... additional params
)
# Step 2: Inject values into prompt
prompt_with_values = assistant_prompt.partial(**formatted_state)
# Step 3: Pass to agent_invoke
return agent_invoke(
prompt_with_values,
tools,
state,
config,
"assistant_name",
)
The format_*_state() function:
def format_assistant_state(state: dict, **kwargs) -> dict:
user_info = state.get("user_info", {})
owner_info = user_info.get("owner_info", {})
# Build context history
context_history = build_context_history(user_info.get("lead_id"))
return {
# Keys MUST match {template_variables} in the prompt string
"user_name": user_info.get("name", "No proporcionado"),
"user_email": user_info.get("email", "No proporcionado"),
"user_phone": user_info.get("phone_number", "No proporcionado"),
"user_country": user_info.get("country_iso", ""),
"user_role": user_info.get("user_type", "prospect"),
"is_agent_message": _format_agent_status(user_info),
"owner_name": user_info.get("org_name", ""),
"owner_phone": user_info.get("owner_phone_number", ""),
"context_history": context_history or "No hay contexto historico disponible.",
# ... assistant-specific variables
}
Critical: Every key returned by format_*_state() MUST have a matching {key} in the prompt. Missing keys cause runtime errors.
Context History Injection
from gu.helpers.helpers import build_context_history
# Fetches from MongoDB chat_memory collection, sorted by batch
context_history = build_context_history(user_info.get("lead_id"))
# Returns: '"""\nParte 1: [resumen]\nParte 2: [resumen]\n"""'
# Or empty string "" if no history
Always provide a fallback:
"context_history": context_history if context_history else "No hay contexto historico disponible."
Two Prompt Approaches
Approach 1: Template Variables + .partial() (preferred)
Used by most agents. Variables are injected at runtime:
PROMPT = """User: {user_name}, Property: {property_info}"""
prompt = ChatPromptTemplate.from_messages([
("system", PROMPT),
("placeholder", "{messages}"),
])
# In node.py:
prompt.partial(user_name="Juan", property_info="Casa en Polanco")
Use when: Prompt structure is the same, only data changes.
Approach 2: Python f-string Functions (visit_tracker pattern)
Used when prompt structure changes per user/context:
def get_survey_prompt(appointment_list: str, appointment_id: str) -> str:
return f"""
Tu tarea es evaluar la visita...
Citas: {appointment_list}
ID: {appointment_id}
"""
def create_prompt(content: str) -> ChatPromptTemplate:
return ChatPromptTemplate.from_messages([
("system", content),
("placeholder", "{messages}"),
])
# In node.py:
prompt_content = get_survey_prompt(appts, appt_id)
prompt = create_prompt(prompt_content)
# No .partial() needed — variables already filled by f-string
Use when: Prompt itself changes structurally (different sections per scenario).
Multiple Prompt Variants
Some agents have multiple prompts for different scenarios:
# appointment_assistant pattern
PROMPT_WITH_PROPERTY = """..."""
PROMPT_WITHOUT_PROPERTY = """..."""
prompt_with_property = ChatPromptTemplate.from_messages([...])
prompt_without_property = ChatPromptTemplate.from_messages([...])
# In node.py:
if property_info:
prompt = prompt_with_property.partial(**formatted_state)
else:
prompt = prompt_without_property.partial(**formatted_state)
Shared Prompt Sections
For reusable sections across prompts, use Python constants with f-string embedding:
# In a shared file or at top of prompt.py
DELEGATION_RULES = """
# DELEGACION SILENCIOSA - REGLA CRITICA
- NUNCA digas: "Te paso con...", "Te comunico con..."
- Cuando delegues, ejecuta la herramienta SIN texto adicional
"""
KNOWLEDGE_RESTRICTIONS = """
# RESTRICCIONES DE CONOCIMIENTO
- Solo responde con datos explicitos
- NUNCA inventes datos
"""
# Embed in the main prompt using f-string
FULL_PROMPT = f"""
# IDENTIDAD Y ROL
Eres Gu...
{DELEGATION_RULES}
# INSTRUCCIONES
...
{KNOWLEDGE_RESTRICTIONS}
"""
# Then create ChatPromptTemplate from FULL_PROMPT
# Note: f-string embeds the constants, {template_vars} stay for .partial()
Common Template Variables Reference
Available across all agents:
{context_history} - Conversation history from chat_memory
{user_name} - User's name
{user_email} - User's email
{user_phone} - User's phone
{user_country} - Country ISO code
{user_role} - "prospect" or "agent"
{is_agent_message} - Formatted agent status text
{owner_name} - Organization name
{owner_phone} - Owner's phone number
{last_message} - User's most recent message
Property-related:
{property_info} - Formatted property summary
{consulted_properties_summary} - Recently viewed properties list
Time-related (appointment_assistant):
{now} - Current datetime formatted
{today_day_name} - "lunes", "martes", etc.
{today_day_number} - Day of month
{today_month} - "enero", "febrero", etc.
{today_hour_without_seconds} - "14:32"
Organization-related:
{org_name} - Organization name
{facebook_link} - Facebook URL
{instagram_link} - Instagram URL
{web_link} - Website URL
{privacy_notice} - Privacy policy link
Checklist for Modifying a Prompt
- Identified the prompt file:
gu/agents/{name}/nodes/prompt.py - Added/modified template variable
{new_var}in the prompt string - Added key
"new_var"to theformat_*_state()return dict - Ensured every
{var}in prompt has a matching key in the format function - Tested that
.partial()doesn't raise missing variable errors - Kept prompt under 500 lines (move details to separate referenced files)