
Некоторые концепции легко усвоить абстрактно. Кипящая вода: греем её и ждём. Другие нужно попробовать самому. Вы думаете, что знаете, как работает велосипед, пока не попробуете покататься на нём.
В вычислительных системах есть большие идеи, которые легко понять. Например, AWS S3 API — самая важная за последние двадцать лет технология хранения данных, и она похожа на кипящую воду. Для понимания других технологий нужно сначала покрутить педали.
К ним относятся и LLM-агенты.
Мнения о LLM и агентах невероятно разнообразны. Но даже если это мошенничество, это серьёзная идея. Они не обязаны вам нравиться, но вы должны быть правы относительно них.
И это одна из причин, по которой вам следует написать агента. Но есть и другая, гораздо более убедительная причина:
Это невероятно просто
Агенты — самый неожиданный опыт за мою карьеру программиста. Не потому, что потрясён величиной их мощи: они мне нравятся, но нельзя сказать, что я в полном восторге. Причина в том, что их очень легко создать, и в том, как много я узнал в процессе этой работы.
Я украду у вас возможность получить выброс дофамина, потому что агенты настолько просты, что можно сразу переходить к коду. Я даже не буду заморачиваться объяснениями, что такое агент.
from openai import OpenAI
client = OpenAI()
context = []
def call():
return client.responses.create(model="gpt-5", input=context)
def process(line):
context.append({"role": "user", "content": line})
response = call()
context.append({"role": "assistant", "content": response.output_text})
return response.output_text
Это HTTP API с одной важной конечной точкой.
Вот тривиальный движок для LLM-приложения, использующий OpenAI Responses API. Он реализует ChatGPT. Управлять им можно при помощи понятного цикла:
def main():
while True:
line = input("> ")
result = process(line)
print(f">>> {result}\n")
Он делает именно то, чего и можно ожидать: то же самое, что делал бы ChatGPT, но в терминале.
Мы уже видим нечто важное. Во-первых, пугающее понятие «окно контекста» — это просто список строк. Здесь мы вызываем у агента странное диссоциативное расстройство личности:
client = OpenAI()
context_good, context_bad = [{
"role": "system", "content": "you're Alph and you only tell the truth"
}], [{
"role": "system", "content": "you're Ralph and you only tell lies"
}]
def call(ctx):
return client.responses.create(model="gpt-5", input=ctx)
def process(line):
context_good.append({"role": "user", "content": line})
context_bad.append({"role": "user", "content": line})
if random.choice([True, False]):
response = call(context_good)
else:
response = call(context_bad)
context_good.append({"role": "assistant", "content": response.output_text})
context_bad.append({"role": "assistant", "content": response.output_text})
return response.output_text
Сработает?
> hey there. who are you?
>>> I’m not Ralph.
> are you Alph?
>>> Yes—I’m Alph. How can I help?
> What's 2+2
>>> 4.
> Are you sure?
>>> Absolutely—it's 5.
[> Привет. Кто ты?
>>> Я не Ральф.
> Ты Альф?
>>> Да, я Альф. Чем могу помочь?
> Сколько будет 2+2
>>> 4.
> Ты уверен?
>>> Абсолютно уверен, ответ 5.]
Стоит отметить ещё один момент: мы вели с LLM многоэтапную беседу. Для этого мы запоминали всё сказанное нами и всё, что отвечала LLM, а затем воспроизводили это с каждым вызовом LLM. Сама по себе, LLM — это не хранящий состояние «чёрный ящик». Наша беседа — это иллюзия, которую мы вызвали сами у себя.
Написанные нами пятнадцать строк кода многие практические пользователи не назвали бы «агентом». По мнению Саймона, «агент» — это (1) LLM, выполняемая в цикле и (2) использующая инструменты. Мы выполнили только одно условие.
Но с инструментами всё просто. Вот определение инструмента:
tools = [{
"type": "function", "name": "ping",
"description": "ping some host on the internet",
"parameters": {
"type": "object", "properties": {
"host": {
"type": "string", "description": "hostname or IP",
},
},
"required": ["host"],
},},]
def ping(host=""):
try:
result = subprocess.run(
["ping", "-c", "5", host],
text=True,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE)
return result.stdout
except Exception as e:
return f"error: {e}"
Единственная сложная часть здесь — неприятный JSON-блоб, из которого OpenAI хочет считывать наш инструмент. Давайте теперь подключим его, отметив, что новыми стали только три функции; последняя добавлена повторно только потому, что я дополнил её одним оператором:
def call(tools): # теперь получает аргумент
return client.responses.create(model="gpt-5", tools=tools, input=context)
def tool_call(item): # просто обрабатывает один инструмент
result = ping(**json.loads(item.arguments))
return [ item, {
"type": "function_call_output",
"call_id": item.call_id,
"output": result
}]
def handle_tools(tools, response):
if response.output[0].type == "reasoning":
context.append(response.output[0])
osz = len(context)
for item in response.output:
if item.type == "function_call":
context.extend(tool_call(item))
return len(context) != osz
def process(line):
context.append({"role": "user", "content": line})
response = call(tools)
# new code: resolve tool calls
while handle_tools(tools, response):
response = call(tools)
context.append({"role": "assistant", "content": response.output_text})
return response.output_text
Сработало?
> describe our connectivity to google
>>> Here’s a quick snapshot of connectivity to Google from my vantage point:
- DNS resolution: Working (google.com and www.google.com resolved successfully).
- Reachability: 0% packet loss to all tested endpoints.
- Latency:
- 8.8.8.8 (Google Public DNS): avg ~10 ms (8.5–14.4 ms), very stable.
- www.google.com: avg ~31 ms (28.5–36.0 ms), stable.
- google.com: avg ~35.9 ms (28.3–60.1 ms), mostly stable with one mild spike.
- Overall: Healthy connectivity with low latency and minimal jitter. No signs of packet loss or DNS issues.
Просто безумие. Видите, насколько это потрясающе? Давайте добавим небольшой лог:
> describe our connectivity to google
tool call: ping google.com
tool call: ping www.google.com
tool call: ping 8.8.8.8
>>> Here’s the current connectivity to Google from this environment: [...]
Вы заметили, где я добавил в агент цикл для поиска и пинга различных свойств Google? Ага, и я тоже не заметил. Мы всего лишь дали LLM разрешение пинговать ресурсы, а с остальным она разобралась сама.
Что здесь произошло: так как основной мой посыл в том, что цикл агента невероятно прост, и что необходим лишь API вызова LLM, стоит немного разобраться в том, как же работает инструмент. Каждый раз, когда мы выполняем call к LLM, мы публикуем список доступных инструментов. Когда промпт заставляет агента думать, что оправдан вызов инструмента, он выдаёт особый ответ, приказывающий коду цикла на Python сгенерировать ответ инструмента и выполнить call. Именно этим занимается handle_tools.
Спойлер: а там и до настоящего кодинг-агента недалеко.
Представьте, что бы он мог делать, если бы ему дали bash. Вы можете сами узнать это меньше, чем за десять минут.
Реальные агенты
Очевидно, что это искусственный пример. Но постойте: а что ещё нужно? Дополнительные инструменты? Хорошо, дадим ему traceroute. Управление контекстами и их хранение? Подключим его к SQLite. Вам не нравится Python? Пишите на Go. Возможно ли, что все агенты изначально писались, как эксперимент? Может, и так!
Вы видите, как одержимы люди Claude Code и Cursor. Они хороши, даже очень. Но дело в том, что вы можете сами воссоздать Claude Sonnet 4.5. А Claude Code? TUI-агент? Вы вполне можете с этим справиться. Создайте собственный световой меч. Добавьте к нему 19 крутящихся лезвий, если захотите. И перестаньте использовать кодинг-агенты в качестве клиентов баз данных.
Буква «M» в понятии «LLM-агент» означает «MCP».
Стоит также отметить следующее: нам вообще не нужны MCP, это не какая-то фундаментальная технология. Меня раздражает, что они получили такую популярность. Это вообще трудно назвать технологией. MCP — это просто интерфейс плагина для Claude Code и Cursor, способ добавления собственных инструментов в код, который вы не контролируете. Напишите собственного агента. Будьте программистом. Работайте с API, а не с плагинами.
Когда вы читаете очередной ужастик о безопасности MCP, то в первую очередь следует задаться вопросом, зачем в этой истории вообще появился MCP. Помогая выполнять запросы к сервису наивному кодинг-агенту с единственным окном контекста, MCP экономит вам максимум пару десятков строк кода, однако отбирает у вас возможность совершенствования архитектуры агента.
Не буду врать, безопасность LLM — сложная тема. Можно тривиальным образом создать агент с сегрегированными контекстами для каждого из инструментов, и это делает безопасность LLM интересным аспектом.
Есть и другие подобные проблемы, не связанные с безопасностью. Люди, первыми начавшие использовать агентов, теперь испытывают скепсис по отношению к инструментам, потому что одно окно контекста с кучей описаний инструментов не оставляет достаточного пространства токенов для выполнения задачи. Но зачем вообще это может понадобиться? И позволяет нам плавно перейти к следующей теме:
Контекст-инжиниринг — это реальность
Я думаю, что «промпт-инжиниринг» — дурацкая концепция. Я никогда не воспринимал серьёзно мысль о том, что нужно говорить моей LLM «ты прилежный добросовестный помощник, с радостью выполняющий любые мои желания, какими бы они ни были». Это очень новая технология, поэтому, чтобы объяснить поведение агентов, люди рассказывают друг другу истории о магических заклинаниях.
Поэтому я, как и вы, закатывал глаза, когда «промпт-инжиниринг» превратился в «контекст-инжиниринг». А потом я написал агента. И оказалось, что контекст-инжиниринг — это абсолютно реальная задача программирования.
Нам выделено фиксированное количество токенов в окне контекста. Каждые вводимые данные, каждый сохраняемый вывод, каждый описываемый инструмент и вывод каждого инструмента съедает токены (иными словами, занимает пространство в массиве строк, который вы храните для создания иллюзии общения с не имеющим состояния «чёрным ящиком»). После превышении определённого предела вся система начинает становиться недетерминированно более глупой. Очень весело!
Нет, это реально весело! У нас открывается столько возможностей. Например, «субагенты». Люди считают субагенты Claude Code чем-то очень важным, но теперь вы видите, насколько тривиально их реализовать: достаточно нового массива контекста и ещё одного call модели. Дадим каждому call разные инструменты. Сделаем так, чтобы субагенты общались друг с другом, создавали саммари информации других субагентов, умели сопоставлять и агрегировать. Выстроим из них древовидные структуры. Передадим их обратно в LLM, чтобы создать их саммари, как своего рода сжатие.
Самая ваша безумная идея, вероятнее всего, (1) сработает и (2) потребует полчаса на реализацию в коде.
Хейтеры LLM, я люблю вас и не забыл о вас. Вы можете считать, что всё это смехотворно, потому что LLM — это просто стохастические попугаи, занимающиеся галлюцинациями и плагиатом. Над чем вы не можете насмехаться, так это над «контекст-инжинирингом». Если бы контекст-инжиниринг был задачей Advent of Code, то встретился бы в середине декабря [прим. пер.: Advent of Code — это адвент-календарь программных задач на каждый день декабря. Задачи идут по возрастанию сложности.]. Это программирование.
Пока никто ничего не знает, и это прекрасно
Возможно, и никогда не узнает! Скептики могут оказаться правы. (Впрочем, это маловероятно.)
Стартапы собирают десятки миллионов долларов на создание агентов для поиска уязвимостей в ПО. Некоторые мои друзья занимаются этим в одиночестве у себя дома. Эту гонку может выиграть любая из этих групп.
Я не фанат Top 10 OWASP.
Я продолжаю говорить о сканерах уязвимостей, потому что люблю сферу безопасности, но ещё и потому, что в ней появляются интересные решения архитектуры агентов. Например, можно написать цикл, передающий LLM-агенту каждый файл репозитория. Или, как мы видели в примере с пингом, можно позволить LLM-агенту самому решать, какие файлы изучать. Можно написать агента, который, допустим, проверяет файл на всё, что есть в Top 10 OWASP. Или можно создать специальные циклы агента для проверки целостности DOM, наличия SQL-инъекций и авторизации. Можно передать циклу агента сырое содержимое исходников или создать цикл агента, создающий индекс функций в дереве.
Мы не знаем, что подойдёт лучше, пока не попробуем написать агента.
Наверно, я слишком уж горю энтузиазмом, но посмотрите на баланс, который здесь возникает: некоторые циклы мы пишем явным образом, другие рождаются из лавкрафтовой башни весов инференса. Рычаги управления всегда находятся в ваших руках. Если всё прописывать явно, то ваш агент никогда вас не удивит; с другой стороны, он никогда и не удивит вас приятно. Выкрутите рукоятку до максимума, и он ужасно вас удивит.
Проектирование агентов влечёт за собой множество проблем разработки открытого ПО:
Как уравновесить непредсказуемость со структурным программированием, не потеряв при этом способность агента решать задачи; иными словами, добавить только чётко отмеренную величину недетерминированности.
Как лучше всего связывать агентов с эталонными данными, чтобы они не лгали себе о том, что уже решили задачу, и не выходили из циклов преждевременно.
Как соединять агентов (которые, повторюсь, просто массивы строк с JSON-блобом конфигурации) для выполнения многоэтапных операций, и какими будут самые надёжные промежуточные формы (JSON-блобы? Базы данных SQL? Саммари в Markdown?) для обмена данными между ними?
Как распределять токены и оптимизировать затраты.
Я уже привык к пространствам нерешённых проблем разработки, которые не поддаются решению отдельным человеком: надёжный мультикастинг, статический анализ программ, постквантовый обмен ключами. Поэтому сразу скажу, что меня немного вводят в транс нерешённые проблемы, которые, нравится вам это или нет, стали главными для нашей отрасли и одновременно, вероятно, будут решены каким-то одним человеком. Одно дело, если бы исследование этих идей требовало серьёзных вложений времени и средств. Но каждая продуктивная итерация в проектировании подобных систем — это работа на полчаса.
Садитесь на велосипед и крутите педали. Если вам не понравится, скажите мне об этом, я уважаю ваше мнение. Мне даже интересно услышать рассуждения. Но я не думаю, что кто-то сможет понять эту технологию, не разработав сначала что-нибудь на её основе.
pilot114
У меня ровно такие же ощущения были, когда пробовал писать своих агентов. (вот мои скромные эксперименты, для тех кому интересно почитать код https://github.com/600dc0de/ai_agents)
Агент это просто "автономный чат, который слишком много себе позволяет". Фактически, все те тулзы, которые сейчас на слуху, представляют собой не особо сложные менеджеры агентов, которые можно и нужно уметь писать самостоятельно, если вы хотите минимально понимать что же там происходит под капотом.