23 · Cálculos¶
Qual é o total médio dos pedidos 4411 e 4412? Nenhuma ferramenta da loja responde isso: get_order
devolve um pedido. O roteador lê o turno como duas leituras e um cálculo sobre os resultados delas, e o
cálculo roda na sua máquina, escrito pelo seu próprio modelo. Este capítulo é como, e o que cada parte pode
ver.
Idiomas
O seletor lê turnos escritos em inglês e em português do Brasil (pt-BR). O idioma é escolhido, nunca adivinhado a partir do turno: na página Settings do console (Language the selector reads), em language numa requisição de plano, ou no settings.language do seu arquivo de vocabulário; sem nenhum deles, inglês. Um turno em português pode ser digitado com os acentos ou sem eles. O português europeu não é uma gramática que o seletor tenha, e um turno em qualquer outra língua não é lido.
O plano¶
with Client() as client:
plan = client.plan("qual é o total médio dos pedidos 4411 e 4412", agent="desk", catalogue=catalogue)
print(plan.outcome, "| compute:", plan.compute)
for step in plan.steps:
what = (f"over steps {list(step.compute['sources'])}" if step.kind == "compute"
else f"{step.selection.tool} {dict(step.selection.arguments)}")
print(f" {step.index} {step.kind} | {step.decided_by} | {what}")
O plano lê cada pedido que o turno nomeia (each order_id) e depois uma etapa compute que depende das
duas leituras:
run | compute: run
0 tool | R2 source: get_order, each order_id | get_order {'order_id': '4411'}
1 tool | R2 source: get_order, each order_id | get_order {'order_id': '4412'}
2 compute | R1+R2: médio, total over get_order | over steps [0, 1]
succeeded 26.25 number
the prompt names the fields, never a value: True True
not_admitted | the function was not admitted: import: socket (line 1)
A etapa de cálculo leva o seu cálculo: a instrução nas palavras da pessoa, o tipo de resultado que ela deve
devolver -- number, number[], table ou text -- e as etapas cujos resultados ela lê (sources).
Rodando: client.compute¶
O seu código executa as etapas de leitura como executa qualquer ferramenta, e depois entrega a etapa e os
resultados delas a client.compute, com o seu modelo:
def write(prompt):
"""O seu modelo. Recebe o prompt e devolve o código da função; aqui, uma resposta que ele poderia dar."""
return ("```python\n"
"def compute(data):\n"
" totals = [float(order['total']) for order in data.values()]\n"
" return sum(totals) / len(totals)\n"
"```")
with Client() as client:
step = next(step for step in plan.steps if step.kind == "compute")
sources = [{"tool": "get_order", "result": get_order(plan.steps[i].selection.arguments["order_id"])}
for i in step.compute["sources"]]
done = client.compute(step, sources, model=write)
print(done.state, done.result, done.result_type)
print("the prompt names the fields, never a value:", "40.00" not in str(done.prompt), "total" in str(done.prompt))
Ele roda em quatro partes, todas na sua máquina:
- O prompt. O runtime licenciado o monta a partir do pacote de prompts que a sua licença traz: a instrução, o tipo de resultado e a forma das fontes -- os nomes dos seus campos e os seus tipos JSON, nunca um valor delas. A última linha da saída acima diz isso desta execução.
- A função. O seu modelo a escreve.
modelé qualquer função do prompt -- umComputePrompt, umastrcom a parte de sistema e depois a do usuário, com.system,.usere.schemapara um modelo que recebe papéis -- para o código: o SDK do seu provedor, o seu próprio endpoint, um modelo local. O pacotestalwart-agentsnão guarda nenhuma credencial de modelo e não chama nenhum provedor. - A admissão. O runtime lê a função antes que qualquer coisa a rode. Uma função que abre um socket, lê um arquivo ou importa qualquer coisa fora dos módulos do pacote é recusada, e nada roda.
- A execução. Um container novo da imagem do sandbox: sem rede, um sistema de arquivos só de leitura, nenhum arquivo e nenhuma variável da sua máquina. O resultado é verificado contra o tipo pedido, e o resultado verificado é o que você recebe.
client.compute devolve um Computation: state é succeeded (result tem o valor verificado), failed
(o sandbox a rodou, ou não conseguiu iniciar, e failure diz qual dos dois) ou not_admitted. result_type é
o tipo que o plano pediu -- aqui um number, a média.
Uma função recusada¶
def careless(prompt):
return "import socket\n\ndef compute(data):\n return socket.gethostname()\n"
with Client() as client:
refused = client.compute(step, sources, model=careless)
print(refused.state, "|", refused.failure["message"])
A última linha da saída: a admissão nomeia a regra e a linha, o estado é not_admitted, e nada rodou. Uma
função recusada não conta nada.
Onde um cálculo roda¶
Um plano só pede uma etapa de cálculo onde a sua máquina consegue rodar uma: um motor de containers responde,
a imagem do sandbox está nele e a sua licença o permite. Em qualquer outro lugar, o plano recusa o turno,
nomeando o cálculo (compute off: ...), e nenhuma etapa dele roda (capítulo
22). python -m stalwart_agents.sandbox baixa a imagem uma vez e imprime a
sua referência.
Nada de um cálculo sai da sua máquina: nem as fontes, nem o prompt, nem a função, nem o resultado. O que
sai são duas contagens no seu relatório de uso: compute_runs, uma execução que chegou ao sandbox, e
compute_seconds, o seu tempo de relógio arredondado para cima. A cota de um mês gasta levanta
QuotaExceededError antes que o seu modelo seja consultado.
Os limites¶
Os limites do capítulo 20 valem aqui, e mais estes:
- A função é do seu modelo. Um resultado verificado tem o tipo pedido; se ele é o cálculo que a pessoa quis dizer é trabalho do seu modelo, e nada aqui prova isso.
- A admissão recusa o que sai do sandbox. Ela não lê a função atrás de erros.