СВЕТ НЕ СТОЈИ У МЕСТУ.RSS
BIG CHANGE.
BIG CHANGE, на један додир
BIG CHANGE, на један додир

Оставите простор за ширу слику.

Додајте BIG CHANGE на почетни екран или радну површину. Отвара се у сопственом прозору, са најновијим причама када сте на мрежи.

iPhone & iPad
Отворите bigchange.ai у прегледачу Safari. Додирните Share, затим Add to Home Screen. Укључите Open as Web App ако се прикаже, а затим додирните Add.
Android
Отворите bigchange.ai у прегледачу Chrome. У менију прегледача изаберите „Инсталирај апликацију“ или „Додај на почетни екран“.
Рачунар
У прегледачу Chrome или Edge користите икону за инсталацију у адресној траци или мени апликација прегледача. У прегледачу Safari на Mac рачунару изаберите File → Add to Dock.

Markdown издање

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

![Conceptual charcoal illustration of one person holding a larger blank ruled source page and a smaller blank candidate page, with an orange reading strip across the source.](https://bigchange.ai/api/media/file/source-candidate-human-review-hero-v2.png)
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 [водич за избор AI задатка из сопственог рада](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: Попишите свој рад и изаберите први задатак за пробу са AI](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) — Повезан контекстуални водич за избор задатка пре ове интеграције; не утврђује исправност овог поступка.