Ir para o conteúdo

9 · Acompanhando uma resolução longa

A maioria das verificações termina em menos de um segundo. Algumas, como uma enumeração de todas as maneiras de uma política quebrar, uma random forest grande ou uma caçada sobre um rulebook extenso, levam minutos. Enquanto um job roda, o serviço transmite o que o motor está fazendo, quadro a quadro, e este capítulo diz como lê-lo, como retomá-lo e o que fazer onde uma rede não deixa um stream passar.

O stream

GET /v1/solves/{id}/events
Accept: text/event-stream
Last-Event-ID: 41                 (opcional; ?after=41 é o mesmo)
Authorization: Bearer qls_live_…

O stream primeiro repete tudo o que o job já registrou, depois o acompanha ao vivo, e se fecha quando o job chega a um estado terminal. No cliente, stream=True em qualquer método de verificação lhe dá os quadros como um iterador que termina no quadro terminal:

frames = client.verify_guardrail(
    "gemini.co", "respond with permitted investment answer", "has_advising_permission",
    pins={"personal_recommendation": 1}, profile="enumerate", stream=True,
)
for event in frames:                      # resumed by itself if the connection drops
    if event.type in ("phase", "verdict", "succeeded", "failed", "stream_degraded"):
        print(event.type, event.data.get("phase") or event.data.get("status") or "")

Com o curl, passe -N. Sem ele, o curl acumula a resposta e imprime a resolução inteira quando ela termina, o que parece exatamente o endpoint não funcionando.

Os quadros

Todo quadro tem o mesmo envelope: id, type, job_id, seq, at e data. Só data varia, e type é um de um conjunto fixo:

Evento Significado
accepted a requisição foi validada e existe um id de job
queued o job está esperando um worker; emitido de novo quando uma concessão expira, com a contagem de tentativas aumentada
leased um worker o assumiu
lease_lost o worker que o detinha parou de renovar a sua concessão; a tentativa é abandonada e o job volta para a fila
started o solver está executando, e os argumentos com que foi executado
compiled a política foi analisada e o circuito existe; as recusas de tempo de compilação já foram avaliadas a esta altura
generation um passo de uma requisição de escrita assistida: o modelo consultado, a sua resposta recebida, o rascunho compilado, uma rodada concluída
progress uma amostra de progresso do solver
phase uma fase do solver começou ou terminou
verdict a projeção do veredito, assim que o resultado é analisado e antes de os artefatos serem enviados
artifact um artefato terminou de ser enviado e pode ser baixado
succeeded terminal: a execução funcionou e um resultado foi analisado, seja qual for o veredito nele
failed terminal: nenhum resultado foi produzido; traz o motivo classificado e o fim da saída de erro
cancelled terminal: quem chamou retirou o job
drain_requeue o prazo de esvaziamento de uma implantação foi excedido com o job ainda rodando; o worker deixa a sua concessão expirar de propósito, para o job voltar à fila em vez de se perder
expired o job nunca foi assumido e o seu tempo de vida acabou; terminal, e nada rodou
refused uma regra que a borda não conseguiu avaliar foi avaliada com a política em mãos e a requisição foi recusada; terminal, e nenhum orçamento de resolução foi gasto
heartbeat o stream está vivo; nenhum estado mudou

O quadro terminal traz o job inteiro, exatamente como GET /v1/solves/{id} o devolveria. Um cliente que acompanhou um job até o fim não precisa de nenhuma outra requisição para saber o veredito.

O que os números de progresso dizem

Um quadro progress é uma amostra da fase que está rodando, e um quadro phase marca o início ou o fim de uma fase, com o motivo que o motor dá. Quatro fases relatam progresso:

  • phase0, o solver SAT: quantos fatos ele já estabeleceu (unary), quantas relações de duas ou de muitas variáveis ele aprendeu (binary_xor, multi_xor) e quantas rodadas se passaram sem nenhuma.
  • cascade, a expansão algébrica: o rank alcançado e quanto ele cresceu desde a última amostra (rank_delta). Rank subindo é a expansão trabalhando; rank parado é a expansão terminada, ou presa.
  • phase3, o fechamento que conta todas as soluções: a sua round, a basis que ele construiu e as obstructions que restam. Enquanto ele roda, o rank fica congelado e não diz nada; basis subindo enquanto obstructions cai em direção a zero é o sinal honesto de progresso.
  • seatbelt, a busca do reparo: quantas candidatas ela já examinou (scanned) do total, a melhor pontuação encontrada (best) e o alvo em que ela para (target). best é null até algo ter sido pontuado.

Uma execução curta emite quase nenhuma amostra, e as suas fronteiras de fase contam a história toda. Uma tabela de propriedades marca cada linha com uma fronteira solve_begin que traz o id da linha, para que o trabalho de uma linha nunca seja atribuído a outra.

Não há porcentagem

De propósito. O motor não consegue saber quanto tempo um fechamento vai levar até tê-lo terminado, então uma barra de progresso seria um palpite apresentado como uma medição. Os números acima são o que se sabe, e os quadros os trazem como são.

Retomando

id é o número de sequência do quadro, então Last-Event-ID é um cursor: devolva o último id que você viu e receba tudo o que vem depois dele, uma vez cada. ?after=N é o mesmo cursor para uma pilha HTTP que não define o cabeçalho. Em qualquer número de reconexões, os quadros que você recebe são exatamente a sequência do job, uma vez cada. O cliente retoma por você.

Um worker que falha no meio de um job aparece, não é escondido: o stream traz lease_lost, depois leased e started de novo, com attempt aumentado. O relógio do próprio motor recomeça do zero enquanto seq continua, e todo quadro traz attempt, para que um gráfico possa manter as duas execuções separadas.

Um comentário, : heartbeat <hora>, chega a cada 15 segundos, mais curto que o tempo ocioso de qualquer proxy, para que um trecho silencioso de uma resolução longa não tenha a conexão cortada. Um stream em que nada chega por vinte minutos é fechado; reconecte com o seu cursor e ele continua de onde parou.

Quando um stream não passa

Algumas redes acumulam ou cortam respostas de longa duração. Os mesmos quadros estão disponíveis por consulta: GET /v1/solves/{id}/events?mode=poll&after=N devolve uma lista JSON limitada, lida do mesmo cursor, na mesma ordem, e GET /v1/solves/{id} traz um bloco progress, o quadro mais novo por fase, com o seq a partir do qual consultar. É a mesma informação, num ritmo mais espaçado.

O cliente passa a consultar sozinho depois de dois streams que falharam, e diz isso com um quadro stream_degraded antes de continuar. Se um stream chega todo de uma vez, no fim, há um proxy acumulando respostas entre você e nós; o capítulo 15 diz como encontrá-lo. O stalwart doctor verifica se o streaming passa pela sua rede.

Os limites de streams

Limite Valor Quando é alcançado
Streams por chave o streams do seu plano 429 stream_limit
Streams por job 8 429 stream_limit_job
Novos streams por chave por minuto 60 429 stream_limit_rate

Cada um traz Retry-After. Uma chave revogada enquanto um stream está aberto é desconectada em até 15 segundos.

O que um stream não é

  • Não é um controle. O stream só lê. Para cancelar um job, DELETE /v1/solves/{id}.
  • Não é um webhook. O serviço não faz chamadas de saída para você; uma equipe que quer ser avisada constrói isso sobre este stream.
  • Não é o veredito. Só o quadro terminal, ou o próprio job, traz o veredito. Um quadro de progresso que parece promissor não promete nada.

Os limites

  • Os números de progresso descrevem o trabalho do motor, não quanto dele falta.
  • Um stream que cai não perde nada se você retomar com o seu cursor; um cliente que recomeça do início recebe os quadros de novo.
  • Só o quadro terminal, ou GET /v1/solves/{id}, é a resposta.