Голы агент: што ламаецца, калі мадэль нічога не ахоўвае
Будуем аднолькавага неабароненага агента для падарожжаў у Claude Agent SDK і LangChain Deep Agents і на трох збоях бачым, якіх элементаў абвязкі не хапае.
Крыніцы: Harness Engineering: The Naked Agent: Why Your Framework Hands You a Loop, Not a Harness — I
Кліент прасівае агента перэзаброніраваць рэйс. Мадэль звучыць впэўнена, вызов інструмента успехвае, а адпаведзь паведамляе, што все адмашчана. Аднак пазней хтось з’являе ўвагу, што дата — 32-й марца.
Самэй тады руйнуецца ілюзія. Модэлі не хацелася інтэлігэнцыі; працу не выканалаў усі сістэмны элементы, адколі жадны з іх не ведаў, калі трэба ўздроміць. Далей працюе агент без жадных дапаможных прыемоў: без пераканання даных, без памяці, без можлівасці вярнуцца да пачатку, без захоўнага ўстройства. Пагляд на яго неудачу адкрывае, дзе падчыняюцца працоўныя агенты, і паказвае, што час працы мовнай модэлі мае большое значэнне, чым сама модэль. Той жа спрошчаны агент будуецца два разы — з Claude Agent SDK і з LangChain Deep Agents — і ўсё раву неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша неякша не
Інцыдэнт 32-го марца
Уявіце агента з броніравання паўзачакоў, які прымае тэкст „32-й марц“ ад рэальнага корыстувальніка. Мадэль перадала гэты тэкст безпасоваўсіль да API з пашукама рэйсаў. Чырвоныя часы і пяцьдзесят долераў на выклікі мадэлі пазней прывелі да таго, што чалавеку давялася поясняць купаку, якога не існавало, кліенту, якому было запаведзана, што усё ужо уладзілася.
Мадэль нічога не стварыла. Яна праслае роўна тое, што яй было даўана, бо канструкцыя не мела можлівасці вярнуцца з пытаннем, чым гэта зробіць. Гэта і ўсё, што можна сказаць пра гэты случай. Інтэлігэнція выходзіць ад модэлі, а дысцыпліна — ад канструкцыі; таму, калі агент у працэсе вырабоцтва паводзіцца неналежна, прычына практычна завжды крыўціцца ў шары дысцыпліны. Замена слабейшай модэлі чы выправленне запіту не даюць кантролю, якога няма, а стварэнне кантролю пачынаецца з розумення таго, сколькі коштае ўсуненне яго. Таму выхадны пункт просты: адна кранічна чулівае аб’ект, два фрэймворкі, без канструкцыі.
Доказы таго, што час выканання мае перавагу
Даныя тэстаў падтрымліваюць гэты тверджэння. Научнікі з Прынстона заморазілі мадэль GPT-4 Turbo і зменілі толькі код інтэрфейса, які яе з’ёднаў з базай кода. Частка проблем SWE-bench, якія былі рашаны, вырасла з 3,8% да 12,47%, тыяму трохі больш, хоча вагі мадэлі засталіся незменнымі. Работа пад назвай „SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering“ была опублікована у 2024 году і прыйнята на NeurIPS таго ж года. Развіццю спрыяў дызайн інтэрфейса, а не інтэлект мадэлі.
У гэй адульгаванні ёсць старэйшая назва — „механічная сімпатыя“: стварэнне програмнага забезпечэння, якое працуе разам з апаратным забезпечэннем чыста падлогай, а не проты яго. Падход „Harness engineering“ распрастраńвае гэю ідэю на новую падлогу — мовную мадэль, якая мае вікна кантэксту і ліміты на атэнцыю. Мадэль встановлівае верхнюю межу, а „Harness“ вяршыць, насколькі стабільна сістэма можа яе дасягнуць.
Змена спосабу мышлення парадыгматызуе тое, што вы рабіце пасля неудачы:
- Раней: пасля неправільнай адпаведзі, практыкуеце корэктывы да запіту, дадаеце додатковыя засцярогі, прыменяеце большую мадэль і спадзяецеся
- Пасля: пасля неправільнай адпаведзі, выявляеце, які элемент керавання быў нехватны, і выкарыстоўваеце гэтыя правілы не ў запытэ, а пад час выканання
Запыты пераканваюць, а механізмы прымусу выкананняяць.
Цыкл пад кожным фрэймворкам
Усе фрэймворкі для агентаў базуюцца на адным пяцькроковым цыкле. Прадаеце інструменты модэлі, атрымваеце адпаведзь пра ўжыцце інструмента, выкананяеце яго, вяртаеце рэзультат і атрымваеце наступны крок. Дакументацыя Anthropic пра выкарыстоўванне інструментаў, API Responses ад OpenAI і механізм выканання агентаў ад LangChain супадаюць па сутнасці. SDK-ы з’являюцца і знікаюць, але структура застаецца незменнай.
Базовая версія выкарыстоўвае прыродны SDK-прадаўця, два інструмента для працы і не мае жадных мер безпекі:
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
TOOLS = [
{"name": "search_flights",
"description": "Search flights between two cities for a date.",
"input_schema": {"type": "object", "properties": {
"origin": {"type": "string"}, "destination": {"type": "string"},
"date": {"type": "string", "description": "YYYY-MM-DD"}},
"required": ["origin", "destination", "date"]}},
{"name": "book_flight",
"description": "Book a specific flight.",
"input_schema": {"type": "object", "properties": {
"flight_id": {"type": "string"}, "passenger_name": {"type": "string"}},
"required": ["flight_id", "passenger_name"]}},
]
def run_naked(user_msg: str) -> str:
messages = [{"role": "user", "content": user_msg}]
while True: # ① no iteration cap
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=TOOLS, messages=messages,
)
if resp.stop_reason != "tool_use":
return resp.content[0].text
call = next(b for b in resp.content if b.type == "tool_use")
result = dispatch(call.name, call.input)
# ② direct side effect, no check
messages.extend([
# ③ whole history, every turn
{"role": "assistant", "content": resp.content},
{"role": "user", "content": [{"type": "tool_result",
"tool_use_id": call.id, "content": result}]},
])
Чытайце гэты тэкст, прымоўваючы ў вачо тры прычыны неудачы:
- Цыкл
while True:не мае меры. Неэфектыўны план продовжае запускаць модэль, пакуль не будзе выкарана вся сума бюджэту, адтакоўна не лічычы колькасць ітэрацыяў. - Функцыя
dispatchперадае аргументы, заданыя модэлем, без жадных пераканальніх пераглядоў безпасна да элемента, які вызывае побачны эфект, таму значэнне, якое правільна парсуецца, але не можа існаваць, потрапляе да рэальнага API. - Усё історыя паведамленняў расшырваецца і перадаецца занова ў кожны раз, тыха падвайнуючы кантэкст, пакуль яго якасць не падые.
Няхай ніякія з аспектаў безпекі, якія вам патрэбны, не знаходзіцца ўнутрь гэтага цыклу. Яны знаходзяцца ў кодзе, які ўсё ўсё не быў напісаны. Адыяктуванне пад певную платформу таксама не заканчывае проблему. Claude Agent SDK і Deep Agents прыносяць лепшая структураваны цыкл, з належным протаколам інструментаў і адносна простым шляхам выканання, але ў сваёй базовай версіі яны не маюць ніякіх верыфікатораў, ніякай памяці, яю можна было бы выкарыстоўваць, і ніякіх механізмаў вярнення да стану пад час адмітання памылак. Цыкл — гэта тое, што прыходзіць, а рашэння пра тое, як яго адмацаваць, застаецца задачай распрацоўшчыка.
Ёсць той самы «голы» агент у кожной з платформ.
Версія Claude Agent SDK
SDK абгружае цыкл за вас. Інструменты прызначаюцца за дапамогою дэкаратара @tool, выкладзяцца як сервер MCP у межах таго ж працэса і запускаюцца праз query(). Адсутня будзь-якая контрольная функцыя: аргументы перакантролююцца толькі па схеме, нічога не запам’ятовуецца між вызывамі, і няма можлівасці вярнуцца да стану пад час адмітання памылак.
import asyncio
from claude_agent_sdk import (
query, ClaudeAgentOptions, tool,
create_sdk_mcp_server, AssistantMessage, ResultMessage,
)
@tool("search_flights", "Search flights between two cities for a date.",
{"origin": str, "destination": str, "date": str})
async def search_flights(args):
# ① no check that date exists
hits = flights_api.search(**args)
return {"content": [{"type": "text", "text": str(hits)}]}
@tool("book_flight", "Book a specific flight.",
{"flight_id": str, "passenger_name": str})
async def book_flight(args):
# ② destructive, ungated
confirmation = flights_api.book(**args)
return {"content": [{"type": "text", "text": confirmation}]}
server = create_sdk_mcp_server("travel", tools=[search_flights, book_flight])
async def main():
options = ClaudeAgentOptions(
mcp_servers={"travel": server},
allowed_tools=["mcp__travel__search_flights",
"mcp__travel__book_flight"],
)
async for msg in query(prompt="Rebook this customer for March 32nd.",
options=options):
if isinstance(msg, AssistantMessage):
for b in msg.content:
if hasattr(b, "text"):
print(b.text)
elif isinstance(msg, ResultMessage):
print("done:", msg.subtype)
# ③ no state survives this run
Выдзеляюця тры наступныя рэчы:
search_flightsпрыме ўсё, што пропанае мадэль і якае падходзіць па схеме, таму дата, якая не можа існаваць, таксама потрапляе ў корпус інструмента.book_flightўражае даныя і нічога яго не захоўвае. Дэкаратар паў’язуе яго з цыклам без жадных механізмаў апраўлення чы пераканання.- Пасля таго, як прыходзіць
ResultMessageіquery()вяртаеся, сесія завершваецца, і нічога з таго запуску не застаецца для наступнага вызову.
SDK аддаў чысты цыкл. У яго няма контролю над некорэктнымі датамі, подвойнымі платэжамі чы тым, што трэба запамятаць.
Версія Deep Agents
Deep Agents выконвае гэты цыкл інакш. Звычныя інструменты LangChain выкорыстоўваюць create_deep_agent(), які вяртае скомпіляваны граф для invoke. Разлік толькі ў тым, што няма верыфікатора, а таксама, калі не пасылаецца checkpointer, граф забывае усё, што было між вызывамі.
from langchain.tools import tool
from deepagents import create_deep_agent
@tool
def search_flights(origin: str, destination: str, date: str) -> str:
"""Search flights between two cities for a date (YYYY-MM-DD)."""
return str(flights_api.search(origin, destination, date))
# ① no date check
@tool
def book_flight(flight_id: str, passenger_name: str) -> str:
"""Book a specific flight."""
return flights_api.book(flight_id, passenger_name)
# ② ungated side effect
agent = create_deep_agent(
# ③ the loop, no controls
model="anthropic:claude-sonnet-4-6",
tools=[search_flights, book_flight],
)
result = agent.invoke({"messages": [{"role": "user",
"content": "Rebook this customer for March 32nd."}]})
print(result["messages"][-1].content)
# Ask a follow-up in a second invoke, and it starts from zero: no thread,
# no memory.
Знову тры пункты:
- Функцыя
search_flightsвыклікае API з необработаным стрэлкамdate, і нічога не паўтарае, што дата ўсамдзеяна, прычым запіт ўжо надсылаецца. book_flightвыконвае броніраванне ў той жа момент, калі мадэль яго выбірае, без жадных перапытак між прызначэнням і наследкамі.create_deep_agentвяртае граф, які запускае цыкл і ўсё, што ў яго ўключана. Без контрольнага пункта няма ні потока, ні памяці між вызывамі.
Тры спосабы, якімі «голы» агент выходзіць з ладу
Два фрэймворкі далі два чыстыя цыклы і абсолютнае браку дысцыпліны — так выглядае «голы» агент у 2026 годзе. Гэта не проста умовная мадэль. Гэта форма дзесяткаў дэманстрацыйіяў агентоў, якія ўтыкваюцца на GitHub ў кожны тыдзень. Пад рэальным навантажэнням ён выходзіць з ладу трома спосабамі, і кожны з этых выходаў адпавядае той кантролі, якую дадае правільны інструментарый.
Немагчымы вхідны даны. Калі задаецца „32-га чэрвеня“, мадэль робіць тое, што яна робіць, і запоўняе поле date стрэлкам, якая мае правільную структуру, але не мае значэння. Схема толькі паўтарае, што date — гэта стрэлка, і не можа з’ясавіць, чыісць такі дзень. Пасля чаго book_flight запускаецца пры фантамнай схеме паляцэнтавання, а мадэль анунціруе успех.
Выклікана функцыя book_flight з выдуманым ідэнтыфікатаром рэйса і іменем пасажыра, і яна вяртае „Забронювана“. Адпаведная дзеянне была выканана, і нічога ў цыкле не пыталася пераканацца, чы трэба яе выканаць.
book_flight(flight_id=”AC-PHANTOM”, passenger_name=”J. Moffatt”)
# -> “Booked.” The action fired. Nothing in the loop asked whether it should.
Няявна межа дзеяння стала прычыной адзінакоў, калі не было нічога, што бы пераканало, чы рэшэння, запропанавана модэлем, супадае з тым, што на самай працэ выйшло. Заполненне гэтага прыему ёсць задачай кантрактов і пераканальнікаў.
Занадта большы кантэкст. Простая петля перасылае цэлую історыю на кожны крок. Дзялёгі абмены пасля перэбукінгу, заполнены рэзультатамі пошуку, тэкстам правіл і праказамі, можа складацца з пяцідзесят тысяч токэнаў, і нічога яго не скорачае. Адбываецца тое, што літэратура называе разложэннем кантэксту, і агент прыпіняе выкананне крокаў, якія ён раней чыста выканаў у тым жа запуску. Гэта не вызывае ніякіх адзінакоў. Агент проста паступова пагіршаецца.
У гэтым случае нехватала рабочай памяці і сборкі кантэксту, якія належаць да катэгорыі памяці і стойкага стану, а таксама да сборкі кантэксту.
Ісправленыя памылкі. Інструмент вяртаецца з пакетам памылак, а мадэль, частае, чым хто-небудзь могаў спадзявацца, пішае ввічлівы кантакт, які прыховвае проблему. Адна з аналізаў інцыдэтаў у працэсе вырабоцтва опісвала такія інструменты як абяцкіх брэхуноў: база дадзенаў, якая вяртае нуль рэядоў, спрыймаецца як фактычная адпаведзь, а не як непаводарэнне. Выбір правільнага інструмента ніколі не быў складным. Складнае — гэта розбіраць, які інструмент справды працуе, а які толькі пераканліва падражнае працэю. Тут не спостыралася жадная зворачная лінія, а ўстановленае яе ўскладнюе адзірванне і выяўленне змян.
Спрыяйце ўважліваму падходу да гэтага як да дыягностычнага шаблона, а не як да спісу прыганяючых момантав. У всіх трох прыкладах мадэль рабіла тое, што робяць мадэлі, а апошнія сістэмы не змоглі гэта зафіксаваць. Ні в аднам з прыкладаў не было модэлі, якая бы не была дастатнька розумной.
Функцыональная лінія для інжынерыі кантролю
Чатырохэтапны ўзроў павтараецца падчас стварэння будзь-якага кантролю:
- Увайсці функцыю harness і абставіну неяксамоці, якую яна кантролюе.
- Рэалізаваць яе ў обох фрэймворках як спецыяльны фрагмент коду.
- Вызваць абставіну неяксамоці, каб падтвердзіць, што кантроль выдэржвае навантажэння.
- Прабачыць, як рэагуе harness.
У вышэўшых раздзелах гэты ўзроў быў выкананы тры разы без яго пазначэння: выявіць «голы» шов, вызваць абставіну неяксамоці, якую ён спрычыняе, і пазначыць кантроль, на які ён натыкаецца. Пасля чаго швы закрываюцца по аднам.
Спробуйце самі
Эту абставіну можна пачуць, не чытаючы далей. У рэдагары разы ў разы быстрыя эксперыменты яе паказваюць:
- Направіць «голага» агента ў будзь-ям з фрэймворкаў на інструкцыю па перэзаброніванні кліента на 32-е марца і пазірэць, як немагчымая дата прабрацуецца ў функцыю
book_flight - Обмежыце кольцо паўторэння пяцьма ітэрацыямі. эта першая рэальна напісаная прыемка керавання, і яна належыць да часу выканання, а не да запыту
- Нехай
book_flightвяртае{"is_error": true, ...}і пераканаецца, чы рэштытны аптавіт належыць да нядачы чы яе ігнаруе - Перад пачаткам выправьце адна версію
claude-agent-sdkі адну версіюdeepagentsчыlangchain, адколі обе API зменяюцца між версіямі, і чытайце назвы модэляў (claude-sonnet-4-6,anthropic:claude-sonnet-4-6) як заменнікі для таго, што ў даны момент ёсць актуальным
Кожная платформа з’являецца з адкоўраным, добра створаным цыклам, але жадна з іх не з’являецца з інструментамі для керування. Гэта ўласна якасць такой сітуацыі, а не дзефект інструментаў; самэль тут і заключаецца справжняя праца. Claude Code, OpenCode, Codex і Deep Agents усе адно ўважаюцца інструментамі для керування агентамі, і распрацоўшчык, які мае труднасці з іх вікорыстаннем, не адналічыцца ад таго, хто досягае надзеяных рэзультатаў, ствараючы механізмы керування, якіх не хапае цыклу. Мадэль вялікі час была простай часткай. Надзеянасць ствараецца за дапамою дысцыпліны, пабудаванай навакол яе, па меры адмов, а першым елементам яе ёсць межа дзеяння — першыя правілы, якія дазволяюць выйсці за межы запіту і працаваць пад час выканання.