O cliente
stalwart_agents.client é o cliente da API Stalwart: escolha a
ferramenta certa para cada turno de uma conversa, verifique cada chamada de ferramenta que o seu
agente faz e verifique guardrails, grafos de agentes e random forests.
pip install stalwart-agents
Python 3.10 a 3.14. Defina QLS_API_KEY, e o cliente a lê. O cliente precisa da chave e de uma
licença ativada (stalwart licence activate, uma vez em cada máquina) para tudo, exceto
verificar um certificado (a licença e o runtime).
Planos e verificações de ferramentas leem os turnos dos seus usuários e as chamadas dos seus
agentes, por isso são respondidos no seu próprio host: pelo runtime licenciado que o wheel traz
(stalwart licence activate, uma vez) ou pelo qls-router enterprise que você executa no seu
próprio ambiente (router_url=, ou QLS_ROUTER_URL). A API hospedada da Stalwart não responde a
nenhum dos dois. Sem nenhum deles configurado, um plano ou uma verificação levanta
SelectorUnavailableError, que diz qual configurar.
Roteando uma conversa
Registre as suas ferramentas uma vez e, depois, leia cada turno de uma conversa em um plano. Nenhum modelo é consultado: o plano nomeia a ferramenta, ou a pergunta a fazer ao usuário, e a regra que decidiu.
from stalwart_agents.client import Client
client = Client()
version = client.register_catalogue(mcp_tools_list, agent="desk")
chat = client.session("desk", catalogue=version)
plan = chat.read("show me the stock in depot HB-7")
print(plan.outcome) # "run", "ask", "declined" or "unread"
for step in plan.steps:
print(step.kind, step.selection.tool, step.selection.missing)
A sessão carrega o contexto da conversa -- a pergunta ainda em aberto, os valores digitados, o que uma chamada devolveu -- de um turno para o seguinte. Ela não interpreta nada disso: o próximo contexto volta com cada plano e é enviado com o próximo turno.
plan = chat.read("show me the stock in depot HB-7")
if plan.outcome == "ask" and plan.chain:
# The step can be completed by listing first: make those calls, and report them.
results = [{"tool": c["tool"], "arguments": c["arguments"], "result": my_tools(c)} for c in plan.chain]
plan = chat.answered(plan, results)
stored = chat.to_json() # for a stateless backend; session_from_json() restores it
Um plano não tem valor de verdade: if plan: levanta um erro, porque "funcionou?" significa
quatro coisas aqui. Um plano recusado nunca é entregue a um modelo, diga o on_unread o que
disser. Um turno que precisa de um cálculo que nenhuma ferramenta responde é executado na imagem
de contêiner qls-sandbox no seu próprio host, quando há um mecanismo de contêineres e a imagem
(client.compute); quando não há, o plano é recusado, com um motivo que começa com
compute off:.
O que é enviado aos servidores da Stalwart
Uma resolução e as suas entradas (uploads incluídos), um prompt de geração de guardrails, um catálogo, uma planilha de regras e o modelo do minerador de vocabulário são analisados nesta máquina antes de qualquer envio, em busca de endereços de e-mail, números de telefone, números de cartão de pagamento, IBANs, números do National Insurance do Reino Unido, números de CPF e os identificadores que você lista como seus:
import re
from stalwart_agents.client import PersonalDataError
guarded = Client(own_identifiers=["Acme Holdings", re.compile(r"ACME-\d{6}")])
try:
guarded.register_catalogue({"tools": [{"name": "get_order", "description": "Ask jo@acme.co.uk"}]})
except PersonalDataError as refused:
print(refused.findings[0].kind, refused.findings[0].path) # email document.tools[0].description
Uma ocorrência não envia nada. O erro nomeia o tipo de cada ocorrência e onde ela está, nunca o
seu valor. Um endereço em example.com, .example, .invalid ou .test é um exemplo, e não o
de uma pessoa.
Verificando uma chamada de ferramenta
check = chat.check_tool_call("send_payment", {"payee": "ACME", "amount": 120})
print(check.decision, check.reasons) # "allow", "confirm" or "refuse"
chat.ran(check, outcome="ran") # what became of the call, for the next check
Verificando um guardrail
result = client.verify_guardrail("policy.co", "execute_transfer_funds", "is_authenticated")
print(result.status) # "UNSAT", "SAT", "STALL", or None for a void verdict
if result.certificate:
result.certificate.save("certificate.json") # verified against its digest as it is written
Um veredito SAT não levanta nada: ele é a resposta à pergunta, com o reparo em
result.corrective_action e result.patched_policy.
Verificando um certificado você mesmo
Um certificado pode ser verificado sem chave, sem rede e sem nenhum software nosso.
- O verificador independente: um único arquivo, só com a biblioteca padrão, sob licença MIT:
https://api.stalwart.it/downloads/qls-check.py, com o seu digest emhttps://api.stalwart.it/downloads/qls-check.py.sha256. O formato que ele verifica está especificado emhttps://api.stalwart.it/certificate-spec/. - O mesmo arquivo, carregado neste pacote, por conveniência:
from stalwart_agents.client import check_certificate
outcome = check_certificate(certificate_path)
print(outcome.outcome, outcome.reason) # "verified", "not_self_contained" or "failed"
ou stalwart check certificate.json. stalwart check --print-source escreve o arquivo carregado,
para compará-lo com o publicado. Uma verificação estabelece que uma derivação é válida, nunca
que uma política é segura.
Erros
| Exceção | Quando | O que carrega |
|---|---|---|
LicenceRequired (de stalwart_agents.errors) |
sem chave de API, ou sem licença ativada nesta máquina | nomeia stalwart licence activate e onde obter uma chave |
ConfigurationError |
uma URL base inválida, um runtime que não está lá | |
SelectorUnavailableError |
um plano ou uma verificação de ferramenta sem runtime utilizável e sem router_url |
|
PersonalDataError |
dados pessoais em uma solicitação destinada aos servidores da Stalwart; nada foi enviado | .findings (tipo e caminho) |
AuthenticationError |
401 | |
NotEntitledError |
403 | .entitlement |
RefusedError |
409, uma combinação que não pode responder à pergunta | .slug, .explanation, .stage |
QuotaExceededError |
429 em um medidor mensal; nunca é repetido | .meter, .used, .limit, .resets_at |
ConcurrencyLimitError, RateLimitError |
429 que se resolve sozinho; levantado depois das novas tentativas | .current, .limit |
InputTooLargeError |
413 | .limit_bytes |
JobFailedError |
um job que terminou failed |
.cause, .stderr_tail |
DigestMismatch |
um download cujo hash não corresponde ao seu digest | |
StreamError |
um stream que não pode ser retomado | |
ServiceError |
5xx, depois das novas tentativas | .request_id |
Novas tentativas
Leituras, planos e verificações de ferramentas são repetidos em uma conexão interrompida, um
tempo esgotado, um 5xx e um 429 que se resolve sozinho (respeitando o Retry-After). Uma
resolução é enviada sob uma chave de idempotência que o cliente deriva, de modo que um envio
repetido é um único job e um segundo envio deliberado são dois. O registro de um catálogo, um
registro de feedback e uma tentativa sobre um achado são repetidos sob uma chave por chamada, de
modo que uma nova tentativa registra uma vez e uma segunda chamada registra de novo. Os efeitos
de um cancelamento nunca são repetidos.
O que o cliente não faz
Ele verifica a forma do que você envia -- uma chave de configuração que o serviço não tem, um
perfil que um modo recusa -- e nada mais
(o que o cliente não valida).
Se uma solicitação pode responder à sua pergunta é o serviço que diz, uma vez. Ele não carrega
código de seletor: um turno é lido pelo runtime local licenciado que o wheel do stalwart-agents
traz, ou pelo seu qls-router, nunca por este módulo.
Compatibilidade
| stalwart-agents | API |
|---|---|
| 1.x | /v1 |
Licença
© Stalwart Technologies Ltd. Proprietário; veja LICENSE. O verificador carregado em
stalwart_agents/client/_vendor/ é MIT, com o seu próprio LICENSE. Suporte: crm@stalwart.it.