Галоўная / Артыкулы / Практычныя прытамулкі: ваша рамка AI-агента, верагчыва, не тая, якой трэба. Як гэта паспрацаваць

Практычныя прытамулкі: ваша рамка AI-агента, верагчыва, не тая, якой трэба. Як гэта паспрацаваць

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

2234 слоў

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

Запит, які всі ставяць на адварот

Калі працуеце на стадыі «Запит, які задаюць усі», спачатку запішыце умовы контракту: неабяжлівыя даны, сигнал успеху і тое, што выходзіць пад частыя неудачы. Такі список пераканаецца дапамагае залічваць змяны ў кодзе чыста.

Аксія 1: Насколькі дэтерміністычным павінен быть ваш разгалужэння?

Калі працуеце над стадзіяй «Як дэтэрміністычны» для Асаху 1, спачатку запішыце контракт: неабяжлівыя даннэ, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список перакладоў заходзіць на тое, каб пазнейшыя змены коду былі чыстымі. Дакументавайце як шлях успеху, так і шлях вяснавання. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў ёсць часткай продукту, а не пазнейшым дапрацоўкам. Стварайце контрольныя пункты пасля дорогіх крокаў. Функцыя вяснавання не должна зноў выклікаць той самы вызов LLM, калі аператар перапрыбуе пазнейшы вузел.

# A branch where non-determinism is FINE — picking a tone for a summary email.
# If the agent occasionally phrases things slightly differently, nobody's paged.
def draft_summary_tone(context: dict) -> str:
    return llm_call(
        prompt=f"Summarize this incident in a {context['audience']}-appropriate tone.",
        temperature=0.7,  # variability here is a feature, not a bug
    )
# A branch where non-determinism is NOT fine — deciding whether to page a human
# at 4am versus auto-remediating. This must be code, not a prompt.
def route_alert(alert: dict) -> str:
    if alert["severity"] == "critical" and alert["service"] in PAGE_ALWAYS_SERVICES:
        return "page_oncall"
    if alert["auto_remediation_available"] and alert["confidence"] > 0.9:
        return "auto_remediate"
    if alert["severity"] == "critical":
        return "page_oncall"
    return "log_and_monitor"

Асах 2: Як дзеўна жыць адна ейка працы?

Калі працуеце над стадзіяй «Як дыўга» у Axis 2, спачатку запісайце умовы працы: неабходныя даны, сігнал успеху і тое, што выходзіць у разе частковага невыпання. Такі список контроля дапамагае заліцвачыць пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, невыпанне павінна вказваць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг задач. Зробіце перапактаванне пасля дорогіх крокаў. Програма не павінна зноў выклікаць той самы кантакт з LLM, калі аператар праказвае пазнейшы вузел.

# Short-lived: starts and finishes inside one HTTP request.
# This is the "no framework needed" zone — a framework here is pure overhead.
async def handle_summarize_request(request: SummarizeRequest) -> SummarizeResponse:
    text = await fetch_document(request.doc_id)
    summary = await llm_summarize(text, max_tokens=300)
    return SummarizeResponse(summary=summary)
# Long-lived: this alert might sit in "awaiting human ack" for six hours
# while the on-call engineer is asleep, then resume on a completely
# different process after a deploy rotated the pods underneath it.
class AlertTriageWorkflow:
    async def run(self, alert: dict) -> dict:
        decision = await self.classify_and_route(alert)
        if decision == "page_oncall":
            await self.page(alert)
            await self.wait_for_ack(timeout_hours=1)  # this line is the whole ballgame
        ...

Калі працуеце над стадзіяй «Як дыўга» у Axis 2, спачатку запісайце умовы працы: неабходныя даны, сігнал успеху і тое, што выходзіць у разе частковага невыпання. Такі список контроля дапамагае заліцвачыць пазнейшыя змены ў кодзе. Запісвайце час выканання і кост токеноў або запытаў разам з функцыйнальнымі рэзултатамі. Відразувая візуабельнасць костаў запобегае неспакойным рахункам, калі процес пераходзіць з дэмавайнага режыма ў спяльныя сераўры.

Аса 3: Што будзе, якшо крок выконваецца два разы?

Этап «Што будзе» асі 3 працуе наяўней, калі яго розглядаць як вимерную плошчу. Запісаўце адна «золатая» транскрыпцыю, адин прыклад неудачы і прыметку пра вярнэнне да поперадньага стану пры расшырэнні масштаба. Зберагачце настройкі параду ўнутры коду прыемлівача. Файлы сераўнавальнага сераўсу, хранілішчы секрэтных дадзеных і флагі функций павінны знаходзіцца ў аднам месцы, куды аператары можаць адбавляць без неабходнасці чытання всей структуры. Зберагачце стан графа ў простам і типаваным формате. Вкладныя блокі маскуюць інфармацыю пра тое, який вузел запісаў які поле, і спакоююць продаж чытання пасля перерываў.

# BEFORE — looks fine in a demo, is a live incident waiting to happen
async def auto_remediate(alert: dict):
    await restart_service(alert["service"])  # what if this activity gets retried?
# AFTER — idempotent by construction
async def auto_remediate(alert: dict, idempotency_key: str):
    if await remediation_ledger.already_applied(idempotency_key):
        logger.info("remediation already applied, skipping", key=idempotency_key)
        return await remediation_ledger.get_result(idempotency_key)
    result = await restart_service(alert["service"])
    await remediation_ledger.record(idempotency_key, result)
    return result

Аса 4: Каму патрэбна чытаць рашэнне пазней, і ў якім формате?

Axis 4, які патронуюць працу на сцэне, работаюць найкраща, калі іх расследжваць як вимерную паверхню. Запісаўце адна «золатая» транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць масштабы. Дакументаваўце як шлях успеху, так і шлях вяснавання. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней. Храніце стан графаў у простам і типаваным формате. Вкладзеныя блокі маскуюць, який вузел запісаў канкрэтны поле, і спакшваюць продовжэнне роботы пасля перарываў.

# A framework-agnostic audit record — this is what actually matters
# in a postmortem, regardless of what orchestrated the steps.
@dataclass
class DecisionRecord:
    alert_id: str
    timestamp: float
    step: str
    reasoning: str        # what the LLM said, verbatim
    decision: str         # the structured outcome, not prose
    confidence: float | None
    human_override: bool

async def log_decision(record: DecisionRecord):
    await audit_store.insert(record)
    # Also emit as a structured log line — cheap insurance for when
    # the audit store itself is the thing that's down during an incident.
    logger.info("agent_decision", **asdict(record))

Axis 5: Калі саме заключаецца вашыя практычныя обмежэння ў швальнасці команды?

Этап Axis 5 What’s працюе найкраща, калі яго розглядаць як вимерную паверхню. Зафіксавце адны ідеальны прыклад роботы, адны прыклад неудачі і запіс пра відкатанне раней, чым расширваце сферу дзеяння. Волійце маленькі, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выходзіць, прычына неудачы павінна вказываць на адную адпаведальнасць, а не на заплутаны ланцюг дзеяння. Рэжым графа павінен быць простым і з адначытаемымі даннымі. Вкладзеныя блокі маскуюць інфармацыю пра тое, який вузел запісаў канкрэтны поле, і спакоююць працу пасля перерываў. Этап Axis 5 What’s працюе найкраща, калі яго розглядаць як вимерную паверхню. Зафіксавце адны ідеальны прыклад роботы, адны прыклад неудачі і запіс пра відкатанне раней, чым расширваце сферу дзеяння. Запісвайце часы выконання і косты токенав або запытаў разам з функцыйнальнымі рэзултатамі. Відразы костаў з самага пачатку запобегаюць неспакойным рахункам, калі процес пераходзіць з дэмавайнага режыму ў спяльныя сераўы.

# Week-one prototype: prove the concept fast, accept the debt knowingly.
from crewai import Agent, Task, Crew

triage_agent = Agent(role="Alert Triage", goal="Decide how to handle infra alerts")
crew = Crew(agents=[triage_agent], tasks=[Task(description="Triage: {alert}", agent=triage_agent)])
crew.kickoff(inputs={"alert": alert_payload})

Axis 6: Калькуюце вы свой час адпаведзення і бюджет костаў на кожную рашэнне?

Для стадзіі Axis 6 What’s неабяжна пазначыць вхідныя даны, адпаведальнага за крок і критэрыі завершэння пры перадзеяванні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не спрабоўваючы з’ясаваць захаваны стан. Конфігурацыю трэба залічыць паза кодам прыемлівання. Файлы сераўнавальнага сераўса, хранілішчы секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць, не чытаючы весь лянцуг задач. Прызначыць людскія празгляды для рэшэнняў, якія выкарыстоўваюць грошы або зменяюць даны праўай працы. Падключэння пад час компілявання не ўзроўнаўваецца з повнайасцю бізнес-процэсаў.

# Expensive pattern: every routing decision is its own LLM call,
# multiplied across a multi-agent conversation with several turns.
# At alert volumes (hundreds/day, sometimes bursts of thousands during
# a real incident), this is a real line item, not a rounding error.
async def route_via_llm(alert: dict) -> str:
    return await llm_call(f"How should we handle this alert? {alert}")

# Cheaper, faster, and more auditable: cheap deterministic pre-filtering
# in code, LLM reserved for genuinely ambiguous cases.
async def route_alert_efficiently(alert: dict) -> str:
    if alert["service"] in KNOWN_NOISY_SERVICES and alert["severity"] == "low":
        return "log_and_monitor"          # zero LLM calls for the common case
    if alert["signature"] in KNOWN_REMEDIATION_PLAYBOOK:
        return "auto_remediate"           # deterministic lookup, zero LLM calls
    return await llm_call(f"Novel alert, needs judgment: {alert}")  # LLM only when genuinely needed

Аднае цэлы: шлях рашэння, а не дрэва рашэння

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

Is this unit of work stateless and finishes in seconds?
  └─ YES → skip the framework entirely. Plain functions + retries. Ship it.
  └─ NO, continue.

Does it need to survive process restarts / wait on humans for hours-to-days?
  └─ YES → you need durable execution (Temporal or equivalent) as the backbone,
           regardless of what else you pick for the reasoning layer.
  └─ NO, continue.

Are the important branches safety- or compliance-critical
(money, infra changes, irreversible external actions)?
  └─ YES → LangGraph-style explicit graphs, keep LLM scoped to narrow nodes.
  └─ NO, mostly exploratory/creative → CrewAI or AutoGen are legitimate defaults.

Is this still a prototype whose findings might get thrown away?
  └─ YES → optimize for speed of iteration over long-term correctness,
           but write down when you'll revisit that tradeoff.
@activity.defn
async def classify_alert_activity(alert: dict) -> dict:
    # LangGraph-style graph runs here — bounded reasoning, deterministic routing —
    # inside an activity Temporal will retry and time-box like any other side effect.
    result = alert_triage_graph.invoke({"alert": alert, "audit_log": []})
    return {"decision": result["decision"], "confidence": result["confidence"]}

@workflow.defn
class AlertTriageWorkflow:
    def __init__(self):
        self._acked = False

    @workflow.signal
    async def acknowledge(self):
        self._acked = True

    @workflow.run
    async def run(self, alert: dict) -> dict:
        classification = await workflow.execute_activity(
            classify_alert_activity, alert,
            start_to_close_timeout=timedelta(seconds=20),
            retry_policy=workflow.RetryPolicy(maximum_attempts=3),
        )
        if classification["decision"] == "page_oncall":
            await workflow.execute_activity(page_oncall, alert, start_to_close_timeout=timedelta(seconds=10))
            await workflow.wait_condition(lambda: self._acked, timeout=timedelta(hours=1))
            if not self._acked:
                await workflow.execute_activity(escalate_to_secondary, alert, start_to_close_timeout=timedelta(seconds=10))
        elif classification["decision"] == "auto_remediate":
            await workflow.execute_activity(
                auto_remediate, alert, f"remediate-{alert['id']}",
                start_to_close_timeout=timedelta(minutes=2),
                retry_policy=workflow.RetryPolicy(maximum_attempts=2),
            )
        return {"alert_id": alert["id"], "decision": classification["decision"]}

Пашчэрстаныя памилкі, якія вы часта бачыце

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

Ці пункты ўключаюцься ў загальныя правілы.

Практычна адпаведзь

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

Чек-ліст для аператыўнай роботы

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

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

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

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

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

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

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