Construa um agente

Esta página leva você do zero a um agente seu, respondendo sobre HTTP: uma conta, uma chave, uma licença, duas ferramentas, um agent.yaml, um turno, uma confirmação e o stalwart serve.

Abra uma conta

Cadastre-se em stalwart.it, com o seu endereço de e-mail ou com o Google. Uma conta gratuita é aberta com a sua primeira chave de API, no console em api.stalwart.it/console. A chave é mostrada uma única vez, quando é criada: copie-a nesse momento. O que o plano gratuito inclui, e o que os planos pago e enterprise acrescentam, está em stalwart.it.

Instale o pacote e ative a licença

$ pip install "stalwart-agents[anthropic]"    # or openai, google, mistral, ollama; or none, for no model at all
$ export QLS_API_KEY=qls_live_...
$ stalwart licence activate

stalwart licence activate é executado uma vez em cada máquina que executa os seus agentes. Nada no pacote funciona sem a chave e uma licença ativada; o stalwart doctor diz o que falta e como corrigir. A licença e o runtime diz o que é uma licença e o que funciona sem ela.

Escreva as ferramentas

Uma ferramenta é uma função Python comum. As suas anotações de tipo são o seu esquema, a primeira linha da sua docstring é o que ela faz, e @tool(...) lhe dá as palavras que as verificações leem:

# shop/tools.py
from decimal import Decimal

from stalwart_agents import tool


def get_order(order_id: str) -> dict:
    """Show an order: its items, its total and its status."""
    return {"order_id": order_id, "total": "42.00", "status": "paid"}


@tool(writes=True, undo="refund_card")
def charge_card(order_id: str, amount: Decimal) -> dict:
    """Charge the card on file for an order."""
    return {"charged": str(amount)}


@tool(writes=True)
def refund_card(order_id: str, amount: Decimal) -> dict:
    """Refund an amount to the card on file for an order."""
    return {"refunded": str(amount)}

writes=True diz que a ferramenta altera algo, de modo que a sua chamada é verificada pelas suas regras antes de ser executada. undo="refund_card" diz como a escrita é desfeita se a execução falhar depois dela.

Declare o agente

# agent.yaml
agents:
  desk:
    model: "anthropic:claude-sonnet-5-5"   # your provider and key, from your environment
    tools: [get_order, charge_card, refund_card]
    on_unread: model                       # a turn no tool covers goes to the model
tools:
  - python: shop.tools                     # every public function of the module
runs: atomic                               # a failed run is undone, last write first
storage: sqlite:///runs.db                 # where runs and waiting confirmations are kept

O modelo só é chamado onde este arquivo envia um turno para ele: aqui, um turno que nenhuma ferramenta cobre. Omita model, ou defina on_unread: refuse, e nenhum modelo é chamado (exemplos completos).

Verifique-o antes de colocá-lo no ar

$ stalwart verify agent.yaml

O stalwart verify registra o catálogo, verifica cada caminho do grafo dos agentes e caça bugs no rulebook, e sai com 1 quando o portão falha, de modo que funciona na CI como funciona aqui. O catálogo que ele registra é aquele com que a planilha de regras abaixo é confrontada, e ele traz o rulebook contra o qual o grafo foi provado, para que cada verificação de ferramenta o aplique (protegido e provado).

Defina as regras

As regras sobre as suas ferramentas são uma planilha que uma pessoa pode ler em voz alta. Aqui, toda cobrança espera por uma pessoa, e nada acima de 200 é reembolsado sem a aprovação de um supervisor:

# rules.yaml
rules:
  - action: charge_card
    requires_confirmation: true
  - action: refund_card
    requires: [supervisor_approved]
    when: { amount_over: 200 }
$ stalwart rules compile rules.yaml --save

--save registra a planilha no seu catálogo mais recente, e toda verificação de ferramenta a aplica a partir de então. Uma regra que o compilador não consegue expressar com exatidão é recusada com a sua linha, nunca aproximada.

Converse com ele

from stalwart_agents import Agent

desk = Agent.from_file("agent.yaml")        # the graph is verified once, before the first turn
reply = desk.chat("show me order 4411", user="u-17")
print(reply.text)                           # get_order was chosen from the words; no model was asked

Confirme uma escrita

Uma escrita que, segundo as regras, precisa de uma pessoa espera por ela. A resposta diz o que espera, nas palavras que uma pessoa aprova:

reply = desk.chat("charge 42.00 to order 4411", user="u-17")
for waiting in reply.waiting:
    print(waiting.tool, waiting.wording, waiting.decisions)   # charge_card ... ['approve', 'skip', 'cancel_all']
reply = desk.confirm("u-17", "approve")     # the call runs now, with the exact arguments shown

skip recusa esta chamada e segue adiante; cancel_all interrompe a execução e desfaz o que ela escreveu.

Coloque-o no ar

$ export QLS_OPERATOR_TOKEN=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
$ stalwart serve agent.yaml

Abra http://127.0.0.1:8700/ para a interface de chat, com as suas confirmações, recusas e reversões. As mesmas rotas respondem a qualquer cliente:

$ curl -X POST http://127.0.0.1:8700/threads/t-17/messages -d '{"text": "show me order 4411"}'
$ curl http://127.0.0.1:8700/threads/t-17/interrupts                 # what waits for a person
$ curl -X POST http://127.0.0.1:8700/threads/t-17/resume -d '{"interrupt_id": "...", "decision": "approve"}'

Um operador desfaz a execução de uma conversa com POST /threads/{id}/rollback, que exige o token de operador como Authorization: Bearer; sem token definido (--operator-token ou QLS_OPERATOR_TOKEN), a rota responde 403 e diz como definir um. Com storage: postgres://..., vários workers respondem às mesmas conversas, e uma confirmação é retomada em qualquer worker que a receba.

A seguir