35 · Pessoas no circuito¶
Idiomas
O seletor entende turnos escritos em inglês. O português é a sua próxima gramática e ainda não foi lançado; até lá, um turno escrito em português não é lido como português.
Algumas chamadas esperam por uma pessoa: toda escrita, antes de rodar; uma escrita de que ninguém conhece o
resultado; um desfazer que continua falhando. Este capítulo mostra o que essa pessoa vê e responde, como uma
resposta chega à execução onde quer que ela espere -- em outro worker, vinda de outro canal -- e a interface
que mostra tudo isso, @stalwart-agents/agent-ui, que o stalwart serve lhe dá antes que você escreva
qualquer parte dela.
Uma confirmação, do lado da pessoa¶
A cobrança do agente da loja do capítulo 30, enquanto espera (a saída é impressa em inglês):
{"card": "visa-0019", "customer": "c-208", "order_id": "4411", "status": "unpaid", "total": "40.00"}
charge_card | Run charge_card? | ['approve', 'skip', 'cancel_all']
{'name': 'amount', 'value': 40, 'origin': 'typed'}
{'name': 'order_id', 'value': '4411', 'origin': 'typed'}
{"amount": "40", "charge_id": "ch-4411", "order_id": "4411"}
Uma confirmação mostra:
- a chamada: a ferramenta e a sua pergunta --
Run charge_card?, ou o texto que a sua ferramenta dá; - cada argumento, com a origem do seu valor:
typed(a pessoa o digitou),reply(ela respondeu a uma pergunta),context(a sua aplicação o deu, como o cliente autenticado),computed,result:<passo>(um passo anterior o devolveu), oumodel(o modelo o escreveu). A interface diz isso em palavras: you typed it, the assistant wrote it; - os motivos por que ela espera, como a verificação os deu:
write,argument_origin: amount,tainted_call: untrusted:read_inbox(capítulo 32); - o aviso de irreversível, para uma ferramenta marcada
irreversible(capítulo 34); - as respostas que ela aceita.
As respostas¶
Toda pausa que uma execução faz, e as respostas que cada uma aceita, geradas a partir do produto:
| Tipo | Quando uma execução pausa com ele | Respostas que aceita |
|---|---|---|
confirm |
uma chamada que a verificação respondeu com confirm |
approve, skip, cancel_all |
outcome_unknown |
uma escrita que pode ou não ter tido efeito, e que não é segura para enviar de novo | happened, did_not_happen |
saga_stuck |
um desfazer que falhou depois das suas novas tentativas, com o que ainda falta desfazer | retry, done_by_hand, abandon |
ask |
uma pergunta que a execução precisa ver respondida para seguir | answer |
delegate |
um turno que nada lê, com on_unread: ask: o modelo pode recebê-lo? |
approve, decline |
approve roda a chamada exatamente com os argumentos mostrados, e nada mais. Uma confirmação aprova
apenas a chamada mostrada: o seu token cobre aquela ferramenta com aqueles argumentos naquele ponto da
execução, então uma chamada cujos argumentos mudaram depois de ser mostrada -- pelo modelo, ou por qualquer
outra coisa -- é verificada de novo e pergunta de novo (capítulo 32).
skip e cancel_all estão no capítulo 34.
Em Python, reply.waiting lista o que espera, e desk.confirm(user, "approve") responde.
Retomando em qualquer worker¶
Uma execução que espera é guardada onde o storage: diz -- sqlite:///runs.db para uma máquina,
postgres://... para várias. Toda pausa tem um id estável: a mesma chamada, pausada pelo mesmo motivo, tem o
mesmo id em todo worker. Assim, a resposta pode chegar em qualquer lugar: o worker que a recebe lê a execução
do armazenamento, leva a resposta até ela, e segue. Nada fica guardado no processo entre uma pergunta e a sua
resposta.
Várias pendentes ao mesmo tempo¶
Os agentes que o agent.yaml descreve tratam as chamadas de um turno em ordem, cada uma até o fim, então uma
execução deles espera por uma coisa de cada vez. Passos que uma equipe escreve por conta própria podem rodar
chamadas lado a lado, e então uma execução espera por várias ao mesmo tempo. Cada uma é respondida pelo seu
próprio id -- desk.confirm(user, "approve", waiting=id), ou o id em POST /threads/{id}/resume -- e as
outras continuam abertas. Sem id, desk.confirm responde a primeira que espera; GET
/threads/{id}/interrupts lista todas.
Uma retomada obsoleta¶
Uma resposta a uma pausa que já não está aberta -- outra pessoa a respondeu, e a execução seguiu -- é
recusada como 409 interrupt_stale, e não muda nada. Duas respostas à mesma pausa que chegam juntas, num
worker ou em dois, são distinguidas por uma reivindicação guardada no armazenamento de execuções: a primeira
a obtém e retoma a execução; a outra é interrupt_stale. Uma resposta que carrega um token diferente do que
foi mostrado à pessoa também é interrupt_stale. A interface lê de novo o que espera quando recebe uma.
Aprovação vinda de outro canal¶
Uma confirmação pode ser respondida de outro lugar que não o chat: uma fila, um e-mail, uma ferramenta de
chat que a sua equipe já usa. A aplicação que o stalwart serve roda, make_app em stalwart_agents.serve,
recebe dois argumentos para isso:
on_interrupt=, avisado de cada nova pausa como(thread, interrupt). OWebhook(url, secret)do pacote é um deles: ele envia a pausa como JSON, assinada num cabeçalhoStalwart-Signature: t=<tempo>,v1=<hmac>, o HMAC-SHA256 do tempo e do corpo sob o seu segredo. Um receptor só aceita uma entrega dentro de cinco minutos do seu próprio relógio, então uma entrega capturada não pode ser reapresentada depois. Um envio que falha nunca faz o turno falhar, pois ele já rodou: a resposta lista o que não pôde enviar emnot_notified;secret=: quando definido, uma retomada também precisa carregar umStalwart-Signatureválido, então só o canal que tem o segredo pode responder.
O próprio stalwart serve não recebe nenhum dos dois; uma equipe que quer aprovações vindas de outro canal
monta o make_app no seu próprio servidor.
Se você já usa LangGraph
O Webhook fica junto do servidor de retomada, stalwart_agents.langchain.server, que se monta em
qualquer aplicação ASGI sobre o seu próprio checkpointer; o
capítulo 27 é a porta para ele.
O que o stalwart serve lhe dá¶
$ stalwart serve agent.yaml
serve os seus agentes por HTTP, com a interface em /, antes que você escreva qualquer interface. As suas
rotas, geradas a partir do pacote:
| Rota | Faz |
|---|---|
GET / |
a interface: chat, confirmações, recusas com a sua regra, rollbacks |
POST /threads/{id}/messages |
um turno: {"text": ...}; a resposta traz a resposta do agente, o que espera por uma pessoa, e qualquer recusa ou rollback como dados |
GET /threads/{id}/interrupts |
o que espera por uma pessoa na conversa, cada coisa pelo seu próprio id |
POST /threads/{id}/resume |
a decisão de uma pessoa sobre uma delas: {"interrupt_id", "decision", "token"}; as outras continuam abertas |
GET /threads/{id}/stream |
os passos da conversa como server-sent events, retomáveis por Last-Event-ID |
POST /threads/{id}/rollback |
um operador desfaz a execução da conversa, com Authorization: Bearer <token de operador> |
POST /threads/{id}/feedback |
o polegar de uma pessoa numa resposta: {"message", "thumb"}, o polegar up ou down, vindo da sessão a que a resposta foi mostrada |
GET /healthz |
200 enquanto o serviço responde |
GET /config.json |
com o que a interface em / está configurada, o nome do agente e a base sob a qual estão as suas rotas |
As suas opções:
| Opção | O que o produto passa a fazer |
|---|---|
--host |
o endereço em que escutar; 127.0.0.1 quando não informado |
--port |
a porta; 8700 quando não informado |
--agent |
o agente que responde primeiro; o primeiro do arquivo quando não informado |
--operator-token |
o que a rota de rollback precisa carregar como Authorization: Bearer; QLS_OPERATOR_TOKEN quando não informado, e sem nenhum dos dois a rota responde 403 |
O rollback do operador desfaz a execução de uma conversa de fora dela. Ele recebe o token de operador como
Authorization: Bearer <token>, comparado em tempo constante; sem token definido, a rota fica desligada e
responde 403, dizendo como definir um.
@stalwart-agents/agent-ui¶
A interface que o stalwart serve mostra é um pacote próprio, para uma equipe que põe a conversa nas suas
próprias páginas:
$ npm install @stalwart-agents/agent-ui
Incorporando¶
Importar o pacote define os seus elementos; um deles é a conversa inteira:
<stalwart-chat thread="u-17" base="/agent"></stalwart-chat>
base é onde o seu stalwart serve (ou a sua própria montagem dele) responde; thread é o id da conversa.
Todos os elementos, gerados a partir do código do pacote:
| Elemento | Mostra | Atributos | Propriedades | Eventos | Partes CSS | Slots |
|---|---|---|---|---|---|---|
<stalwart-chat> |
a conversa, e o que estiver esperando uma decisão | base, theme, thread, thumbs |
eventSource, fetcher |
— | thumbs, thumbs-notice, log, status, steps, composer, citations, sources |
header, footer |
<stalwart-confirm> |
uma chamada que espera, os seus argumentos com as suas origens, os motivos, o aviso de irreversível e as suas respostas | theme |
interrupt |
stalwart-decision |
confirm, turn, arguments, reasons, irreversible, choices |
— |
<stalwart-decision> |
uma recusa numa frase, a sua regra ao expandir | rule, sentence, theme |
— | — | decision, rule |
— |
<stalwart-rollback> |
o que uma execução desfez, e o que não pôde desfazer | theme |
report |
— | rollback |
— |
<stalwart-trace> |
os passos de uma conversa | theme |
events |
— | trace |
— |
<stalwart-confirm> dispara stalwart-decision com a pausa e a resposta escolhida; <stalwart-chat> a
envia ao backend por você.
O backend com que ela fala¶
A interface fala com base e com nada mais: as rotas acima -- um turno, o que espera, uma decisão,
o fluxo de passos, um polegar. Nenhuma telemetria, nenhuma fonte, nenhum script de outro host. Cada
requisição nomeia a sessão da página, depois que o servidor entrega uma, então um polegar só é aceito da
sessão a que a resposta foi mostrada.
Aplicando um tema¶
Toda cor e todo tamanho é uma propriedade personalizada de CSS, com um padrão claro e um escuro;
theme="light" ou theme="dark" fixa um deles, e sem nenhum dos dois a configuração da própria pessoa
escolhe:
| Propriedade personalizada | Claro | Escuro |
|---|---|---|
--stalwart-bg |
#ffffff |
#121212 |
--stalwart-fg |
#1a1a1a |
#f1f1f1 |
--stalwart-muted |
#595959 |
#bdbdbd |
--stalwart-line |
#d0d0d0 |
#3c3c3c |
--stalwart-accent |
#0b57d0 |
#8ab4f8 |
--stalwart-accent-fg |
#ffffff |
#0b0b0b |
--stalwart-warn |
#8a4b00 |
#ffb74d |
--stalwart-bad |
#b3261e |
#f28b82 |
--stalwart-radius |
8px |
8px |
--stalwart-gap |
12px |
12px |
--stalwart-font |
system-ui, sans-serif |
system-ui, sans-serif |
Cada região é uma ::part (listadas acima), e <stalwart-chat> tem os slots header e footer para o seu
próprio conteúdo. Para as cores da loja, por exemplo:
stalwart-chat { --stalwart-accent: #2e7d32; --stalwart-radius: 4px; --stalwart-font: Georgia, serif; }.
Trocando as suas palavras¶
Toda palavra que a interface mostra é uma string com uma chave, e setStrings({ approve: "Aprovar", skip:
"Pular este passo" }) troca qualquer uma delas; um {name} numa string é preenchido onde ela é mostrada. As
strings de fábrica:
| Chave | Texto de fábrica |
|---|---|
conversation |
Conversation |
placeholder |
Write a message |
send |
Send |
sending |
Sending… |
waiting |
Waiting for your decision |
confirmTitle |
Confirm {tool}? |
argumentsTitle |
What it will use |
origin_typed |
you typed it |
origin_reply |
you answered it |
origin_context |
from your account |
origin_computed |
calculated |
origin_model |
the assistant wrote it |
origin_result |
from an earlier step |
origin_agent |
from another assistant |
reasonsTitle |
Why this needs you |
irreversible |
Nothing can undo this step once it runs. |
approve |
Approve |
skip |
Skip this step |
cancelAll |
Cancel everything |
refused |
Not done |
rule |
Rule |
showRule |
Which rule? |
rollbackTitle |
Undoing what this run did |
undone |
Undone |
notUndoable |
Could not be undone |
stuck |
Undoing {tool} failed. What should happen? |
retry |
Try again |
doneByHand |
I undid it myself |
abandon |
Leave it |
unknownTitle |
Did {tool} take effect? |
happened |
Yes, it happened |
didNotHappen |
No, it did not |
delegateTitle |
I couldn't work that out. Shall I pass it to the assistant? |
delegateApprove |
Yes, pass it on |
decline |
No |
reconnecting |
Reconnecting… |
traceTitle |
What happened |
step |
Step {step} |
error |
Something went wrong: {message} |
thumbUp |
Helpful |
thumbDown |
Not helpful |
thumbsNotice |
A rated message is kept to improve the assistant, and nothing else of the conversation. |
sources |
Sources |
Os limites¶
- Uma confirmação aprova apenas a chamada mostrada, com os seus argumentos; um argumento mudado pergunta de novo. Ela prova que uma pessoa aprovou, não que ela leu.
- Uma resposta chega a uma execução que espera; ela nunca muda uma que já seguiu (
interrupt_stale). - O receptor de um webhook é seu: a assinatura prova que a entrega veio do seu servidor dentro de cinco minutos, e nada sobre quem a respondeu do outro lado.
- A interface mostra o que a execução relata -- a regra de uma recusa, os desfazeres de um rollback -- nunca uma leitura das palavras da resposta.