Галоўная / Артыкулы

Інжыніерыя ціклаў: чатыры вкладаныя ціклы для самапрагрэсуючых агентаў AI

Інжынерыя ціклаў для самапраўляючыхся агентаў AI: чатыры вкладзеныя ціклы (ReAct, перапрыбуткі, паллер кялі, метод падыходу да вершыны) плюс суддзя, метраж бюджэту і перакананне ў недаступнасці маніпуляцый.

Выдавец: REACTAPP.TOP32 хв чытання7374 слоў

Крыніцы: Loop Engineering: Building Self-Improving AI Agents with Four Nested Loops

Прыблызнае два гады праця з модэлю значыла чэргаванне. Інжынер пішае запіт, чытае адпаведны адказ і ручная уводзіць наступны фрагмент кантэксту. Шырокая практыка мае скрытыя затраты, сваерады «падатак на організацыю», таму што скорасць усяго працэсу абмежваецца тым, насколькі быстра можа пераглядаць інформацыю людзін. Ёй крануцца змяны акцэнса: трэба перастаць кераваць інструментам і пачаць праектаваць сістэму, якая яго керуе.

Тры ідэі апісваюць гэтыя змяны.

  • Вплыв пераходзіць на вышэйшы рэвэл. Роль пераходзіць з прадачы точных інструкцый на праектаванне цыклаў, якія павтараюцца, а пасля — на роль каго-то на кшталт керуючага. Чалавек задае мету і умову завершэння, а агент керуе кожным крокам на шляху межы ўсімі гэтымі элементамі.
  • Аўтонамныя цыклы маюць відзначальную структуру. Яны знаходзяць задачы, перадаюць ўздоўжнікі пад-агентам, пераканальваюць рэзультаты за дапамогою незалежных тэстаў і фіксуюць свой статус, што выводзіць чалавека за межы цыклу сортавання, вылечэння і пераканальвання.
  • Інжынерыя цэлей знаходзіцца на верхнім роўні. Хутчэй тут відступаюць внутрашні механізмы цыклу. AlphaEvolve, які парадураваў рэкорды па множэнню матрыц, якія існавалі з 1969 года, і Kiro ад Amazon, які ператварае высокія цялі на трэбаванні і спецыфікацыі, паказваюць гэты патэрн: калі правільна задаць функцію оцэнкі, машына знаходзіць адпаведны адпаведнік.

Ёнклававанню ціклу абоўкол агента патрэбны чысты ментальны модэль таго агента. Таму логічны парадак стварэння пачынаецца з простага працоўніка, які выкананае заданні, далей дадаецца другі модэль, які оцэнюе яго рэзультат (чым сістэма перастае оцэнюваць сябе саму), два модэля з’яеднаюцца ў цікл, а рэзультаты зберагаюцца на дыску, адтуды што модэлі забываюць, а хранальнік — ні. Пасля чаго прыходзяць захопы проты таго, калі агент маніпулюе метрыкамі, тыпу шахрайства, якое было зафіксавана пад час тэставання o3 ад OpenAI або METR, далей — метрыка, якая сочыць за віткамі ў срэдавішчы, якое можа быць багатае на токены або беднае на яны, і нарэшце — пераход з 70-радковага скрыпта да чагосьці, што можа працаваць у рэальных умовах.

Далей прыводзіцца рабочая рэалізацыя всій гэтай ідэі, якая выкорыстоўвае працэс рээстрацыі страхавання як тэставальную платформу.

Чаму гэта ўзгляд на архітэктуру

Пераход ад модэлю, які адпавядае на запытанні, да агента, які надзеяздна выкананае дзеянні, — гэта не проста задача напісання запытання. Цэта проблема дыяграматыкі. Большасць навучальных матэрыялаў паказвае агента, які выкорыстоўвае адну засобу. Рэальныя сістэмы патрабуюць стойкасці да збоёў, самакорэкціі, лімітаў выдаткаў, выяўлення пахісніцтва і стабільнага праграсу, усьго цагом.

Падход, які тут выкарыстоўваецца, называецца інжынерыяю ціклам: ствараюцца калькі кантрольных ціклам, кожны з якіх мае адзінчы завадзь, так што весь сістэма будзе працаваць лепшая, чым будзе можаць кожны з ціклам окалічна.

Сфера страхавання ёсць падходжым прыкладам. Рашэнні прыймаюцца з малага фіксаванага набора (прыйняць, змяніць, адмовіць, перадаць), правілы ёсць ясныя, а історычныя даны з’яўляюцься правыя і неправыя адпаведзі. Тыя ж прынцыпы можна застосаваць да перагляду коду, аналізу дакументаў, сортавання запытанняў падтрымкі і апрацоўкі медычных дакументаў.

Чатыры цікламы, адны ўнутры іншага

Звычныя програмы караюць складнасць за дапамою шароў абстракцыі. Інжынерыя цыклаў караюць ненадзеямасць агента за дапамою шароў зворотнага звязку:

┌──────────────────────────────────────────────────────┐
│  Loop 4 — Hill Climbing (self-improvement over time) │
│  ┌────────────────────────────────────────────────┐  │
│  │  Loop 3 — Event Queue Poller (orchestration)   │  │
│  │  ┌──────────────────────────────────────────┐  │  │
│  │  │  Loop 2 — Verification & Retry           │  │  │
│  │  │  ┌────────────────────────────────────┐  │  │  │
│  │  │  │  Loop 1 — ReAct Agent              │  │  │  │
│  │  │  │  (think → act → observe → think)   │  │  │  │
│  │  │  └────────────────────────────────────┘  │  │  │
│  │  └──────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────┘

Кожны цыкл мае адну задачу, адну проблему, протыякоры якой ён дзеяе, і адны сігнал зворотнай свярзкі, на які ён рэагуе. Важлівая дысцыпліна — гэта не дазволіць проблемам прасачыцца з аднаго цыклу ў іншы.

Домэн

Перш чым говорыць пра архітэктуру, апісуемы даны. Заява на страхаванне — гэта простая запісь:

{
  "id": "APP-001",
  "applicant_age": 34,
  "state": "FL",
  "property_type": "residential",
  "coverage_amount": 450000,
  "claim_history_5yr": 1,
  "credit_score_tier": "good",
  "flood_zone": true,
  "has_flood_rider": false,
  "business_use": false
}

Кожна з 20 прыёмных аплікацыяў таксама мае пазнаку правільнага рашэння:

"APP-001": {
  "split": "train",
  "expected_decision": "modify",
  "expected_flags": ["flood_rider_required", "windstorm_exclusion"]
}

Рашынак — адна з чатыро можлівасцей:

  • прыняць: стандартны рызык, затвердзіць як ёсць
  • змяніць: затвердзіць за певнымі умовамі
  • адзінак: рызык перасілае допустны ліміт
  • перадаць: неабяжна дапамога ад вышэйшага спеціяліста

Малая база знання мае пяць секрэтных правіл страхавання, якія стосуюцца зон паводка, історыі заявок, крэдытнага рэйтынгу, верхніх меж страховага пакрыцця і спецыфічных для штата обмежэнняў. Агент не павінен быць інформаваны пра іх у інструкціях; ёму трэба самому іх знайсці і застосавіць за дапамогою спецыяльных інструментаў. Шысцьце сем аплікацыяў выкорыстоўваюцца для навчання, а чатыры залішаюцца для тэставання.

Цікл 1: агент ReAct

Захід працюе проты: модэлі, якая выдумвае рашэнні без доказаў. Сігнал зворачнай свярзкі: граф вызваў інструментаў, які паказвае, чы рэагент пераглядаў усе значныя даныя пры выданні рашэнні.

ReAct, як скрацанне для «reason plus act», ёсць базовы патэрн. Рэагент размышляе пра тым, што йому трэба, вызывае інструмент, чытае рэзультат, зноў размышляе і павтарае процэс, пакуль не вынесе рашэнні:

System Prompt
     │
     ▼
[Think] → What information do I need?
     │
     ▼
[Act]   → Call tool (lookup_application, retrieve_rules, ...)
     │
     ▼
[Observe] → Tool returns result
     │
     ▼
[Think] → What does this mean? What next?
     │
    ... (repeat until decision is made)
     │
     ▼
[Act]   → draft_decision (terminal tool call)

У LangGraph гэта стае графам станоў з двума вузламі і умовным рэшэткам. Стан несе ID прыемлівача, спіс паведамленняў, поля для прыняцьбы рашэнняў, урокі, актуальны расход і лічылку крокаў:

class UnderwritingState(TypedDict):
    application_id: str
    messages: Annotated[list[AnyMessage], add_messages]  # reducer accumulates
    decision: str
    rationale: str
    flags: list[str]
    recommended_premium_adj: float
    lessons: list[str]
    spend: float
    _steps: int

Адзін дакледны асаблівання — анотацыя add_messages змушвае LangGraph дадаць новыя прыемы ў спіс пры кожным кроку замест таго, каб заменіць іх, таму агент захоўвае весь дыялог праз кожны вызов інструмента для адной аплікацыі.

Конструктор берае сістэмны прапан і спяльны метраж віткоў і прыяўляе інструменты да модэлю:

def build_loop1(system_prompt: str, meter: CostMeter):
    model = ChatAnthropic(model="claude-sonnet-4-6").bind_tools(TOOLS)

Унутры яго знаходзяцца вузел агента, вузел інструментаў, функцыя маршрутацыі і складанне графа:

    def agent_node(state: UnderwritingState) -> dict:
        prompt = system_prompt
        if state.get("lessons"):
            prompt += "\n\nUnderwriting lessons:\n" + "\n".join(
                f"- {l}" for l in state["lessons"]
            )
        msgs = [SystemMessage(content=prompt)] + list(state["messages"])
        response = model.invoke(msgs)
        meter.record_from_message(response)          # track spend per call
        return {
            "messages": [response],
            "_steps": state.get("_steps", 0) + 1,
            "spend": meter.spent,
        }

    def tools_node(state: UnderwritingState) -> dict:
        last = state["messages"][-1]
        tool_results, updates = _dispatch_tools(last.tool_calls)
        return {"messages": tool_results, **updates}  # updates captures draft_decision output

    def should_continue(state) -> Literal["tools", "__end__"]:
        if state.get("_steps", 0) >= MAX_STEPS:   # hard cap at 8 steps
            return END
        if getattr(state["messages"][-1], "tool_calls", None):
            return "tools"
        return END

    g = StateGraph(UnderwritingState)
    g.add_node("agent", agent_node)
    g.add_node("tools", tools_node)
    g.set_entry_point("agent")
    g.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
    g.add_edge("tools", "agent")
    return g.compile()

Чатыры інструменты

Агент отрымае роўна чатыры інструменты, якіх якраз дастаткова, каб падтрымаць усе, што ў яго ёсць:

@tool
def lookup_application(app_id: str) -> dict:
    """Return the full application record for the given application ID."""
    # reads from applications.json — deterministic, no LLM call
    ...

@tool
def retrieve_rules(risk_factor: str) -> list[str]:
    """Return underwriting rules matching the given risk factor keyword.
    Use terms like: flood, claims, credit, coverage, state, business."""
    # keyword match against rules_kb.json - forces explicit rule lookup
    ...

@tool
def fetch_risk_signal(signal_type: str) -> dict:
    # simulated external data pull (credit bureaus, flood maps, etc.)
    ...

@tool
def draft_decision(
    decision: Literal["accept", "modify", "decline", "refer"],
    rationale: str,
    flags: list[str],
    recommended_premium_adj: float,
) -> dict:
    """Finalize the underwriting decision with structured output."""
    # terminal action - structured schema forces the agent to commit explicitly
    ...

lookup_application чытае толькі даныя. retrieve_rules адмашоўвае за ключоўым словам у фіксаванай сэтцы правілаў, што вымагае явнага пошуку. fetch_risk_signal імітуюць запрашанне званярных даных, такіх як інформацыя з бюро крэдытнаў чы карты паводзяў. draft_decision — гэта завершальная дзеянне, якае мае найбольша значыць. Яна змушвае агента прыняць адпаведную рашынку з абгрунтаваннем і конкрэтнымі пазнакамі, тады структураваны рэзультат выходзіць непасульком з самага вызову інструмента, і нікому не трэба парсаваць вольны тэкст.

Чаму важна межа колькасці крокаў

MAX_STEPS = 8 спяшае безканецныя ланцюгі інструментаў. Зблукалы модэль можа двацяць разоў вызваць пошук правілаў для слова „паводзь“, шукаючы адпаведны адказ, якога няма, і тым самым выгараць токены і час. Калі колькасць крокаў досягае межы, стан атрымвае рашынку ran_out_of_steps, яку цікл 2 можа распазнаць і прабаваць знову.

Цікл 2: перакантовка і практыка знову

Західна мета: запобегчанне адзінам некорэктным адказам, які вынікаюць з няправільнага логічнага падходу. Сігнал адзначэння: дэтэрміністычны функцыя is_correct(), які не выкарыстоўвае жадных модэляў.

Цікл 1 можа зазнаць няудачы. Агент можа неправа прачытаць пазнаку, праігнораваць правілу або дасягнуць максімальнага ліміту крокаў. Цікл 2 выловляе такія няудачы і дае агенту ўзможнасць практыкаць знову, аддаючы цільаванае адзначэння:

def run_loop2(
    app_id: str,
    system_prompt: str,
    meter: CostMeter,
    max_retries: int = 3,
) -> tuple[dict, bool]:
    state = run_loop1(app_id, system_prompt, meter)
    for attempt in range(max_retries):
        decision = state.get("decision", "ran_out_of_steps")
        if decision == "refer":
            _write_human_queue(app_id, state)   # human-in-the-loop path
            return state, True
        if is_correct(decision, app_id):         # deterministic check
            return state, True
        if attempt >= max_retries - 1:
            return state, False                  # exhausted retries
        # construct targeted feedback for next attempt
        expected = load_historical_decisions()[app_id]["expected_decision"]
        feedback = HumanMessage(content=(
            f"Your decision was '{decision}' but this application requires '{expected}'. "
            f"Review the risk factors carefully and call draft_decision again."
        ))
        extra_messages = list(state.get("messages", [])) + [feedback]
        state = run_loop1(app_id, system_prompt, meter, extra_messages=extra_messages)
    return state, False

Калькольнае паведамленне аб калькольных выборах ў дызайне ўцімкнута.

Суддзя ўзьяўлены. Функцыя is_correct() чытае файл з історыяй рашэнняў і парабягае строкі. Яна ніколі не вызывае модэль. Выварытак ужыць другі модэль як суддзю прынесла б другы істочнік ненадзеямасці, а тут абсалютная правда проста ёсць абсалютная правда.

Перадача на адзначэнне — гэта ўжо адгукнуты рэзультат. Калі агент кажа refer, цыкл 2 запісвае справу ў human_queue.json і паведамляе пра успех. Перадача на адзначэнне адзначэнай адзначкі лічыцца валідным канцэльным станам, а не чымсь, што трэба перапрыбавіць.

Адгукненне называе мету. У замяну фразе "прабаваць зноў" паведамленне паведамляе агенту, якая катэгорыя рашэння ў правілі, і прасіць яго паўтарна пераглянуць фактыры рызыку. Гэта направляе наступны запуск да правільнай адпаведзі, там як слепая перапрыбавка могла б блукаты.

Історыя пераходзіць далей. Дадатковыя паведамленні включаюць усю пакульную розмову плюс адзінакі, таму ў наступны раз агент бачыць усё, што ужо прыметыў, і менш верагатыва, што ён будзе павтараць той самы неправі падход.

Цікл 3: прагледчык календара запускаў

Захішчае ад: жорсткага з’яеднання межы тымі, хто стварае працу, і тымі, хто яе выкарыстоўвае. Сігнал адзінаку: змест pending_applications.json — звычайнага файла, які выступае як календар запускаў.

Этот цікл являе сабою оркестратора. Ён берае даныя з календара запускаў, праходзіць адну аплікацыю чэрза ціклы 1 і 2, апдэйтуе статус запуска і час а час разы запускае цікл 4:

def run_event_loop(
    system_prompt: str,
    meter: CostMeter,
    pending_path: Path = DEFAULT_PENDING,
    state_path: Path = DEFAULT_STATE,
    once: bool = False,
) -> None:
    run_state = load_run_state(state_path)
    verify_judge_checksum(run_state.get("judge_checksum", ""))   # tamper check
    total = len(json.loads(pending_path.read_text(encoding="utf-8-sig"))
                if pending_path.exists() else [])
    processed = 0
    while True:
        pending = (json.loads(pending_path.read_text(encoding="utf-8-sig"))
                   if pending_path.exists() else [])
        if not pending:
            if once:
                print("[loop3] queue empty - done")
                break
            print("[loop3] queue empty - sleeping...")
            time.sleep(POLL_INTERVAL)
            continue
        app_id = pending[0]
        processed += 1
        print(f"[loop3] {processed}/{total}  {app_id} ...", flush=True)
        try:
            result_state, passed = run_loop2(app_id, prompt_with_lessons, meter)
        except BudgetExhaustedError:
            raise                                # budget exhaustion is fatal
        except Exception as exc:
            print(f"[loop3] {app_id} ERROR: {exc!r} - skipping")
            # remove from queue and continue - one bad app cannot block the rest
            remaining = [x for x in pending if x != app_id]
            pending_path.write_text(json.dumps(remaining), encoding="utf-8")
            continue
        # ... update state, distill lesson, trigger hill-climbing

Чаму файл для очэрады

Очэрада — гэта файл у фармаце JSON, а не стрым у Redis чытаў SQS очэрада, і гэта рабіцца навмесна, таму што для локальнага розвіцця яго лёгка адгледзець (атрымаць файл і пазнакаміць, што чакае на апрацоўку), лёгка праказаць (пачаць занова пасля збою, і очэрада застанецца недыяранай) і ён аддзейнаваны (тое, што яго запалняе, не патрабуе інформацыі пра спожывача). У прымэтнай сістэме быў бы выкарыстоўваны належны посерэднік паведамленняў, а контракт інтэрфейсу, тыя самыя спісы ID, не змяніцца.

Адкрыцце пазусоў

Перад апрацоўкай будзь-чаго, Loop 3 пераканальваецца ў правядомасці дадзеных:

verify_judge_checksum(run_state.get("judge_checksum", ""))

Пераказ працюе так:

def compute_judge_checksum() -> str:
    content = _HIST.read_bytes()           # historical_decisions.json
    return hashlib.sha256(content).hexdigest()


def verify_judge_checksum(expected: str) -> None:
    actual = compute_judge_checksum()
    if actual != expected:
        raise RuntimeError(
            f"Judge checksum mismatch - historical_decisions.json was tampered with."
        )

Хэш SHA-256 файла з історычнымі рашэннямі зберагаецца ў стане запуску пад час ініцыялізаціі і пераглядаецца ў кожны раз, калі пачынаецца цыкл запуску. Калі хтась зменіў асалодныя даны, каб паспрыяць агенту, сістэма адмовляецца працаваць. Чыгунам так закрываецца таяйныя слабасці ў сістэмах самапраграскі: найдэшавшы спосаб прынясці лепшых рэзультатаў — гэта змяніць крытэрыя оцэнкі.

Пропуск проты зупінкі

Структура try/except у Цыкле 3 чыста пазначае граніцу. Калі выкарана вялічына бюджэту, усё зупіняецца. Будзь-якія іншыя асаблівасці фіксуюцца, аплікацыя, якая вызвала проблему, адключаецца з чэргі, і працоўваецца наступная. Такім чынам, адна несправная аплікацыя не можа заморозіць весь працэс, тады як выкараны бюджэт ёсць фатальным за прызначэнням, адтуды што сістэма не должна продаваць далей, калі няма чаго выдаваць.

Цыкл 4: падыманне па пагорку

Захістваць кантэкст: фіксаваны запрос, які ніколі не вучыцца на адпаведных досведах. Сігнал адзынака: точнасць трэніравання разам з рэшынкай аўдыту.

Кожны восьмы рашэнні (HILL_CLIMB_EVERY = 8), цыкл 3 перадае задачу цыклу 4, які адкрывае цыкл размышлення, змены, ацэнкі і фільтрацыі, каб автаматычна паспрабаваць падбіць рэшэнні сістэмнага запросу:

seed prompt
     │
     ▼
evaluate on training set → score, failures
     │
     ▼
┌────────────────────────────────┐
│  for each round:               │
│                                │
│  reflect(current, failures)    │  ← LLM analyzes failure patterns
│       │                        │
│       ▼                        │
│  candidate_prompt              │
│       │                        │
│       ▼                        │
│  evaluate(candidate, train)    │  ← full eval run
│       │                        │
│       ▼                        │
│  audit_prompt(candidate)       │  ← anti-cheating check
│       │                        │
│       ▼                        │
│  if score > best AND clean:    │
│      best = candidate          │  ← keep
│  else:                         │
│      discard                   │  ← revert
└────────────────────────────────┘
     │
     ▼
write best_prompt.txt

Размышляйце

REFLECT_TEMPLATE = """You are improving the system prompt of an insurance underwriting agent.

CURRENT PROMPT:
{prompt}
The agent got these decisions WRONG on training applications:
{failures}
Look for PATTERNS in the failures. Infer GENERAL underwriting rules that would fix them.
Do NOT memorize specific application IDs or applicant details.
Write an improved system prompt. Reply with ONLY the new prompt text."""


def reflect(current_prompt: str, failures: list[dict]) -> str:
    client = anthropic.Anthropic()
    failure_text = "\n".join(
        f"- APP {f['app_id']}: agent said '{f['got']}', expected '{f['expected']}'. "
        f"Application data: {f['application']}"
        for f in failures
    )
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1500,
        messages=[{"role": "user", "content": REFLECT_TEMPLATE.format(
            prompt=current_prompt,
            failures=failure_text,
        )}],
    )
    return response.content[0].text.strip()

Інструкцыя не запам’ятовваць конкрэтныя ID прыкладненняў чы адпаведныя даны заявак мае вялікую значымасць. Без яе мадэль могла бы напісаць такі патрон, як «для APP-003 завжды адмовіць», які б ідэальна пасав да даных для навчання, але абсалютна не пасав да случаў, якія яна не бачыла. Новы патрон павінен генералізаваць на адзінаках у невыпадках, а не на адзінаковых случаях.

Ацэнка

def evaluate_prompt(system_prompt: str, split: str = "train", meter=None):
    tasks = load_tasks(split)
    failures = []
    for task in tasks:
        state = run_loop1(task["id"], system_prompt, meter)
        decision = state.get("decision", "ran_out_of_steps")
        if not is_correct(decision, task["id"]):
            failures.append({
                "app_id": task["id"],
                "got": decision,
                "expected": task["expected_decision"],
                "application": task["application"],
            })
    score = (len(tasks) - len(failures)) / len(tasks)
    return score, failures

Кожны запрошаны прыем прабывае ў цікле 1 у всіх 16 прыкладах для навчання. Гэта дорага — 16 запускаў модэлю за раунд, тое ўсьмо 80 за пяць раундаў, але сігнал чысты: прыем павінен працаваць у всім дыстрыбуцыі даных для навчання, а не толькі на аднам прыкладзе.

Gate

def improve(seed_prompt: str, lessons: list[str], meter: CostMeter, rounds: int = 5) -> str:
    best = with_lessons(seed_prompt, lessons)
    best_score, failures = evaluate_prompt(best, split="train", meter=meter)
    for r in range(1, rounds + 1):
        candidate = reflect(best, failures)
        score, cand_failures = evaluate_prompt(candidate, split="train", meter=meter)
        verdict = audit_prompt(candidate)        # anti-cheat check
        if score > best_score and verdict == "VERDICT: CLEAN":
            best, best_score, failures = candidate, score, cand_failures
            BEST_PROMPT_PATH.write_text(best, encoding="utf-8")
            print(f"round {r}: {score:.0%} KEPT  ({len(cand_failures)} failures)")
        else:
            reason = "cheating" if verdict != "VERDICT: CLEAN" else f"{score:.0%} <= {best_score:.0%}"
            print(f"round {r}: {score:.0%} REVERTED ({reason})")
    return best

Прынцыпава, два ўмовы павінны быць выпаненыя, прытымкі кандыдата можа быць прыняты. Перша — score > best_score, тое ўсё, што кандыдат павінен строго перамагаць ныякшага кандыдата. Равенства адхоўваюцца, што запобегае змінам у запытанні. Другая — аўдытар павінен вернуць VERDICT: CLEAN, паўтараючы, што кандыдат не мае жорсткая-закодаваных адпаведзей.

Аўдытар запытання

AUDIT_SYSTEM = (
    "You are a security auditor for AI systems. "
    "Check whether the prompt contains any hardcoded application IDs (e.g. APP-001) "
    "or hardcoded decisions for specific applications. "
    "These would constitute cheating — memorizing answers instead of learning rules. "
    "Reply with exactly one of:\n"
    "VERDICT: CLEAN\n"
    "VERDICT: CHEATING\n"
    "Followed by a one-sentence explanation."
)


def audit_prompt(evolved_prompt: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=200,
        system=AUDIT_SYSTEM,
        messages=[{"role": "user", "content": f"PROMPT TO AUDIT:\n{evolved_prompt}"}],
    )
    return response.content[0].text.strip().split("\n")[0]   # first line only

Гэта ўстановка, у якой LLM выступае як суддзя, але яна обмежваецца адной бінарной структурной запытаннем: чыі прымапт містыць конкрэтныя ID або запам’ятаваныя адказы? Гэта тып класыфікаціі, дзе суддзі-мадэлі ёсць надзеямі. Аўдытар ніколі не оцінюе якасць, стыль чы правільнасць; ён толькі шукае адзін патэрн.

Памяць урокаў

Паштрафаў ад методу «падымання на хыл» сістэма зберагае спіс lessons, які застаецца між запускамі. Калі прыкладка продаваліся пасля кожнай спробы, яна вызывае distill_lesson():

DISTILL_PROMPT = """An insurance underwriting agent made a wrong decision.

Application details: {application}
Agent decided: {got}
Correct decision: {expected}
Write ONE short, general underwriting rule that would prevent this mistake.
Do NOT reference this specific application ID or any applicant names/details.
Reply with only the rule, as a single sentence."""


def distill_lesson(application: dict, got: str, expected: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=150,
        messages=[{"role": "user", "content": DISTILL_PROMPT.format(...)}],
    )
    return response.content[0].text.strip()

Кожны курс пасля чаго дадаецца ў кожны наступны запуск Loop 1 праз with_lessons():

def with_lessons(base_prompt: str, lessons: list[str]) -> str:
    if not lessons:
        return base_prompt
    lessons_text = "\n".join(f"- {l}" for l in lessons)
    return base_prompt + f"\n\nUnderwriting lessons learned:\n{lessons_text}"

Гэта нямногаштучная навучэнне без спуску градыента. Сістэма навучаецца на адмылкіх пад час выканання, скарабатываючы стыснутыя знанні у вачынку правіл наўмовым языкам. Функцыя у стылі протаколу CL-BENCH пераканальваецца, чы рэальна гэтыя урокі дапамагаюць:

def measure_gain(base_prompt: str, lessons: list[str], split: str = "test", meter=None) -> float:
    stateless_score, _ = evaluate_prompt(base_prompt, split=split, meter=meter)
    augmented = with_lessons(base_prompt, lessons)
    stateful_score, _ = evaluate_prompt(augmented, split=split, meter=meter)
    return stateful_score - stateless_score   # positive = lessons help

Контроль бюджэту за дапамою метра выкарыстання

Кожны вызов модэлю праходзіць праз аднаго спяльнага CostMeter, які перадаецца з Loop 4 у Loop 3, з Loop 3 у Loop 2, а з Loop 2 у Loop 1, таму ўсе выкарыстанні скарабатываюцца ў аднам месцы:

BUDGET_USD = 2.00
INPUT_PRICE_PER_TOKEN  = 3.00  / 1_000_000
OUTPUT_PRICE_PER_TOKEN = 15.00 / 1_000_000


class CostMeter:
    def record(self, input_tokens: int, output_tokens: int) -> None:
        if self.spent >= self._budget:              # check BEFORE adding
            raise BudgetExhaustedError(
                f"Budget ${self._budget:.2f} exhausted at ${self.spent:.4f}"
            )
        self.spent += input_tokens * INPUT_PRICE_PER_TOKEN \
                    + output_tokens * OUTPUT_PRICE_PER_TOKEN

Сама практыка арджынгу ёсць ключовая: пераканаўцеся ў бюджэце пры дадаванні косту. Такія спосабамі адхылэнні будзе фіксаванае ў той момент, калі досягнута межа, а не пасля таго, як выкарана перадметка. BudgetExhaustedError прайходзіць па іерархіі ціклу, і Цікл 3 зноў выклікае яго, а не паглотвае, таму выкараны бюджэт завжды завершае весь запуск.

Стан запуску з ідэмпатным налаштаваннем

run_state.json фіксуе ўсё, што адбылася: обрабатаваныя запросы, спісы, якія працавалі і не працавалі, накопаныя урокі, рашэнні з моменту пасляльнага аналізу і чэксума судды. Яго ініцыялізацыя ўжо ў захоўнам цылях:

def _init_run_state() -> dict:
    path = _STATE_DIR / "run_state.json"
    state = {}
    if path.exists():
        try:
            content = path.read_text(encoding="utf-8-sig").strip()  # handles Windows BOM
            if content:
                state = json.loads(content)
        except (json.JSONDecodeError, UnicodeDecodeError):
            pass                                 # corrupt file → start fresh
    if not state:
        state = dict(_DEFAULT_RUN_STATE)
    state["judge_checksum"] = compute_judge_checksum()   # always refresh
    path.write_text(json.dumps(state, indent=2), encoding="utf-8")
    return state

Тут показаны тры ўзвычкі стойкасці. Чытанне з utf-8-sig тылу неявна адключае знакі парадку байтов у Windows. Пусты або зіпсаваны файл пераходзіць у стан за замовчаннем, замест таго каб програма зламалася. А контрольная сума судды перасчыляецца ў кожны раз, калі програма запускаецца, і ніколі не выкарыстоўваецца на адной з пярэдніх запускаў.

Тры спосабы запуску

Існуе тры спосабы запуску, і кожны з іх выкарыстоўвае толькі частку стака.

У однамодовым рэжыме запускаецца толькі Цікл 1, што ўдобна для дэбаггінгу адной аплікацыі і для перагляду таго, якія інструменты вікорыстоўвае агент і чаму:

python -m src.main --mode single --app-id APP-001

Рэжым цыклу выдзеяў запускае цыклы 1, 2 і 3 для кожнага чакаючага застосоўвання і можа запусціць цыкл 4 пасля кожных восьмі рашэнняў. Флаг --once спрацоўвае кеш і выходзіць з працы, замест таго каб адлучваць даныя без канца:

python -m src.main --mode event-loop --once

Рэжым падбору працюе безпосерэдна з Ціклам 4, з пяцьма раундамі падыходу на хылы ў часе трэнавання. Пасля чаго ён ацэнівае найкрашчы запрос, знайдзены ў спісе для трэнавання, па спісе для тэставання і выдрукоўвае адзынак падбору:

python -m src.main --mode improve --rounds 5

Адзін прыемлік, ад пачатку да канца

Гэта ўсёце праймаце дадзэнняў для аднаго прыемліка, які праходзіць праз сістэму:

pending_applications.json
          │
          │  [Loop 3 reads APP-009]
          ▼
    run_loop2("APP-009", prompt, meter)
          │
          │  [Loop 2 calls Loop 1]
          ▼
    run_loop1("APP-009", prompt, meter)
          │
          │  [LangGraph StateGraph]
          ▼
    agent_node → ChatAnthropic.invoke([SystemMsg, HumanMsg])
          │
          │  response: tool_call(lookup_application, {"app_id": "APP-009"})
          ▼
    tools_node → lookup_application.invoke({"app_id": "APP-009"})
          │
          │  returns: {id: APP-009, state: TX, coverage: 1200000, ...}
          ▼
    agent_node → ChatAnthropic.invoke([..., ToolMessage])
          │
          │  response: tool_call(retrieve_rules, {"risk_factor": "coverage"})
          ▼
    tools_node → retrieve_rules.invoke({"risk_factor": "coverage"})
          │
          │  returns: ["Coverage > $1M requires mandatory refer. Flag: high_coverage"]
          ▼
    agent_node → ChatAnthropic.invoke([..., ToolMessage])
          │
          │  response: tool_call(draft_decision, {decision: "refer", ...})
          ▼
    tools_node → draft_decision.invoke({...})
          │
          │  updates state: {decision: "refer", flags: ["high_coverage"], ...}
          ▼
    should_continue → END (no more tool calls)
          │
          ▼
    Loop 2: decision == "refer" → write human_queue.json → return (state, True)
          │
          ▼
    Loop 3: passed=True → update run_state.json → remove APP-009 from queue
          │
          │  [decisions_since_last_improvement becomes 9 >= 8]
          ▼
    Loop 4: improve(seed_prompt, lessons, meter, rounds=5)
          │
          │  reflect → evaluate → audit → gate → write best_prompt.txt
          ▼
    Loop 3: continue with APP-010 using new best_prompt

Урокі з практыкі ўтварэння

1. Раздзеляйце «оракула» ад агента. Суддзя (is_correct, evaluate_prompt) не дзеліць ніякога коду з агентам (run_loop1). Суддзя чытае статычныя файлы, а агент запускае мадэль. Дазвольце модэлі самай оцаніваць свой выход, і тады у вас будзе агент, які ніколі не памыліцца.

2. Зробіце оцэнку дэтэрміністычной. Правільнае выкананне заведамаў, точнасць навучэння і точнасць тэстав ёсць дэтэрміністычнымі для адных і тых сабе вхідных дадзеных. Ядзейная адменшчына існуе толькі ў адказах модэлі, таму будзь-які рэйтынг можна воспродукаваць на адваротах з праграмы і насэнні.

3. Для структураванага выходнага тэксту выкорыстоўваеце інструмент для выдання фінальнай адпаведзі. Інструмент draft_decision лепшы за парсаванне прозы. Яго схема выклікае чысты слоўнік (accept | modify | decline | refer), неабходныя поля (flags, rationale, recommended_premium_adj) і ўсе іх тыпы, таму агент вынужаны ясна выказваць сваю пазіцыю.

4. Захады прыбліжэння да чыстасці ў самапраўцавых сістэмах не ўзлегкай прыроды. Калі сістэма можа правяць сваія сабеінструкцыі, яна будзе шукаць шляхавыя рашэнні, а адным з яўных ёсць запам’ятовванне адпаведзей з трэнінгу. Функцыя audit_prompt() автаматычна выяўляе гэта. Без яе метод «падымання па хыле» незмінна дае прампты з майжэ ідэальной точнасцю ў трэнінгу і майжэ аднарадковай точнасцю ў тэстах.

5. Знакі парадографу байтов і способы кодування ўважаюцца багамі пад час вырабаткі. Калі вы працуеце на Windows або прымуеце файлы ад корыстнікаў Windows, кожны раз, калі чытаеце файлы, неабходна выкарыстоўваць encoding="utf-8-sig". BOM, які ствараецца пры выкарыстоўванні Windows Notepad, спрычынае памылку json.JSONDecodeError: Unexpected UTF-8 BOM; яе важка дыягнаставаць, таму што файл у рэдагувальніку выглядае абсалютна нормальна.

6. Пераканаецеся ў наявнасці бюджэту перад тым, як гульватаваць грошымі, а не пасля.

# WRONG: allows one overspend before raising
self.spent += cost
if self.spent > self._budget:
    raise BudgetExhaustedError(...)
# CORRECT: raises before the overspend registers
if self.spent >= self._budget:
    raise BudgetExhaustedError(...)
self.spent += cost

Календарны вырахунак мае значэнне, калі калькаванне колькіх модэляў адбываецца быстро адзін за іншым. Адначасова дадзіце першыя даны і пераканайцеся пазней — бюджэт можа быць перакрануты на суму, якая перавышае вартасць адной калькавання, прычымы ля выяўленняя памылкі.

7. Ачысціце лініі прыгонаў у дужа дугіх цыклах.

print(f"[loop3] {processed}/{total}  {app_id} ...", flush=True)

У Windows стандартны выхідныя даныя знаходзяцца ў буферы, калі толькі не зазначана інша справа. Калі прыбыць параметр flush=True, рэчык „processing APP-009“ показваецца пасля таго, як вяртаеся вызов API, што паслабляе функцыя індыкатора працы. Заўжды неабходна вычысляць буфер з рэчыкаў, якія прадыходзяць перад блакуючымі операцыямі I/O.

8. Трэба трываліваць бюджэт для методу hill-climbing окрэмна ад бюджэту цыклу змаганняў. Выкарыстоўванне всего бюджэту пад час методу hill-climbing павінна быць зафіксавана на месцы вызову і не павінна прыводзіць да зупінкі цыклу змаганняў. Пасля таго, як восьма оцэнкі прыкладнага програмаў выкарыстоўваюць большую частку бюджэту у розмярэ $2, у методу hill-climbing застаецца мало можлівасцей. Цыкл змаганняў спецыяльна фіксуе памылку BudgetExhaustedError, якая выйшла з методу hill-climbing, і продовжае обрабоцу засталых прыкладных програмаў:

try:
    system_prompt = run_hill_climbing(system_prompt, lessons, meter)
    run_state["decisions_since_last_improvement"] = 0
except BudgetExhaustedError:
    print(f"[loop3] hill-climbing skipped — budget exhausted at ${meter.spent:.4f}")
    run_state["decisions_since_last_improvement"] = 0

Застосаванне шаблона ў іншых месцах

Дызайн з чатырма цікламі не прыязначаны толькі для страхавання. Чырэз тое, каб яго адаптаваць, трэба заменіць наступныя элементы:

  • Інструменты ціклу 1. У даным варыянце: пошук, правілы, сігнал, прымець. У вашам варыянце: інструменты для запошуку і выканання дзеянняў у вашай сферы.
  • Аксіяны факты. У даным варыянце: файл історычных прымэцоў. У вашам варыянце: ваша саўственная нанесеная атрыбутамі база дадзенаў.
  • Суддзя. У даным варыянце: порэванне страк. У вашам варыянце: тэст на правільнасць, спецыяльны для вашага задачы.
  • Урокі. У даным варыянце: правілы атрыбутавання рызыка. У вашам варыянце: шаблоны адказоў на памылкі у вашай сферы.
  • Запрос для падыходу па хілу. У даным варыянце: запрос для сістэмы атрыбутавання рызыка. У вашам варыянце: запрос для сістэмы вашага агента.
  • Бюджэт. У даным варыянце: $2.00. У вашам варыянце: сума, якая павінна купіравацца за кожны запуск.

Адзін строгія правіл — суддзя павінен быць дэтэрміністычным і незалежным ад агента. Калі для вашага заведама нельга знайсці тэст на дэтэрміністычную правільнасць, сістэма не можа надзеязна павышаць свою якасць.

Прабаваць

Настройкі і чатыры каманды:

# 1. Install
cd underwriting_loop
pip install -e ".[dev]"
export ANTHROPIC_API_KEY=sk-ant-...
# 2. Run a single application (debug mode - cheapest)
python -m src.main --mode single --app-id APP-001
# 3. Drain the pending queue with event loop
python -m src.main --mode event-loop --once
# 4. Run standalone hill-climbing (5 rounds)
python -m src.main --mode improve --rounds 5
# 5. Run the full offline test suite (no API key needed)
pytest tests/ -v

Рэзультаты адчынення на вышыну выглядаюць так:

seed score: 44%  (9 failures)
round 1: 56% KEPT  (7 failures)
round 2: 56% REVERTED (score 56% <= best 56%)
round 3: 69% KEPT  (5 failures)
round 4: 75% KEPT  (4 failures)
round 5: 69% REVERTED (score 69% <= best 75%)
Final test evaluation...
spend:              $1.8342
test score:         75%
gain (vs baseline): +25%

Прыгунчык-прампт пачынае трэніраванне з 44%. Чыры разы пасля гэтага прампт-пераможцы пасягае 75% у тэстовай выборцы, якая не была выкарыстоўвана пад час трэніравання, тое ўсьмо 25 пунктавых прыроста па супэрнатыўнай працэсу прыкрашэння прампта без ручнай рэдагування.

Што дае наявнасць ціх цыклав

Інжынерыя цыклав — гэта не фрэймворк. Цэта спосаб разбівання надзеямоўнасці агента на шары, кожны з якіх мае адну адпаведальнасць і адны механізм зворотнага звязку. Чатыры цыклы рашаюцы чатыры разныя формы абыекцыі:

  • Цыкл 1 адпаведае за апарэнтнасць: агент павінен ацэніць доказы пры вынікненні рашэння.
  • Цыкл 2 адпаведае за абыекцыі пад час адной експанзіі: адна неправільная логічная дорага не должна назаўсёды заблокаваць працэс.
  • Цыкл 3 адпаведае за працэсавую здатнасць і стойкасць: адна неякісная працэса не должна заблокаваць весь ланцоўк.
  • Цікл 4 усувае парадоксальныя памылкі пацелаванням самага запіту на адказ на базе накупілых няўдаласцей.

Складзіце іх разам, дадзіце дэтэрміністычнага суддзю, захаваце все за дапамою кантролі бюджэту і выяўлення парадоксальных змян, і сістэма з часам становіцца значна лепшай без якога-лібо участка людзя.

Што вы атрымуеце

Адмацавайце гэтыяя дызайн з адкрытымі вачамі. Найбольшая ажыратае паведамленне з болей шырокіх даследжэнняя цікалаў агентаў — гэта „канцэпцыя канцэптуальнага выдання“ Адды Османі: даўанне ціклу паўнага кантролю і прыпынэнне чытання яго рэзультатаў. Адносныя перавагі застаюцца рэальнымі і вімернымі.

  1. Самапраўцаванне без участка людзя. Метод „альпінізму“ падняў точнасць навучэння з 44% да 75% без якога-лібо ручнага рэедагавання запытаў, а сістэма сама выявіла правілы прадацы на адпаведнасць на базе патэранаў няўдаласцей.
  2. Айсоляцыя канфлыктов между слоямі. Адна несправная прыкладка не спрычынае зупінкі всіго працэсу. Цыкл 3 фіксуеі бяговыя канфлыкты кожной прыкладкі, фіксуе іх у логах і продовжае працу. BudgetExhaustedError — ўжо ўсё, што можа дастацца да верхніх рэвю-этапаў, і гэта зраблена намерна.
  3. Прогрэс, які можна вымерыць і аўдытаваць. Функцыя measure_gain() дае конкрэтную цыфру, паказваючы разлік між сістэмамі з станамі і без іх на выдэланай частцы дадзеных. Прырост можна прэдставіць заінтэрэсаваным сторонам у відсотках і фінансавых затратах.
  4. Ацэнка, захішчаная ад маніпуляцый. Чэксум SHA-256 на абсалютна правільных дадзеных запобегае найпашчэрэзнейшаму мовчазнаму бяговаму канфлыку у сістэмах, якія самыя сабе паспрабоўваюць паспрабаваць падняць свою эфектыўнасць, тэбто калі хтось маніпулюе адпаведзямі, каб рэзультаты выглядалі лепшая. Калі чэксумы не збігаюцца, ніч не запускаецца.
  5. Знанні, які накапліваюцца. Урокі, выведзеныя з неудач, застаюцца актуальнымі параду за парадам, таму кожны новы запуск Ціклу 1 пачынаецца розумней, чым пярэдні, і не трэба занова вучыцца з нуля.
  6. Продуцэнт і кансюмер застаюцца аддыякраванымі. За дапамою очэркі файлоў тое, што стварае чакальны спіс, ўскладненая ад працэса ацэнкі. Очэрк можна націснуць, пачаць зноў, пераглядзець або перыграць, пры чым код агента застаецца недастрыжаным.
  7. Эскалія да людзяў — гэта стан першага класу. Рашэнні, выданыя функцыяй refer, падаюць у human_queue.json і лічыцца як прынятыя. Цікл не прабуе зноў тое, што павінен адмацаваць чалавек.
  8. Практыкі прабоўкаў ўсмеркаваныя. Адзыв пасля невялікага успеху паведамляе, што пайшло не так, напрыклад: "Вы сказалі "зменіць", але правадзібны адказ — "зглянуцься, пераглянуць фактары рызыку". Гэта дае лепыя рэзультаты быстрэй, чым абыякова перапісваўка дадзеных.

Какія ў цьму затраты

  1. Колькасць токенаў быстра расце. Пяць раундав падымання па горэ пры 16 прыемоў дадзеных для навчання трэбуе ўжо прынеймна 80 запускаў ціклу 1, плюс запускі для аналізу і аўдыту. Одны запуск з параметрам --mode improve зайняў 1,83 долара з бюджэту у 2,00 долараў. Перасягнуць бюджэт — не аўтаномны кейс; гэта стандартны спосаб заканчэння запуску.
  2. Затрымкі рэальныя. Адзін прыем дадзеных праз весь стак можа зайняць 30–60 секунд, а 16 прыемоў у рэжыме ціклу змаганняў — калькі мінут. Гэта падходзіць для пакетной обработкі, але не падходзіць для інтэрактывнага відношэння.
  • Недэтэрмінізм маскіе неудачы. Цікл 1 выкарыстоўвае жывой мадэль, таму аднойчы той самы запіт у той самай прыкладнай програме можа даць разныя последовальнасці інструментаў. Неудача можа не павтарыцца, а для атрымання значымых паказнікаў точнасці патрэбна калькuluвання кілька разоў.
  • Аднароджэнне ўскладнена. Перад першым запускам патрэбны стан запуску, очакуючыя чэргі, історыя рашэнняў з контрольнымі сумамі, файл з найкрашчымі запітамі і спіс урокаў. Гэта у калярдзены раз больш інфраструктуры, чым адна вызова agent.invoke().
  • Перэнастроўка ніколі не знікае. Без захавання аўдыту, метод «падымання па хыле» зрэшты дае запіт, які запам’ятае адпаведзі на трэнаванні, даваючы ідэальныя балы на трэнаванні, але майжэ вялікі шанс на тэстах. Аўдыт — гэта ўсё, што стоіць між самапраграсам і самазрадой.
  • Канцэптуальная здача ўлады — гэта справжні спосаб парадкі. Чым надзейней стае цыкл, тым менш людзі чытаюць яго рэзультаты, і калі вы перэстаеце чытаць, вы перэстаеце зазначаць, што цыкл пачаў парадкавацься новым спосабам. Цыклы, якія не стояць під наглядам, патрабуюць зовнішньага контролю, адколі цыкл не можа сам аўдытаваць ся на семантычную правільнасць.
  • Адлагоджэнне распрастраńваецца на весь стак. Неправыя рашэнні могу выйсці з некорэктнага адбівання ў Цыкле 4, працягнутага правіла ў Цыкле 2, інструмента, які не вернуў нічога ў Цыкле 1, або зіпсаванага стану запуску ў Цыкле 3. Адстэпавае аналіз парадкі ў чатырох вкладзеных цыклах ўскладненей, чым аналіз у аднай функцыі.
  • Чы трэба вообшча ствараць цыкл?

    Аналіз настройкаў публічных цыклаў дае чыткыя рэкамендацыі: выконвайце задачы, якія ўзаўмнавляюцца, можна пераглядаць і маюць высокую цэннасць, з вбудованым пераканальнікам, а для всіх іншых проста зробіце запіт раз. Усё зводзіцца да двух пытанняў.

    1. Чы праца ўзаўмнавляецца, чы ёй трэба багато крокаў, якія ніхто не можа прыгадаць? Якшто дастатній адзін запіт, цыкл дадае зайвага навантажэння і не стварае архітэктуры.
    2. Чы агент можа сам выявіць, чы ён завершыў працу? Без тэста, апэктыўнага фармата чы аднаковых адказаў, нічога не завершае цыкл, і ён продаўляецца без канца.

    Адказы на оба пытання павінны быць «так». Якшо хоць аднаму з яных — «ні», прыхіліцеся да адзінокага запіту.

    Хорашы варыянты

    Узаўмнавляючыяся, велікага обсягу задачы. Шысць націскаў на страхаванне, 500 файлаў коду, 1 000 заявкаў на падтрымку. Цыкл компенсуе свою вартасць настройкі, калі задача выканана багато разоў, а не аднажды.

    Існуе дэтэрміністычны спосаб пераканання ў правільнасці. Гэта могу быць адпаведныя меткі, набор тэстаў, верыфікатор схемы або крэтырыя для адгукнуць/непрацэсу. Суддзя ніколі не павінен выклікваць мадэль, таму што суддзя, які базуецца на модэлі, прыносіць у працэс сваю сабестабільнасць.

    Агент можа сам пераканацца ў правільнасці сваей працы. SDK Agent ад Anthropic называе гэты крок найменш цэнімым. У гэтым случае функцыя is_correct() выполняе гэту задачу, а ў кодаваным агенце — набор тэстаў. Калі агент не можа пераканацца ў правільнасці свайго выходу, цікл не завершаецца.

    Меткаваныя даны аб абразы для пераўтаральнага выкарыстання. Для методу «падымання па хыле» патрэбны дадзеныя для трэнавання і тэставання з вядомымі адпаведнымі рашэннямі. Без іх можна аналізаваць неудачы, але нельга з’ясавіць, чы рэальна гэты аналіз дапамогаў.

    Ніхто не сочыць за кожным запускам. Падумайце пра пакеты апрацоўкі дадзеных за ноч, запланаваныя аўдыты чы схемы апрацоўкі дакументаў, дзе няма жаднага чалавека, які бы працаваў унутры ціклу, і самэ такое падзеянне і ўзначае цяперашнюя конструкцыю. Якшо хтось можа адразу пераглянуць кожны рэзультат, то прымусовыя запускі будуць быстрэйшымі.

    Косць адзінаго запуску ацэнкі ўзьямліва. Адзін цыкл апрацоўкі коштае так жа, як і цэлы працэс адработкі кожнага трэнінгавага запіту. Якшо гэта недаступна, то і Loop 4 будзе занадта дорогі, таму запускайце яго рэдкайша чы на меньшай колькасці дадзеных.

    Вам патрэбны цыфры. Функцыя measure_gain() існуе таму, што фраза «сістэма стала лепейшая» павінна быць квантыфікаваная. Якшо заінтэрэсаваныя стороны хочуць конкрэтныя цыфры, інжынерыя ціклу іх падаюць. Якшо дастатнька толькі фраза «выглядае лепей», то гэта занадта.

    Недастаткова адпаведнасць

    Адзін запрос выконвае ўсё. Пераформатаванне CSV-файла, выявленне даты з дакумента, стварэнне апускі з параграфа. У всіх гэтых задачах колькасць крокаў ўпорадкована, таму можна напісаць скрыпт або адзін разовы запрос. Іспользованне ціклу ў такіх случаях — гэта зайвая складнасць для простай задачы.

    „Готава“ — гэта суцэльна суб’ектыўнасць. Запрос „Зробіць гэты электронны ліст болей професійным“ не мае чыстаўніка, а другі мадэль, які оцінюе естэтыку, толькі паўзяе працэс, не даючы чыткага рэзультата. Спачатку трэба чыста апрацаваць цялевую інструкцыю — напрыклад, меней чым 100 слоў, професійны тон, без знакаў воскласці — тады можна будзе пераканацца, чыто все выконана.

    Дзеянні не можна анулюваць без перагляду. Адправка электронных лістоў, з’едыненне запросаў прыняцця коду, выдаленне запісаў, пераказ грошаў. Неабходна строго контролюваць правы прыступ і вымагаць паузу перад тым, як ўсё будзе выдалена, адправлена або оплачана. Ціклы, якія дазволяюць выконваць незворачныя дзеянні без контролю, — гэта прычыны будучых проблем.

    Бюджэт не можа пакрыць расчынку. Калі 2,00 долара — гэта мало, то працэсы, якія займаюць больш часу, спынююцься раней, чым будуць обработаны ўсі запросы на трэнінг. Сістэма рашыла гэту проблему за дапамою BudgetExhaustedError, але для сцэнарыяў з обмежаным бюджэтам правамерным рашэннем ёсць вялікіяя наборы дадзеных для трэнінгу, меньшы колькісць паўтарэнняў або відсутнасць цыклу 4 пакуль не будзе можлівасць платы за яго.

    Няма падтрымкі стану. Калі нельга запісаць стан запуску, урокі і чэргу на дыск або ў базу дадзеных, то нельга продыржыць працу пасля збою, накапліваць урокі між сеансамі або захістыцца ад выкарыстанняў бюджэту пад час запуску. Сераўсы, якія не маюць стану, патрабуюць іншай архітэктуры.

    Вы яшчэ адклікаецеся. Адзін раз вызванне агента з хорашымі інструментамі паказвае, дзе агент дапамагае, тады калі вярстакаваныя ціклы паказваюць толькі, дзе вялікая сістэма дапамагае. Спачатку перагляньце адзін агент, а шары дадзіце тады, калі стануце знаёмыя з яго сопнымі спосабамі абярэння.

    Паслядніе правілы даў Рыс Салліван, які паветарае пра два крайносці: ціклы "tokenmaxxing", якія працуюць толькі для збільшэння вітрачання, і наўважныя однаклеткавыя формулы "promptchud". Цікл мае сэнс, калі ён відтварае рэальныя абярэнні продукту ў рэалістычных тэстовых умовах, а тырае свой сэнс, калі проста выглядае вражаюча.

    Асоўнасці восьміх якосцей ціклу, які працуе

    Ціклы зазвычай перываюцца неякшымі спосабамі. Яны ніколі не зупіняюцца, не выкарыстоўваюць усе бюджэты, а таксама не вяртаюць некалькасці, пазначаныя як готавыя. Аналіз публічных рэшэнняў з вікорыстаннем ціклоў паказаў, што ў кожным ціклы, які працуе надзеямо, ёсць васьмы элементы, і кожны з іх адпавядае певнаму рашэнню па дызайну ў гэтым кодавым базе.

    1. Пераканальванае цялевае значэнне

    З’явіце паўназначэнне „готава“, каб машына магла яго пераканаць, а не каб чалавек могаў пачувацца задоволеным. Фраза „Зробіць хорашае рашэнне па страхаванні“ не можа быць перакананае. Функцыя is_correct(decision, app_id) паўставляе порэванне з пытанымі рашэннямі — або яны збігаюцца, або ні. Без пераканальванага цялевага значэння цікл ніколі не ведае, калі зупініцца, і будзе вяртаць правільны, але некалькасці вынік як готавая праца. Цялевае значэнне ёсць бінарным прадыкатам, а не апісаннем якосці. Раздзяленне на навчальныя і тэстовыя даны запэўняе, што мадэль будзе працаваць эфектыва, а не толькі запамятаваць пытаныя даны.

    2. Абавесцьце ціклу

    Max стараецца, выкорыстоўваючы бюджэт чы горачык часу, завжды, без выключэння. Ніколі не прыпускайце, што мадэль зупініцца сама. У простых наладках, такіх як цыкл while :; do ... done, няма нічага з боку мадэлі, што могла б яе зупініць, таму яны працуюць, пакуль хтось не завершыць працэс; SDK ад OpenAI включае параметр max_turns, таму што практыка паказала, што мадэлі не завжды зупіняюцца самыя. У гэтым кодавым базі ёсць тры механізмы зупінкі на трох прыемах: MAX_STEPS = 8 у Цыкле 1 запобегае бесканцовым ланцоўкам інструментаў, max_retries = 3 у Цыкле 2 запобегае бесканцовым спробам, а BUDGET_USD = 2.00 праз CostMeter являецца апошнім захамоўкам. Калі досягнута максымальная колькасць крокаў, Цыкл 1 вяртае значэнне ran_out_of_steps; калі заканчываюцца спробы, Цыкл 2 вяртае passed=False; а калі заканчываецца бюджэт, адпаведная памылка распространяецца і зупіняе усё.

    3. Хорашы інструменты

    Дзеянні, які можа выконваць агент, павінны быць надзеянымі, чыста парадактаванымі і чатверткаванымі па межах. Сам цыкл ёсць просты, а праца выконвана ў інструментах. Рэкамендацыі Anthropic заключаюцца ў тым, што наборы інструментоў і ўсёяя іх дакументацыя павінны быць розроблены адзячна, адколі нечыстае парадактаванне прыводзіць да нечыстых вызоваў, а занадта моцны інструмент — да неперадворачальных памылак. Ёсць чатыры інструмента, кожны з якіх віконвае адну задачу: чыстая выявленне інфармацыі без парадуктывных эфектаў, пошук за ключоўымі словамі ў статычнай базе знанняў, сімуляцыя запрашэння зовнішніх дадзенаў, а таксама структураванае дзеянне ў тэрмінале, якое вымагае адпаведнасці да запісанага шымату (Literal["accept", "modify", "decline", "refer"]), а таксама паспаку і ясныя флагі. Без гэтага апошнага элемента агент вяртае прозу, якая вымагае чутлівага парсавання.

    4. Памяць

    Зберагаеце історыю, але створыце ўзвод, каб кантэкст не стаў занадта вялікім, а для чаго-небудзь, што трывае дыялогаў больш за адны, запісваеце стан на дыск. У рашытку Anthropic’s long-running-agent harness агент чытае пярэйшы стан з git і файлаў прагрэсу, рабіць поступовы прагрэс і залічвае срэдавішча чыстым для наступнай ітэрацыі. Неаўтаматызаванае вікна кантэксту не перазжывае перэзапускі, збоі чы розмярныя ліміты. Чыры разныя механізмы працуюць у разных часовых масштабах. У межах аднаго запуску, add_messages reducer з LangGraph акумулюе весь дыялог з інструментамі для адной прыемлі. Між запускамі, спіс lessons у run_state.json акумулюе структураваныя правілы з усіх прыемлі і ўсіх перэзапускаў. distill_lesson() — гэта крок кампрэсіі: ён зберагае аднае загальназастосавальныя правілы, а не неаўтаматызаваны транскрыпт абароткі, чым прадтаеіце спіс корачым і кантэкст, які вводзіцца, керованым.

    5. Адзінаковы пераконтрольвальнік

    Ствараць, атрыбутаваць, выправляць, павтараць — і творцы ніколі не оцінююць сваю сябе працу. SDK Anthropic называе перакантрольванне вашай працы найменш ацэнаваным крокам, але гэта перакантрольванне павінна быць незалежным ад стварэння. Агент, які сам сябе оцінюе, стае простым падтрымальнікам, а калі другаму агенту даць толькі задачу атрыбутавання, гэтага можна ухіліцца. У гэтым кодавым базі існуе тры незалежныя ступені перакантрольвання:

    • Перакантрольванне па рубрыцы (структура). RubricMiddleware пераглядае дзевяць правіл на аналізаваны выходны матэрыял за дапамою чыстых булевых выразоў, без вікорыстоўвання модэлю.
    • Перакантрольванне судды (правільнасць). is_correct() поручае адпаведнасць адпаведзення з зберажаным пазначэннем за дапамою точнага порэвання строк.
    • Перакантрольванне аўдыту (борьба з шахрайствам). audit_prompt() шукае запам’ятаваныя адпаведзення за дапамою бінарнай класыфікаціі модэлю, а не ацэнкі якосці.

    Рубрыка і судзія ўсё цяпер абдыцеліва. Аўдыт выкарыстоўвае мадэль, але толькі для бінарнага структурнага пытання (“Чы гэты элемент мае ID прыкладнення?”), у катэгорыі, дзе судзіяўскія мадэлі ўсё надзейныя.

    6. План для складных заданняў

    Для большага, багатоэтапнага заведання нехай агент напісае план прытым, перш чым дзеяць, а для маленькага — прыхіліцеся да яго. Доследжэнні Anthropic пра агентах, якія працуюць дзяўно, паказалі, што кроўчатая праця над адной функцыяй за раз ёсць крытычная, таму што цыкл, якім паведамляюць пацелаваць усё зараз, працуе неэфектыва, тады калі адна змяна за пасаж прыводзіць да рэшэння. У гэтым кодавым базе запрошэнне паказвае чатыры крокі з інструментамі па парадку, які выступае як вбудоўаны план: запрашаць даны, запрашаць правілы, запрашаць зовнішнія сігналы, прыняць рашэнне. Такі парадак не дазволяе агенту адразу прыняць рашэнне без аналізу доказаў, а ліміт у восем крокаў падабраны так, каб паўная последовасць плюс адна спроба паўтарэння былі можлівыя.

    7. Логаванне

    Запісвайце кожную думку, дзеянне і рэзультат, каб пад час сесіі дыбагавання у 3 гадзіны ночы было чым чытаць. Калі які-небудзь крок не запісан, то, з точкі зору дыбагавання, ён не адбыўся. Неудачы ў вкладзеных ціклах практычна немагчыма дыягнаставаць без логаў: які цікл, якае прыкладзенне, якая спроба, які інструмент вернуў некарэктны рэзультат? Для этага існуе тры рэвэлі. Лініі прагрэсу, такія як [loop3] 3/16 APP-009 ..., апошнія павінны быць запісаныя, што ў Windows, дзе stdout ў буферы, ёсць беззаперэчным трэбаваннем. Лініі рашэння паказваюць рашэнне, статус і час выканання для кожнага прыкладзення. Лініі з інформацыяй парадкі падтверджаюць, што выкарана экстракцыя.

    8. Розумеўне костоў

    Ціклы шырэй заўважліва спалююць токены, таму пачніце з малага і обмежанага масштабу, а потым працаваце над ўзростам. Абавязкова сачыце за першымі кількама запускамі, прычым не заставляйце цікл работаць без нагляду. Одна паўтарэння працэсу падымання на «вышыну» за 16 трэнінгавых запускаў, уключаючы пяць паўтарэнняў, а таксама моменты аналізу і перагляду, стоіць майжэ $2,00. Толькі пасля таго, як вы знаеце вартасць за адну ітэрацыю, вы можэце задаць бюджэт і пераходзіць да наступных крокаў. CostMeter фіксуе вытраты з точнасцю да чатыро знакоў пасля камыля і выклікае BudgetExhaustedError яшчэ до таго, як будзе зафіксавана перавытрата:

    # The ordering matters:
    if self.spent >= self._budget:          # check BEFORE adding
        raise BudgetExhaustedError(...)
    self.spent += cost                       # add AFTER the check clears
    

    Цыкл задач таксама фіксуе выкаранае бюджэт пад аплычэнні на спуску з горы окрэма ад выкаранага пад апрацоўцы, таму выкараны бюджэт у серэдзіне раунду самапрацоўкі не прыводзіць да знехаджэння рэшты чэргі. Цыкл працуе стабільна: прахідае дорогі крок самапрацоўкі і продовжае апрацоўку.

    Спіс перакананняя перш чым заставіць яго працаваць сама

    Перш чым даваць можлівасць якому-небудзь цыклу працаваць без нагляду, пераканайцеся ў наступным:

    1. Цель можа быць перакананая машынай (тут is_correct() у супараванні з адпаведнымі дадзеннямі).
    2. На кожным роўні ёсць жорсткія межы (MAX_STEPS, max_retries, BUDGET_USD).
    3. Інструменты маюць чыста апісаныя межы і документацыю (чатыры інструменты, по аднаму на кожную задачу).
    4. Стан сістэмы застаецца незменным пасля перзапуску (run_state.json плюс спіс урокаў).
    5. Контролер аддзелены ад генератора (мідлвэр для ацэнкі плюс дэтэрміністычны суддзя).
    6. Складныя задачы маюць неявны або явны план (чатырохэтапная последовальнасць у запіце).
    7. Ключовыя этапы фіксуюцца і выкарыстоўваюцца (лініі прыгодзі [loop3]).
    8. Бюджэт задаецца і стэжыцца з першага вызову (адзін CostMeter дзеўяецца між усіма цыкламі).

    Калі працягнуць хоць аднаго з этапаў, цыкл не будзе работаць у працоўнай сіткі — не завжды адразу, але непаспэшна. Большасць працоўных сбоев вынікае з відсутнасці крока пераканання, умовы зупінкі або стану, які застаецца. Спачатку выправіце гэтыя аспекты, а потым налаштавайце все іншае.

    Спадні матэрыялы

  • Playwright і TypeScript без пазнання TypeScript: інжынер, слой AI і чатыры бар’еры — Як інжынер з адпаведным дазнаўнем у Selenium і C# стварыў фрэймворк Playwright на TypeScript з выкарыстаннем AI: структура, правілы архітэктуры, навыкі, хукі і чатыроэтапны працэс обработкі заявак.
  • Інжынерыя цыклаў для AI-агентаў: пяць частак, чатыры формы і стварэнне LangGraph — Як спецыяльна спроекаваць цыкл для агента: яго пяць частак, чатыры распашчастыя формы, агент для дакладкі і прабаў у LangGraph, механізмы захавання ад неудач і калі не трэба вяртацца да цыкла.
  • Самапрацоўныя агенты: вясці, навучэнне і кантроліруемыя змяны — Архітэктура для агентаў, якія восстанавляюцца пад час выканання, навучаюцца па меры выканання і зміняюцца толькі праз перакананыя, тэставаныя версіі, з выкарыстоўваннем LangGraph, MongoDB і Phoenix.
  • Ад агентскіх цыклаў да графаў рабочых пралэцэсаў: тыпаваны стан, брамы доказаў і обмежанае восстанавленне — Чаму агенты, якія працуюць дзяўно, не падходзяць для аднаго цыкла, і як явныя вузлы, рэшткі, тыпаваныя артыфакты, перакананыя пераходы і правыя на кожны вузел робяць іх надзеянымі.
  • Запісваўце цыклаў, якія выкананыя АІ-агентам у мобільным дапытку — Чатыры пытанні, на якія павінен адпаведаць кожны цыкл агента, шасць вярстакавых цыклаў ад натыску да фрагментацыі, і пяць спосабоў, якімі перакананне можа пацэлавацца як завершанне без выявлення нічога.
  • Інжынерыя цыклаў: стварэнне цыклаў агента з верыфікаторамі і правіламі зупінкі — Практычны падчыннік для стварэння чатырох шароўдных цыклаў агента: асновы ReAct, калібраторы рубрык, маршрутызацыя LangGraph, контракт цыкла, трыгеры запуску, метод «падымання на хыле» і захоўнікі ад неудач.