Pular para conteúdo

Tools expostas ao agente

Três tools (exatamente) em todo projeto OTIMUS:

  1. transferir_atendimento(motivo)
  2. consultar_horarios(nome_medico, nome_procedimento, nome_convenio, data_interesse="")
  3. agendar_consulta(nome_completo, cpf, data_nascimento, nome_medico, data_exame, hora_exame, nome_procedimento, nome_convenio)

Mais tools adicionam complexidade sem valor — se surgir necessidade, pense duas vezes.

Decorator @tool

from langchain_core.tools import tool

@tool
def consultar_horarios(nome_medico: str, nome_procedimento: str, nome_convenio: str) -> str:
    """Use essa ferramenta para verificar disponibilidade de horarios.

    Parametros:
    - nome_medico: nome do médico (ou "" se sem preferência)
    - nome_procedimento: nome exato do procedimento
    - nome_convenio: PARTICULAR ou convênio aceito

    Se a resposta iniciar com FALHA_CONSULTA, transfira para equipe humana
    e NAO invente horarios.
    """
    ...

A docstring é o prompt da tool — o LLM usa isso para decidir quando e como chamar. Trate-a como parte do system prompt.

1. transferir_atendimento(motivo: str)

Quando chamar (conforme prompt)

  • Ao final do agendamento, depois de confirmar dados.
  • Solicitações que a clínica não atende automaticamente (exames especiais).
  • Quando o paciente pede "falar com humano" / "atendente".
  • Quando há loop sem evolução na conversa.
  • Cancelamento ou reagendamento (fora do escopo do agente).
  • Mensagens ofensivas, vídeos, spam.
  • Urgências.
  • Edad gestacional incompatível com exame (Clinfeto).

Implementação

# agent.py (Clinfeto loopback HTTP — padrão legacy)
@tool
def transferir_atendimento(motivo: str) -> str:
    """..."""
    session_id = _current_session_id.get()
    phone = _current_phone_number.get()
    try:
        resp = requests.post(
            TRANSFERIR_ATENDIMENTO_URL,
            json={"id session": session_id},
            timeout=30,
        )
        resp.raise_for_status()
        log_transfer(session_id)
        return "Atendimento transferido com sucesso."
    except Exception as e:
        log_error("agent_error", f"Erro ao transferir: {e}", session_id, phone)
        return f"Erro ao transferir: {e}"

# VERSÃO RECOMENDADA (in-process)
@tool
def transferir_atendimento(motivo: str) -> str:
    """..."""
    session_id = _current_session_id.get()
    try:
        transfer_session(session_id)   # wts_client
        log_transfer(session_id)
        return "Atendimento transferido com sucesso."
    except Exception as e:
        log_error("agent_error", f"Erro transferir: {e}", session_id)
        return f"Erro ao transferir: {e}"

O que o agente faz depois

Após chamar transferir_atendimento, o agente geralmente ainda gera uma mensagem final para o paciente (ex: "Transferi você para um atendente, que entrará em contato em breve"). O LLM faz isso sozinho se bem prompted.

2. consultar_horarios(...)

Docstring recomendada

@tool
def consultar_horarios(
    nome_medico: str,
    nome_procedimento: str,
    nome_convenio: str,
    data_interesse: str = "",
) -> str:
    """Verifica disponibilidade de horarios na agenda do medico.

    Parametros:
    - nome_medico: nome do medico (ex: "Dr. Rennan", "Dra. Joana").
      Se o paciente nao tem preferencia, passe string vazia "" ou
      "sem preferencia".
    - nome_procedimento: nome EXATO do procedimento conforme a lista da
      clinica (no system prompt).
    - nome_convenio: nome do convenio (ex: PARTICULAR, UNIMED, BRADESCO SAUDE).
    - data_interesse: opcional, formato AAAA-MM-DD.

    ATENCAO:
    - Verifique a lista de procedimentos antes de chamar.
    - Se a resposta iniciar com FALHA_CONSULTA, transfira para humano e
      NAO invente horarios.
    - NUNCA chame essa ferramenta mais de 2 vezes no mesmo turno.
    """

Implementações possíveis

@tool
def consultar_horarios(nome_medico, nome_procedimento, nome_convenio, data_interesse=""):
    """..."""
    from scheduling import consultar_horarios as _do
    return _do(nome_medico, nome_procedimento, nome_convenio)
@tool
def consultar_horarios(nome_medico, nome_procedimento, nome_convenio, data_interesse=""):
    """..."""
    try:
        resp = requests.post(
            CONSULTAR_HORARIOS_URL,
            json={
                "nome_medico": nome_medico,
                "nome_procedimento": nome_procedimento,
                "nome_convenio": nome_convenio,
                "data_interesse": data_interesse,
            },
            timeout=60,
        )
        resp.raise_for_status()
        payload = resp.json()
        return payload.get("result", "") or "FALHA_CONSULTA: resposta vazia"
    except Exception as e:
        return f"FALHA_CONSULTA: erro ao consultar: {e}"

3. agendar_consulta(...)

Docstring recomendada

@tool
def agendar_consulta(
    nome_completo: str,
    cpf: str,
    data_nascimento: str,
    nome_medico: str,
    data_exame: str,
    hora_exame: str,
    nome_procedimento: str,
    nome_convenio: str,
) -> str:
    """Cria o agendamento apos confirmacao do paciente.

    Parametros:
    - nome_completo: nome completo do paciente
    - cpf: somente numeros, 11 digitos
    - data_nascimento: formato AAAA-MM-DD
    - nome_medico: nome do medico (mesmo usado em consultar_horarios)
    - data_exame: formato AAAA-MM-DD
    - hora_exame: formato HH:MM (24h)
    - nome_procedimento: nome EXATO do procedimento
    - nome_convenio: nome do convenio por extenso (NUNCA ID)

    O telefone e injetado automaticamente (nao passe como parametro).

    Se a resposta iniciar com FALHA_CONSULTA, transfira para humano e
    NAO tente novamente.
    """

Injeção de telefone

Nunca deixar o LLM passar o telefone como parâmetro. Injeção via contextvar:

telefone = _current_phone_number.get()   # dentro da tool

Razão: o telefone vem do webhook WTS e é imutável para a sessão. Se o LLM tivesse que passar, teria como errar.

Design das tools — princípios

  1. Docstring é prompt. Seja explícito sobre formato, limites, e o que fazer em erro.
  2. Só strings. Use retornos str (facilita parsing pelo LLM). Se retornar JSON, embrulhe em string.
  3. Nunca lance exceção. Tools que levantam exception abortam o agente. Capture e retorne FALHA_CONSULTA: ....
  4. Não passe session_id, phone, contact_name como parâmetro. Use contextvars. Ver Contextvars.
  5. Idempotência. Chamar consultar_horarios duas vezes seguidas deve dar o mesmo resultado (barra tempo passar). Não mantenha estado na tool.