Contextvars — passar estado sem poluir tools¶
As tools do agente não devem receber session_id, phone, contact_name
como parâmetros. O LLM não tem negócio decidir esses valores — eles vêm
do webhook e são constantes durante o turno.
O padrão Python canônico para isso é contextvars.ContextVar.
Por que não passar como parâmetro¶
- Segurança: se o LLM passar telefone como argumento, pode confundir com o telefone de outro paciente citado no histórico.
- Limpeza do prompt: não poluir a docstring da tool com params técnicos.
- Confiabilidade: o telefone do WhatsApp é o telefone do paciente. Ponto. Nenhum LLM vai inventar melhor do que o webhook.
Setup¶
# agent.py — topo do módulo
import contextvars
_current_session_id: contextvars.ContextVar[str] = contextvars.ContextVar(
"session_id", default=""
)
_current_phone_number: contextvars.ContextVar[str] = contextvars.ContextVar(
"phone_number", default=""
)
_current_contact_name: contextvars.ContextVar[str] = contextvars.ContextVar(
"contact_name", default=""
)
Setagem — antes de invoke¶
def run_agent(message, session_id, phone_number, contact_name):
_current_session_id.set(session_id)
_current_phone_number.set(phone_number)
_current_contact_name.set(contact_name)
result = agent_executor.invoke(...)
return result
Uso dentro da tool¶
@tool
def transferir_atendimento(motivo: str) -> str:
"""..."""
session_id = _current_session_id.get()
phone = _current_phone_number.get()
# ...
Threads¶
ContextVar é thread-local por padrão (e também isolada por
asyncio Task). Como o workflow roda em threading.Thread, cada request
tem seu próprio valor — sem colisão entre conversas simultâneas.
Cuidado com threading.Thread manual¶
Se você spawna uma thread filha dentro de uma tool, o valor do
ContextVar não se propaga automaticamente:
# não propaga — thread filha vê string vazia
threading.Thread(target=worker).start()
# propaga — copia contexto manualmente
import copy
ctx = copy.copy(contextvars.copy_context())
threading.Thread(target=ctx.run, args=(worker,)).start()
Em prática, as tools dos projetos atuais não spawnam threads filhas.
Quando NÃO usar contextvar¶
Se o valor é sem importância para o LLM e pode variar por chamada, passar como parâmetro ainda é o idiomático. Contextvar é para estado ambiente que não pode errar — session, phone.
Anti-padrão observado¶
Nenhum dos dois projetos cometeu, mas é tentador:
# NÃO FAÇA
_globals = {}
def run_agent(..., session_id, ...):
_globals["session_id"] = session_id # não é thread-safe
@tool
def minha_tool():
return _globals["session_id"] # race condition garantida
Use ContextVar. Foi feito para isso.
Atalhos úteis¶
def current_session() -> str:
return _current_session_id.get()
def current_phone() -> str:
return _current_phone_number.get()
def current_name() -> str:
return _current_contact_name.get()
Evita importar os _current_* diretamente em vários lugares.