AI-translated from English; not yet reviewed by a fluent editor.
# Transforme a saída de um agente num registo de ações revisto
> Crie um resultado tipado com OpenAI Agents SDK a partir das suas notas, reveja as provas e o significado e passe o resultado aceite a um registo Markdown local com releitura e verificação de hashes.
By BIG CHANGE Editorial
Published: 2026-10-20T12:52:38.610Z
Updated: 2026-10-20T12:52:38.610Z
Canonical: https://bigchange.ai/blog/agents-sdk-reviewed-action-register-guide

AI-generated conceptual illustration by BIG CHANGE.
Um resultado de agente pode ter a estrutura JSON correta e interpretar mal a fonte. Este guia dá a um programador Python o percurso completo de um ficheiro de notas permitido a um registo local: resultado tipado do produtor, verificações da fonte e do significado, aprovação humana ligada aos hashes exatos e um consumidor que escreve e relê um relatório Markdown agrupado. Use notas próprias com compromissos explícitos. Não foram usadas notas reais ou resultados de modelos na preparação.
## A grande mudança
- **O que mudou:** O OpenAI Agents SDK aceita um `output_type` do Pydantic, pelo que uma aplicação pode pedir um resultado definido ao agente e verificá-lo antes de o usar. O exemplo acrescenta citações da fonte e um contrato com versão ao resultado.
- **Porque importa:** Um programador que liga um agente a outro passo pode travar uma saída malformada, incompleta ou sem fundamento antes de se tornar uma lista de ações. Um revisor continua a ter de avaliar se as notas citadas sustentam cada tarefa, responsável e data e se o modelo omitiu algo.
- **O que observar:** O ponto de decisão está na fronteira do consumidor. Esta receita produz um registo local de triagem apenas após a aprovação da execução, fonte e candidato exatos. Uma importação posterior num sistema de tarefas exigiria autoridade própria, política de repetição e releitura no destino.
## Prepare uma fonte autorizada
Precisa de Python 3.12, uma conta API OpenAI autorizada para as notas, um revisor que leia o original e um modelo cujo suporte atual a saída estruturada por esquema JSON tenha confirmado na conta. Verifique os termos de dados e preços atuais antes de executar o produtor. Não coloque credenciais nas notas ou no ficheiro de aprovação. A única chamada ao modelo abaixo é `produce`; o restante é processamento local.
Guarde as notas originais em UTF-8 em `notes.txt`. Devem conter compromissos de ação explícitos e reais do seu trabalho. Atribua um `source_id` estável, como o identificador do seu registo, e mantenha os bytes do ficheiro inalterados até à revisão e ao consumo. Crie `candidate/` antes de produzir; o ficheiro candidato deve ser novo. Disponibilize um diretório de saída separado para o relatório local posterior.
Registe o hash dos bytes da fonte antes da execução:
```sh
python -c "from pathlib import Path; import hashlib; print(hashlib.sha256(Path('notes.txt').read_bytes()).hexdigest())"
```
Num ambiente Python 3.12 isolado, crie `recipe-requirements.txt` com estas linhas exatas e instale:
```text
openai-agents==0.24.0
pydantic==2.14.1
```
```sh
python -m pip install -r recipe-requirements.txt
mkdir candidate
```
Estas versões foram instaladas em conjunto num ambiente local Python 3.12.7 em 20 de outubro de 2026. Confirme compatibilidade e disponibilidade do modelo quando executar. Guarde o script completo abaixo como `action_register_recipe.py` no mesmo diretório. Tem dois comandos: `produce` cria um candidato e recibo de execução; `consume` constrói o relatório apenas após validação e aprovação.
## Execute o produtor tipado
O contrato `Extraction` exige a versão `action-register/v1`, o estado `ready`, `partial` ou `blocked`, o ID da fonte e SHA-256 dos bytes exatos, ações e um `gap_note`. Cada ação exige texto, citação exata e intervalo de linhas da fonte contado a partir de 1, responsável e data que admitem null, e `complete` ou `needs_review` como estado da prova. Responsável ou data null significam que as notas não os indicam. O modelo é instruído a não inferir nenhum. Todos os campos são obrigatórios e os adicionais são rejeitados.
Escolha explicitamente o modelo de saída estruturada. Indique o seu caminho permitido e identificador:
```sh
python action_register_recipe.py produce --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --model YOUR-STRUCTURED-OUTPUT-MODEL
```
Leia `candidate/actions.json` ao lado de `candidate/actions.run.json`. O segundo regista o UUID da execução, hashes da fonte e candidato, agente final e modelo pedido. Uma falha cria em alternativa um registo de diagnóstico `.failure.json` com o ID de execução como sufixo. Uma interrupção, `None` ou saída final em texto simples, ou um agente final inesperado, nunca contam como sucesso. Este comando não preserva o estado retomável do SDK, `RunState`; o recibo de falha não retoma a execução. Examine pedido, candidato e estado posterior antes de decidir se uma nova execução é adequada.
## Reveja o significado e aprove o candidato exato
A aplicação verifica tipos estritos e campos obrigatórios, identidade da fonte, estado pronto, pelo menos uma ação completa, intervalos válidos, citação no trecho, tarefa ou responsável não vazios e datas canónicas `YYYY-MM-DD`. Isso prova a estrutura e a localização da citação. Não estabelece que a frase seja um compromisso real, que nome ou data tenham o significado afirmado ou que todos os compromissos tenham sido encontrados.
Leia as notas originais e cada ação proposta. Compare significado, responsável, data, citação e omissões. Resolva a ambiguidade através de uma nova execução revista; não aprove candidatos sem fundamento, parciais ou bloqueados. Após verificar que o ficheiro inalterado e o candidato correspondem a `actions.run.json`, escreva `approval.json` com exatamente estas sete chaves. Copie o ID e hashes do recibo verificado. Use a identificação real do revisor e uma descrição substancial do que foi efetivamente verificado:
```json
{
"approved": true,
"run_id": "COPY-RUN-ID",
"source_id": "OWNED-ID",
"source_sha256": "COPY-SOURCE-SHA256",
"payload_sha256": "COPY-PAYLOAD-SHA256",
"reviewer": "YOUR-REVIEWER-ID",
"review_note": "Describe the source, task, owner, date, quotation and omission checks actually performed."
}
```
`approved` tem de ser o booleano JSON `true`, não uma cadeia de texto nem número. Os restantes valores são cadeias de texto; revisor e nota não podem estar vazios. Campos adicionais falham. A aprovação liga um candidato e revisão da fonte, não qualquer reescrita futura.
## Consuma e releia o relatório
```sh
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --approval approval.json --out private-output
```
O consumidor agrupa ações aceites em **Assigned with dates**, **Needs an owner** e **Needs a date**. Cada entrada contém tarefa, estado de responsável e data, citação e linhas de origem. Escreve `action-register-<payload-hash-prefix>.md` e um ficheiro vizinho `.receipt.json` com hashes da execução, fonte, candidato e relatório. Abra ambos, compare as ações citadas com as notas e verifique `report_sha256` contra os bytes do Markdown. Um recibo válido só diz que o ficheiro local foi criado e relido; não aprova escrever num sistema externo de tarefas.
O comando de consumo imprime os caminhos reais do relatório e recibo. Use-os para esta verificação de bytes:
```sh
python -c "from pathlib import Path; import hashlib,json,sys; report=Path(sys.argv[1]); receipt=json.loads(Path(sys.argv[2]).read_text(encoding='utf-8')); assert hashlib.sha256(report.read_bytes()).hexdigest()==receipt['report_sha256']; print('report hash matches receipt')" PATH-PRINTED-FOR-REPORT PATH-PRINTED-FOR-RECEIPT
```
Com um candidato disponível, verifique a rejeição com um exemplo estrutural claramente sintético. Copie o JSON para outro ficheiro, retire qualquer campo obrigatório da primeira ação e invoque `consume` com esse ficheiro malformado e um diretório de saída novo e vazio. Tem de falhar antes de criar o ficheiro. Nunca considere o exemplo uma extração factual nem o aprove. Por exemplo, a verificação local abaixo retira `evidence_quote` de uma cópia:
```sh
python -c "import json; from pathlib import Path; p=json.loads(Path('candidate/actions.json').read_text(encoding='utf-8')); p['actions'][0].pop('evidence_quote'); Path('candidate/SYNTHETIC-malformed.json').write_text(json.dumps(p), encoding='utf-8')"
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/SYNTHETIC-malformed.json --approval approval.json --out synthetic-rejection-output
```
O segundo comando deve falhar e o diretório de saída continuar ausente. Não use o exemplo se o candidato real não tiver primeira ação; já é incompleto para este consumidor. Numa escrita incerta, examine relatório determinístico e recibo antes de repetir. Bytes existentes idênticos podem ser reutilizados; divergentes interrompem para conciliação. Uma fonte ou versão de esquema alterada exige nova revisão. Nunca repita uma chamada ao modelo apenas porque a resposta do consumidor local foi pouco clara.
## Receita Python completa
Copie este script exatamente para `action_register_recipe.py`:
```python
"""Private, bounded recipe. No model call occurs unless `produce` is explicitly run."""
import argparse
import hashlib
import json
import uuid
from datetime import date
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, ValidationError
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True)
class Action(StrictModel):
action_text: StrictStr
evidence_quote: StrictStr
line_start: StrictInt
line_end: StrictInt
owner: StrictStr | None
due_date: StrictStr | None
evidence_status: Literal["complete", "needs_review"]
class Extraction(StrictModel):
schema_version: Literal["action-register/v1"]
status: Literal["ready", "partial", "blocked"]
source_id: StrictStr
source_sha256: StrictStr
actions: list[Action]
gap_note: StrictStr | None
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def canonical(payload: Extraction) -> bytes:
return json.dumps(payload.model_dump(mode="json"), sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
def check(payload: Extraction, source_id: str, source: bytes) -> None:
if payload.source_id != source_id or payload.source_sha256 != digest(source):
raise ValueError("source identity or revision mismatch")
lines = source.decode("utf-8").splitlines()
if payload.status == "ready" and (not payload.actions or payload.gap_note is not None):
raise ValueError("ready requires actions and no gap")
if payload.status != "ready":
raise ValueError("partial or blocked result cannot reach consumer")
for index, item in enumerate(payload.actions, 1):
if item.evidence_status != "complete":
raise ValueError(f"action {index}: evidence needs review")
if not item.action_text.strip() or not item.evidence_quote.strip():
raise ValueError(f"action {index}: empty task or evidence")
if not 1 <= item.line_start <= item.line_end <= len(lines):
raise ValueError(f"action {index}: invalid source line range")
segment = "\n".join(lines[item.line_start - 1:item.line_end])
if item.evidence_quote not in segment:
raise ValueError(f"action {index}: quotation absent from source span")
if item.owner is not None and not item.owner.strip():
raise ValueError(f"action {index}: blank owner")
if item.due_date is not None:
try:
if date.fromisoformat(item.due_date).isoformat() != item.due_date:
raise ValueError()
except ValueError as exc:
raise ValueError(f"action {index}: due date must be YYYY-MM-DD") from exc
def report(payload: Extraction) -> str:
"""Application consumer: a triage register grouped by missing ownership/date."""
groups = [
("Assigned with dates", lambda x: x.owner is not None and x.due_date is not None),
("Needs an owner", lambda x: x.owner is None),
("Needs a date", lambda x: x.owner is not None and x.due_date is None),
]
rows = ["# Action register for " + payload.source_id, "", "Review artifact; source meaning was approved separately.", ""]
for heading, predicate in groups:
rows += ["## " + heading, ""]
matches = [(n, a) for n, a in enumerate(payload.actions, 1) if predicate(a)]
for number, action in matches:
rows += [f"{number}. {action.action_text}", f" Owner: {action.owner or 'Unspecified'}; due: {action.due_date or 'Unspecified'}", f" Source lines {action.line_start}-{action.line_end}: {action.evidence_quote}", ""]
if not matches:
rows += ["None.", ""]
return "\n".join(rows)
def load_payload(path: Path) -> Extraction:
return Extraction.model_validate_json(path.read_bytes(), strict=True)
def consume(source_id: str, source_path: Path, payload_path: Path, approval_path: Path, out_dir: Path) -> tuple[Path, Path]:
source = source_path.read_bytes()
payload = load_payload(payload_path)
check(payload, source_id, source)
run = json.loads(payload_path.with_suffix(".run.json").read_text(encoding="utf-8"))
payload_hash = digest(canonical(payload))
if run.get("source_id") != source_id or run.get("source_sha256") != digest(source) or run.get("payload_sha256") != payload_hash or run.get("last_agent") != "Owned-source action extractor" or not str(run.get("run_id", "")).strip():
raise ValueError("producer receipt does not match accepted candidate")
approval = json.loads(approval_path.read_text(encoding="utf-8"))
if (type(approval) is not dict
or set(approval) != {"approved", "run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"}
or approval["approved"] is not True
or any(type(approval[key]) is not str for key in ("run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"))
or not approval["reviewer"].strip() or not approval["review_note"].strip()
or approval["run_id"] != run["run_id"]
or approval["source_id"] != source_id
or approval["source_sha256"] != digest(source)
or approval["payload_sha256"] != payload_hash):
raise ValueError("human source/meaning approval must bind exact source and payload hashes")
body = report(payload).encode("utf-8")
stem = "action-register-" + payload_hash[:16]
out_dir.mkdir(parents=True, exist_ok=True)
target = out_dir / (stem + ".md")
receipt = out_dir / (stem + ".receipt.json")
record = {"run_id": run["run_id"], "source_id": source_id, "source_sha256": digest(source), "payload_sha256": payload_hash, "report_sha256": digest(body), "reviewer": approval["reviewer"], "operation": "local action-triage register"}
encoded = (json.dumps(record, indent=2) + "\n").encode("utf-8")
for path, data in [(target, body), (receipt, encoded)]:
if path.exists():
if path.read_bytes() != data:
raise ValueError(f"uncertain prior output differs: {path}; inspect before retry")
else:
with path.open("xb") as handle:
handle.write(data)
if path.read_bytes() != data:
raise IOError(f"readback failed: {path}")
return target, receipt
def produce(source_id: str, source_path: Path, candidate_path: Path, model: str) -> None:
from agents import Agent, Runner
source = source_path.read_bytes()
source_text = source.decode("utf-8")
if not source_text.strip() or candidate_path.exists() or not candidate_path.parent.is_dir():
raise ValueError("source must contain text; candidate must be new and its parent directory must already exist")
run_id = str(uuid.uuid4())
agent = Agent(
name="Owned-source action extractor",
model=model,
instructions=("Extract only explicit action commitments from the supplied numbered source. "
"Return action-register/v1. Copy an exact quote with matching 1-based line span. "
"Use null for an unstated owner or due date; never infer them. "
"Use partial and needs_review for ambiguous evidence; blocked for unusable input. "
"Do not use tools or hand off."),
output_type=Extraction,
)
numbered = "\n".join(f"{n}: {line}" for n, line in enumerate(source_text.splitlines(), 1))
try:
result = Runner.run_sync(agent, f"Source ID: {source_id}\nSHA-256: {digest(source)}\nNumbered source:\n{numbered}")
if result.interruptions or result.last_agent is not agent or not isinstance(result.final_output, Extraction):
raise ValueError("run interrupted, unfinished, or finished by unexpected agent; this CLI does not persist resumable RunState, so do not resume from its failure receipt")
output = result.final_output
if output.source_id != source_id or output.source_sha256 != digest(source):
raise ValueError("producer returned wrong source identity")
candidate = canonical(output)
with candidate_path.open("xb") as handle:
handle.write(candidate) # Candidate only; consumer still requires review.
receipt = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "payload_sha256": digest(candidate), "last_agent": result.last_agent.name, "model": model}
with candidate_path.with_suffix(".run.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(receipt, indent=2) + "\n")
except Exception as exc:
failure = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "error_type": type(exc).__name__, "error": str(exc), "state": "unknown_or_blocked; inspect run and destination before retry"}
with candidate_path.with_name(candidate_path.stem + "." + run_id + ".failure.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(failure, indent=2) + "\n")
raise
def main() -> None:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
for name in ("produce", "consume"):
p = sub.add_parser(name)
p.add_argument("--source-id", required=True)
p.add_argument("--source", type=Path, required=True)
if name == "produce":
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--model", required=True)
else:
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--approval", type=Path, required=True)
p.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
if args.command == "produce":
produce(args.source_id, args.source, args.candidate, args.model)
else:
print(*consume(args.source_id, args.source, args.candidate, args.approval, args.out), sep="\n")
if __name__ == "__main__":
main()
```
## Limites e fontes
O contrato e consumidor local foram verificados offline com cinco testes e sete casos sintéticos de produtor simulado ou repetição. Abrangeram esquema estrito, rejeição antes de criar ficheiros, agrupamento, hashes e falhas. Não houve envio de esquema a fornecedor, chamada a modelo, extração de notas reais, aprovação humana factual, relatório com fonte real ou medição de fatura ou qualidade. A API tem encargos dependentes da conta; verifique compatibilidade, preços e permissão sobre dados antes de enviar a fonte.
Para contexto relacionado, o [guia da BIG CHANGE para escolher uma tarefa de IA no próprio trabalho](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) cobre a decisão anterior sobre o que testar. Este guia começa com uma extração selecionada e permitida e leva um resultado tipado a um consumidor local verificado.
### Fontes e leituras adicionais
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) documenta o `output_type` do Pydantic. Não fornece a aprovação humana nem o consumidor local desta receita.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) documenta `final_output`, `last_agent`, interrupções e `to_state()`. Esta interface de linha de comandos não guarda estado retomável.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) cobre instalação e configuração da chave API. Mantenha a chave fora da fonte e dos recibos.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) explica regras de esquema estrito e modelos suportados. Confirme o suporte do modelo escolhido.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) documenta validação estrita e proibição de campos adicionais.
- [OpenAI Agents SDK 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) e [Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) identificam as versões fixadas instaladas localmente. A aceitação do fornecedor não foi testada.
## Sources
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) — Manual oficial de output_type Pydantic. Não implementa a aprovação humana nem o consumidor local do artigo.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) — Comportamento oficial de final_output, last_agent, interrupções e to_state. A receita não preserva RunState nem oferece retoma.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) — Contexto oficial de instalação e chave API; não foi usada chave nem chamada real ao fornecedor na preparação.
- [OpenAI Agents SDK: referência do esquema de saída](https://openai.github.io/openai-agents-python/ref/agent_output/) — Referência oficial de geração estrita de esquema; a inspeção local não prova aceitação pelo fornecedor.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) — Regras estritas de campos obrigatórios e additionalProperties, e compatibilidade; o leitor tem de verificar o modelo escolhido no momento da utilização.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) — Documenta strict e extra-forbid; exatidão semântica continua a exigir revisão humana da fonte.
- [Versão openai-agents 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) — Versão exata do SDK instalada na verificação offline isolada Python 3.12.7; sem chamada ao modelo ou fatura.
- [Versão Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) — Versão exata da biblioteca de validação instalada localmente com SDK 0.24.0.
- [BIG CHANGE: Mapeie o seu trabalho e escolha uma primeira tarefa para testar com IA](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) — Guia contextual para escolher uma tarefa antes desta integração; não estabelece a correção da receita.
Newsletter BIG CHANGE
A perspetiva geral, ao seu ritmo.
Histórias recentes sobre IA e robótica, mudanças que vale a pena acompanhar e ideias práticas para utilizar. Escolha um briefing diário, um resumo semanal ou uma perspetiva mensal.
Próximo envio agendado (UTC): . A sua primeira edição chegará no próximo envio agendado após a confirmação.
A sua privacidade, a sua escolha.
O armazenamento necessário ajuda a proteger o site e a memorizar as suas escolhas. O Google Analytics opcional permanece desativado até que o autorize. Pode ler todos os artigos utilizando apenas o armazenamento necessário. Detalhes de privacidade