AI-translated from English; not yet reviewed by a fluent editor.
# Превратите вывод агента в проверенный реестр действий
> Создайте типизированный результат OpenAI Agents SDK из своих заметок, проверьте источники и смысл, затем передайте принятый результат в локальный Markdown-реестр действий с обратной проверкой хешей.
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.
Результат агента может иметь верную структуру JSON и всё же неверно прочитать источник. Этот материал даёт Python-разработчику полный путь от одного разрешённого файла заметок до локального реестра: типизированный результат производителя, проверка источника и смысла, человеческое одобрение точных хешей и потребитель, записывающий и перечитывающий сгруппированный Markdown-отчёт. Используйте собственные заметки с явными обязательствами. При подготовке не использовались реальные заметки или результат модели.
## Главное изменение
- **Что изменилось:** OpenAI Agents SDK принимает Pydantic `output_type`, поэтому приложение может запросить определённый результат и проверить его до использования. Пример добавляет цитаты источника и версионируемый контракт результата.
- **Почему это важно:** Разработчик, соединяющий агента со следующим шагом, может остановить неверно оформленный, неполный или необоснованный вывод до превращения в список действий. Человек всё ещё должен оценить, подтверждают ли цитаты каждую задачу, ответственного и дату и нет ли пропусков.
- **На что обратить внимание:** Точка решения — граница потребителя. Рецепт создаёт только локальный реестр для разбора после одобрения точного запуска, источника и кандидата. Поздний импорт в систему задач потребует собственных полномочий, правил повторения и проверки чтением в месте назначения.
## Подготовьте один разрешённый источник
Нужны Python 3.12, аккаунт OpenAI API с разрешением на эти заметки, проверяющий с доступом к оригиналу и модель, текущую поддержку структурированного вывода JSON-schema которой вы подтвердили в аккаунте. До запуска проверьте условия данных и актуальные цены. Не помещайте учётные данные в заметки или одобрение. Единственный вызов модели ниже — `produce`; остальная обработка локальная.
Сохраните оригинальные заметки в UTF-8 в `notes.txt`. Они должны содержать реальные явные обязательства из вашей работы. Назначьте стабильный `source_id`, например собственный идентификатор записи, и не меняйте ни байта до проверки и обработки. Создайте `candidate/` до генерации; файл кандидата должен быть новым. Подготовьте отдельный выходной каталог для локального отчёта.
До запуска запишите хеш байтов источника:
```sh
python -c "from pathlib import Path; import hashlib; print(hashlib.sha256(Path('notes.txt').read_bytes()).hexdigest())"
```
В изолированной среде Python 3.12 создайте `recipe-requirements.txt` с этими точными строками и установите пакеты:
```text
openai-agents==0.24.0
pydantic==2.14.1
```
```sh
python -m pip install -r recipe-requirements.txt
mkdir candidate
```
Эти версии установились вместе в локальной среде Python 3.12.7 20 октября 2026 года. Проверьте совместимость и доступность модели при использовании. Сохраните полный скрипт ниже как `action_register_recipe.py` в том же каталоге. У него две команды: `produce` создаёт кандидата и квитанцию запуска; `consume` формирует отчёт лишь после валидации и одобрения.
## Запустите типизированного производителя
Контракт `Extraction` требует версию `action-register/v1`, статус `ready`, `partial` или `blocked`, ID источника и SHA-256 точных байтов файла, действия и `gap_note`. Для каждого действия нужны текст, точная цитата и диапазон строк источника с отсчётом от 1, ответственный и дата, допускающие null, а также `complete` или `needs_review` как статус доказательства. Null у ответственного или даты означает отсутствие этих сведений в заметках. Модели запрещено их выводить самостоятельно. Все поля обязательны, дополнительные отвергаются.
Явно выберите модель структурированного вывода. Укажите собственные разрешённые путь и идентификатор:
```sh
python action_register_recipe.py produce --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --model YOUR-STRUCTURED-OUTPUT-MODEL
```
Прочтите `candidate/actions.json` рядом с `candidate/actions.run.json`. Второй файл фиксирует UUID запуска, хеши источника и кандидата, завершившего агента и запрошенную модель. При сбое вместо этого создаётся диагностическая запись `.failure.json` с ID запуска в суффиксе. Прерывание, `None` или итоговая простая строка, либо неожиданный завершающий агент никогда не считаются успехом. Команда не сохраняет возобновляемое состояние SDK `RunState`; квитанция ошибки не позволяет продолжить запуск. Изучите запрос, кандидата и последующее состояние, прежде чем решать о новом запуске.
## Проверьте смысл и одобрите точного кандидата
Приложение проверяет строгие типы и обязательные поля, идентичность источника, готовый статус, хотя бы одно полное действие, допустимые диапазоны строк, наличие цитаты в диапазоне, непустые задачу или ответственного и канонические даты `YYYY-MM-DD`. Это доказывает структуру и расположение цитаты, но не то, что фраза является обязательством, имя или дата имеют заявленный смысл либо все обязательства найдены.
Прочитайте исходные заметки и каждое предложенное действие. Сравните смысл, ответственного, дату, цитату и пропуски. Устраняйте неоднозначность новым проверенным запуском; необоснованные, частичные и заблокированные кандидаты не одобряйте. Убедившись, что неизменённый файл и кандидат соответствуют `actions.run.json`, запишите `approval.json` ровно с этими семью ключами. Скопируйте ID и хеши из проверенной квитанции. Укажите реальный идентификатор проверяющего и содержательное описание фактически сделанных проверок:
```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` должен быть логическим значением JSON `true`, а не строкой или числом. Остальные значения — строки; проверяющий и примечание не могут быть пустыми. Дополнительные поля приводят к отказу. Одобрение связывает одного кандидата с ревизией источника, а не с будущей переработкой.
## Обработайте и перечитайте отчёт
```sh
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --approval approval.json --out private-output
```
Потребитель группирует принятые действия в **Assigned with dates**, **Needs an owner** и **Needs a date**. Каждая запись содержит задачу, состояние ответственного и даты, цитату и строки источника. Он пишет `action-register-<payload-hash-prefix>.md` и соседний файл `.receipt.json` с хешами запуска, источника, кандидата и отчёта. Откройте оба, сопоставьте процитированные действия с заметками и проверьте `report_sha256` по байтам Markdown-файла. Верная квитанция означает только создание и обратное чтение локального файла; она не одобряет запись во внешнюю систему задач.
Команда обработки выводит реальные пути отчёта и квитанции. Используйте их для проверки байтов:
```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
```
Получив кандидата, проверьте отказ явно синтетическим структурным образцом. Скопируйте JSON в отдельный файл, удалите любое обязательное поле первого действия и вызовите `consume` с неверным файлом и новым пустым выходным каталогом. Сбой должен произойти до создания результата. Не считайте образец фактическим извлечением и не одобряйте. Например, следующая локальная проверка удаляет `evidence_quote` из копии:
```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
```
Вторая команда должна завершиться ошибкой, а выходной каталог остаться отсутствующим. Не используйте образец без первого действия в реальном кандидате: он уже неполон для потребителя. При неопределённой записи сначала проверьте детерминированный отчёт и квитанцию. Существующие идентичные байты можно использовать повторно; другие останавливают процесс для сверки. Изменение источника или версии схемы требует новой проверки. Не повторяйте вызов модели лишь из-за неясного ответа локального потребителя.
## Полный рецепт Python
Скопируйте этот скрипт без изменений в `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()
```
## Ограничения и источники
Контракт и локальный потребитель проверены офлайн пятью тестами и семью синтетическими сценариями имитации производителя или повтора. Проверялись строгая генерация схемы, отказ до создания файла, группировка, хеши и обработка ошибок. Не было отправки схемы поставщику, вызова модели, извлечения реальных заметок, фактического человеческого одобрения, отчёта из реального источника или измерения счёта и качества. Плата API зависит от аккаунта; перед отправкой проверьте совместимость, цены и разрешение на данные.
Для смежного контекста [руководство BIG CHANGE по выбору ИИ-задачи из собственной работы](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) рассматривает более раннее решение, что проверять. Этот материал начинается с выбранного разрешённого извлечения и передаёт один типизированный результат проверенному локальному потребителю.
### Источники и дополнительные материалы
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) описывает Pydantic `output_type`. В нём нет человеческого одобрения или локального потребителя этого рецепта.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) описывает `final_output`, `last_agent`, прерывания и `to_state()`. Этот CLI не сохраняет возобновляемое состояние.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) рассматривает установку и ключ API. Храните ключ вне источника и квитанций.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) объясняет строгую схему и поддержку моделей. Подтвердите поддержку выбранной модели.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) описывает строгую валидацию и запрет дополнительных полей.
- [OpenAI Agents SDK 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) и [Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) обозначают точные локально установленные версии. Принятие схемы поставщиком не проверялось.
## Sources
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) — Официальное руководство Pydantic output_type. Не реализует человеческое одобрение или локального потребителя статьи.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) — Официальное поведение final_output, last_agent, прерываний и to_state. Рецепт не сохраняет RunState и не предлагает возобновление.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) — Официальный контекст установки и ключа API; при подготовке не использовались ключ или реальный вызов поставщика.
- [OpenAI Agents SDK: справочник выходной схемы](https://openai.github.io/openai-agents-python/ref/agent_output/) — Официальная генерация строгой схемы; локальная проверка не доказывает принятие поставщиком.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) — Строгие правила обязательных полей и additionalProperties и совместимость моделей; читатель должен проверить свою модель при использовании.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) — Описывает strict и extra-forbid; семантическая точность остаётся человеческой проверкой источника.
- [Релиз openai-agents 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) — Точная версия SDK установлена в изолированной локальной Python 3.12.7 офлайн-проверке; без вызова модели или счёта.
- [Релиз Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) — Точная версия библиотеки валидации установлена локально вместе с SDK 0.24.0.
- [BIG CHANGE: Опишите свою работу и выберите первую задачу для проверки с ИИ](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) — Смежное руководство для выбора задачи до этого интеграционного рецепта; не доказывает его правильность.
Рассылка BIG CHANGE
Общая картина. В вашем темпе.
Свежие материалы об ИИ и робототехнике, важные перемены и практические идеи. Выберите ежедневную сводку, еженедельный дайджест или ежемесячный обзор.
Следующая запланированная отправка (UTC): . Первый выпуск придёт при следующей запланированной отправке после подтверждения подписки.
Ваша конфиденциальность — ваш выбор.
Необходимое хранилище помогает защитить сайт и запомнить ваши настройки. Необязательная Google Analytics отключена, пока вы её не разрешите. Все статьи доступны для чтения только с необходимым хранилищем. Подробнее о конфиденциальности