36 · Vendo o que aconteceu¶
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.
Quando uma execução faz algo que você não esperava, você quer vê-la passo a passo: o que o roteador leu, o que
cada verificação decidiu e por quê, qual chamada rodou, o que foi desfeito. stalwart trace mostra uma execução
como uma linha do tempo, a partir do armazenamento que o seu agente já mantém ou dos spans que um tracer gravou,
e cada decisão nela nomeia a regra que a tomou.
Uma execução como linha do tempo¶
stalwart trace tem três subcomandos:
stalwart trace show: a linha do tempo no terminal, um bloco por etapa, cada verificação com a sua resposta e os seus motivos, cada pausa por uma pessoa, cada desfazer e a etapa que ele desfaz.stalwart trace html ... -o run.html: a mesma linha do tempo como um único arquivo HTML autocontido, sem script e sem requisição a lugar nenhum, para um ticket ou uma auditoria.stalwart trace diff a.jsonl b.jsonl: onde duas execuções da mesma entrada seguiram caminhos diferentes: a primeira etapa cujas decisões diferem, e o que só cada execução fez ali.
show e html leem um arquivo de spans ou, com --thread e --store, o armazenamento do seu agente.
O mesmo só a partir do armazenamento¶
Uma execução que o seu agente guardou em storage: pode ser lida de volta sem nada gravado antes. O pedido que
falhou do E6 (capítulo 34), com o armazenamento da loja em disco:
desk = Agent.from_file("agent.yaml") # storage: sqlite:///runs.db
reply = desk.chat("charge order 4411 40.00, then update the crm for order 4411, "
"then provision the account for order 4411", user="u-17")
while reply.waiting:
reply = desk.confirm("u-17", "approve") # each write approved as it comes
print(reply.text)
desk.close()
This run stopped: provision_account failed (RuntimeError). Undone, latest first: update_crm (by restore_crm), charge_card (by refund_card).
A conversa é desk/u-17: o nome do agente e o da pessoa. Lida de volta a partir do armazenamento:
$ stalwart trace show --thread desk/u-17 --store sqlite:///runs.db
Run: thread desk/u-17
step -1
node input
step 0
node run
step 1
node run
step 2
node run
check charge_card -> confirm
interrupt charge_card [write] (confirm)
step 3
node run
check charge_card -> allow
step 4
node run
call charge_card
step 5
node run
step 6
node run
step 7
node run
check update_crm -> confirm
interrupt update_crm [write] (confirm)
step 8
node run
check update_crm -> allow
step 9
node run
call update_crm
step 10
node run
step 11
node run
step 12
node run
check provision_account -> confirm
interrupt provision_account [write] (confirm)
step 13
node run
check provision_account -> allow
step 14
node run
call provision_account
step 15
node run
undo restore_crm -> allow undoes step 3
step 16
node run
step 17
node run
undo refund_card -> allow undoes step 1
step 18
node run
step 19
node run
step 20
node run
Cada escrita foi verificada e perguntada (confirm, [write]), aprovada (allow) e executada;
provision_account falhou na etapa 14, e os dois desfazeres vêm em seguida, cada um verificado, o mais recente
primeiro: restore_crm desfaz a atualização do CRM (etapa 3 do histórico) e refund_card a cobrança (etapa 1 do
histórico). Uma etapa sem nada embaixo é uma que só fez a execução avançar.
Spans para qualquer ferramenta de OpenTelemetry¶
Um tracer grava um span em torno de cada um destes, gerados a partir do tracer do pacote:
| Span | Aberto em torno de |
|---|---|
stalwart.node |
cada etapa de uma execução |
stalwart.plan |
o roteador lendo um turno |
stalwart.tool_check |
a verificação de ferramentas decidindo uma chamada |
stalwart.tool |
uma ferramenta rodando |
stalwart.compensate |
um desfazer rodando |
stalwart.rollback |
uma execução que falhou sendo desfeita |
stalwart.rewind |
uma execução retrocedida |
stalwart.interrupt |
uma execução pausando por uma pessoa |
stalwart.resume |
uma execução retomando com a resposta da pessoa |
O span de uma verificação de ferramenta carrega a sua decisão, como estes atributos e como um evento
stalwart.decision:
| Atributo | Contém |
|---|---|
gen_ai.operation.name |
execute_tool, como as convenções GenAI do OpenTelemetry nomeiam uma chamada de ferramenta |
gen_ai.tool.name |
a ferramenta |
gen_ai.tool.call.id |
o id da chamada |
stalwart.outcome |
a resposta da verificação: allow, confirm ou refuse |
stalwart.decided_by |
qual etapa da verificação decidiu |
stalwart.rule |
a regra, o fluxo ou o motivo por trás da resposta |
stalwart.reasons |
todos os motivos que a verificação deu |
stalwart.labels |
as marcas que a chamada carregava |
stalwart.args_digest |
os 16 primeiros caracteres hexadecimais do digest dos argumentos, nunca os valores |
stalwart.history_token |
um digest curto do token do histórico da sessão, nunca o token |
gen_ai.* são as convenções GenAI do OpenTelemetry, então qualquer ferramenta que as leia mostra as chamadas;
stalwart.* são nossos. Com pip install "stalwart-agents[otel]", os spans vão para o tracer provider do
OpenTelemetry, e dele para o exportador que você configurou. Sem nenhum exportador, um tracer JSON-lines os
escreve num arquivo, que stalwart trace show e diff leem.
O conteúdo fica desligado por padrão¶
Nenhum valor de argumento, mensagem, resultado ou turno é colocado num span a menos que o tracer seja criado
com capture_content=True. Com o conteúdo desligado, um digest de 16 caracteres fica no lugar de cada um, de
modo que dois spans podem ser comparados sem que nenhum guarde o valor. Ligá-lo acrescenta exatamente estes
atributos:
| Atributo | Conteúdo desligado (o padrão) | Conteúdo ligado (capture_content=True) |
|---|---|---|
stalwart.arguments |
stalwart.arguments_digest |
os argumentos da chamada, como valores |
stalwart.result |
stalwart.result_digest |
o resultado da ferramenta |
stalwart.message |
stalwart.message_digest |
uma mensagem da conversa |
stalwart.turn |
stalwart.turn_digest |
o turno da pessoa, como digitado |
Eles vão para onde vai todo outro atributo: para os exportadores do tracer provider que você lhe deu, ou para o arquivo JSON-lines que você nomeou. Um tracer não envia nada para nós, em nenhum dos casos: um trace é seu.
O validador de estado¶
O estado que uma execução mantém -- as suas mensagens, o seu histórico, o que espera por uma pessoa, o que desfazer -- precisa continuar consistente, e uma etapa que a sua equipe escreve pode quebrá-lo. O validador nomeia cada quebra:
| Violação | Significa |
|---|---|
call_without_result |
uma chamada que o modelo pediu não tem resultado, e a conversa seguiu adiante |
result_without_call |
um resultado não responde a nenhuma chamada da conversa |
results_out_of_order |
os resultados de uma etapa não a seguem na ordem das suas chamadas |
duplicate_message_id |
duas mensagens compartilham um id |
message_after_interrupt |
uma mensagem foi acrescentada enquanto uma pausa por uma pessoa ainda estava aberta |
stack_step_not_in_history |
a lista do que desfazer nomeia uma etapa que o histórico não tem |
history_broken |
as etapas do histórico não são 0, 1, 2, ..., ou ele tem entradas e nenhum token |
history_token_invalid |
o token do histórico não confere: uma entrada foi editada, removida ou reordenada fora da execução |
label_on_removed_message |
uma marca nomeia uma mensagem que a conversa não guarda mais |
stalwart trace show e html marcam a primeira quebra numa execução lida do armazenamento com
<<< FIRST VIOLATION, na etapa que a causou, para que a linha do tempo mostre onde o estado deu errado, e não
onde ele falhou mais tarde.
Seguindo uma decisão até a sua regra¶
Cada verificação numa linha do tempo nomeia a sua resposta e quem a decidiu, e o seu span nomeia a regra:
stalwart.decided_byé a etapa da verificação de ferramentas que decidiu (capítulo 32);stalwart.ruleé a regra, o fluxo ou o motivo: a recusa de um rulebook nomeia o seu fluxo (capítulo 45), uma regra de ordem nomeia o vínculo que quebrou;stalwart.reasonslista cada motivo que uma confirmação deu.
Se você já usa LangGraph
Os spans são abertos pela execução que recebe um tracer: desk.graph(tracer=JsonLinesTracer("run.jsonl"))
compila o grafo do agente com stalwart_agents.langchain.core.tracing.JsonLinesTracer, e
stalwart_agents.langchain.otel.OtelTracer abre os mesmos spans no OpenTelemetry, com NodeSpans como o
callback que abre um por nó. validate_state(state) e o invólucro de depuração validating(graph), que para
na primeira violação com o nó e a etapa, estão em stalwart_agents.langchain.graph.
O capítulo 27 os mostra nos termos do próprio LangGraph.
Os limites¶
- Uma linha do tempo lida do armazenamento mostra o que o armazenamento guardou: as verificações, as chamadas, as pausas e os desfazeres, não os tempos dos spans, e não o que aconteceu dentro das suas ferramentas.
- O conteúdo capturado vai para onde o seu exportador o envia. Ligue-o só onde esse destino pode guardar as palavras dos seus usuários e os resultados das suas ferramentas.