Почему все делают агентов неправильно (и как исправить это с помощью Org-mode)
Откройте любой гайд по созданию AI-агентов. Что вы увидите? Тысячи строк кода на Python, запутанные классы, сложные абстракции. Авторы гордятся тем, что их код "масштабируемый" и "производственный". А на деле получается монстр, который ломается при первом же изменении требований.
Проблема в подходе. Вы пишете код, который описывает поведение агента. А нужно писать документацию, которая становится кодом. Разница колоссальная.
К февралю 2026 года большинство фреймворков для агентов превратились в слона: тяжелые, медленные, неповоротливые. LangChain? CrewAI? Они решают проблемы, которых у вас нет. А реальные проблемы - отладка, понимание потока данных, изменение логики - остаются.
Org-mode - это редактор в Emacs для литературного программирования. Вы пишете текст с кодом внутри. Текст объясняет, код выполняется. Звучит просто? Это революционно.
Литературное программирование: когда документация становится исполняемой
Дональд Кнут придумал литературное программирование в 1984 году. Идея: программа должна читаться как книга. Сначала объяснение, потом код. Сначала "почему", потом "как".
За 40 лет идея не прижилась. Пока не появились LLM.
Современные модели (я использую GPT-4.5-Turbo на 09.02.2026) отлично понимают структурированный текст. Они могут читать вашу документацию и выполнять инструкции. Org-mode превращает эту возможность в суперсилу.
Начинаем с простого: агент для медицинских вопросов
Возьмем MedMCQA - датасет с медицинскими вопросами и вариантами ответов. Наша задача: создать агента, который анализирует вопрос, ищет информацию, выбирает ответ.
Типичный подход: написать класс MedicalAgent с методами analyze, research, decide. Мой подход: написать Org-файл.
1 Создаем базовую структуру агента
Откройте новый файл medical-agent.org. Начните с заголовков:
* Медицинский агент для MedMCQA
** Конфигурация
** Вопросы и ответы
** Логика агента
** Результаты
В разделе Конфигурация определите настройки:
#+begin_src python :exports none :session main
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.getenv('OPENAI_API_KEY'),
base_url=os.getenv('OPENAI_BASE_URL', 'https://api.openai.com/v1')
)
MODEL = "gpt-4.5-turbo" # Актуально на 09.02.2026
TEMPERATURE = 0.1
MAX_TOKENS = 2000
#+end_src
Не храните API-ключи в коде. Используйте .env файл. В 2026 году утечки ключей все еще самая частая причина проблем.
2 Линейная цепочка: простейший агент
Сначала сделаем агента, который работает пошагово:
#+begin_src python :exports none :session main
def linear_agent(question, options):
"""Простейший агент: вопрос -> анализ -> ответ"""
# Шаг 1: Анализ вопроса
analysis_prompt = f"""
Проанализируй медицинский вопрос:
{question}
Определи:
1. Тему вопроса (кардиология, неврология и т.д.)
2. Ключевые термины
3. Что именно спрашивается
"""
analysis = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": analysis_prompt}],
temperature=TEMPERATURE,
max_tokens=MAX_TOKENS
).choices[0].message.content
# Шаг 2: Выбор ответа
decision_prompt = f"""
На основе анализа выбери правильный ответ:
Анализ: {analysis}
Вопрос: {question}
Варианты:
{chr(10).join([f'{i+1}. {opt}' for i, opt in enumerate(options)])}
Верни только номер правильного варианта (1, 2, 3 или 4).
"""
decision = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": decision_prompt}],
temperature=TEMPERATURE,
max_tokens=50
).choices[0].message.content
return {
"analysis": analysis,
"decision": decision,
"question": question
}
#+end_src
Проверим работу:
#+begin_src python :exports results :session main
# Тестовый вопрос из MedMCQA
test_question = "У пациента с хронической болезнью почек отмечается гиперкалиемия. Какой препарат противопоказан?"
test_options = [
"Фуросемид",
"Спиронолактон",
"Гидрохлоротиазид",
"Амилорид"
]
result = linear_agent(test_question, test_options)
print(f"Анализ: {result['analysis'][:200]}...")
print(f"Ответ: {result['decision']}")
#+end_src
Это работает. Но есть проблема: что если анализ недостаточен? Нужна проверка. Что если нужно уточнить вопрос? Нужна обратная связь.
Линейная цепочка ломается при сложных задачах. Как в статье "Как победить контекстный блот и 'зону тупости' в агентах" - простые цепочки не справляются со сложностью.
Переходим к графовому управлению
Графовое управление - это когда агент не просто идет от A к B к C. Он может вернуться, выбрать другой путь, параллелить задачи.
В Org-mode это реализуется через заголовки и свойства. Каждый заголовок - узел графа. Свойства - условия перехода.
3 Создаем граф агента
Определим узлы нашего графа:
* Граф медицинского агента
:PROPERTIES:
:graph_start: yes
:END:
** Анализ вопроса
:PROPERTIES:
:next_nodes: Проверка_достаточности, Поиск_доп_инфо
:condition: always
:END:
** Проверка достаточности
:PROPERTIES:
:next_nodes: Выбор_ответа, Уточняющий_вопрос
:condition: confidence > 0.7
:END:
** Поиск дополнительной информации
:PROPERTIES:
:next_nodes: Проверка_достаточности
:condition: always
:END>
** Уточняющий вопрос
:PROPERTIES:
:next_nodes: Анализ_ответа
:condition: always
:END>
** Выбор ответа
:PROPERTIES:
:next_nodes: Конец
:condition: always
:END>
** Конец
:PROPERTIES:
:graph_end: yes
:END:
Теперь реализуем исполнение графа:
#+begin_src python :exports none :session main
class GraphNode:
def __init__(self, name, properties):
self.name = name
self.properties = properties
self.next_nodes = properties.get('next_nodes', '').split(',')
self.condition = properties.get('condition', 'always')
def should_execute(self, context):
"""Проверяет условие выполнения узла"""
if self.condition == 'always':
return True
# Пример: проверка уверенности
if 'confidence' in self.condition:
required_conf = float(self.condition.split('>')[1].strip())
return context.get('confidence', 0) > required_conf
return True
def execute_graph(question, options):
"""Исполняет граф агента"""
# Загружаем граф из Org-файла
# В реальности парсим файл, здесь упрощенный пример
nodes = {
'Анализ_вопроса': GraphNode('Анализ_вопроса', {
'next_nodes': 'Проверка_достаточности,Поиск_доп_инфо',
'condition': 'always'
}),
'Проверка_достаточности': GraphNode('Проверка_достаточности', {
'next_nodes': 'Выбор_ответа,Уточняющий_вопрос',
'condition': 'confidence > 0.7'
}),
'Выбор_ответа': GraphNode('Выбор_ответа', {
'next_nodes': 'Конец',
'condition': 'always'
})
}
context = {'question': question, 'options': options, 'confidence': 0}
current_node = 'Анализ_вопроса'
execution_path = []
while current_node != 'Конец':
node = nodes[current_node]
execution_path.append(current_node)
if node.should_execute(context):
# Выполняем логику узла
if current_node == 'Анализ_вопроса':
context = execute_analysis(context)
elif current_node == 'Проверка_достаточности':
context = execute_sufficiency_check(context)
elif current_node == 'Выбор_ответа':
context = execute_decision(context)
# Выбираем следующий узел
if node.next_nodes:
# Здесь может быть сложная логика выбора
# Для простоты берем первый
current_node = node.next_nodes[0]
else:
# Условие не выполнено, идем по альтернативному пути
if len(node.next_nodes) > 1:
current_node = node.next_nodes[1]
return {
'context': context,
'execution_path': execution_path,
'final_answer': context.get('answer')
}
#+end_src
Преимущество графа: визуальная понятность. Откройте Org-файл, сразу видно все возможные пути. Хотите изменить логику? Просто поменяйте свойства узла.
Структурированный вывод: заставляем LLM возвращать данные, а не текст
Самая большая проблема с LLM - неструктурированный вывод. Модель возвращает текст, который нужно парсить. Ошибки парсинга ломают всю систему.
Решение: заставляем модель возвращать JSON с четкой схемой. В 2026 году все современные модели (GPT-4.5, Claude 3.7, Gemini 2.0) отлично работают с JSON.
#+begin_src python :exports none :session main
def structured_analysis(question):
"""Анализ вопроса со структурированным выводом"""
prompt = f"""
Проанализируй медицинский вопрос и верни результат в JSON.
Вопрос: {question}
Верни JSON со следующей структурой:
{{
"topic": "основная тема",
"subtopics": ["подтема1", "подтема2"],
"key_terms": ["термин1", "термин2"],
"question_type": "диагностика/лечение/этиология",
"complexity": 1-5,
"confidence": 0.0-1.0
}}
Только JSON, без дополнительного текста.
"""
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=TEMPERATURE,
response_format={"type": "json_object"} # Критически важно!
)
import json
result = json.loads(response.choices[0].message.content)
return result
#+end_src
Параметр response_format={"type": "json_object"} появился в OpenAI API в 2024 году. В 2026 это стандарт. Без него модель может вернуть текст перед JSON или после, что сломает парсинг.
Интеграция с внешними инструментами: когда агенту нужны "руки"
Чистые LLM знают много, но не все. Иногда нужно:
- Поискать в медицинской базе данных
- Посчитать дозировку препарата
- Проверить взаимодействия лекарств
В Org-mode каждый инструмент - отдельный блок кода. Агент решает, какой инструмент вызвать.
#+begin_src python :exports none :session main
# Медицинская база знаний (упрощенная)
MEDICAL_KB = {
"спиронолактон": {
"category": "калийсберегающий диуретик",
"contraindications": ["гиперкалиемия", "болезнь Аддисона"],
"mechanism": "антагонист альдостерона"
},
"фуросемид": {
"category": "петлевой диуретик",
"contraindications": ["анафилаксия к сульфонамидам"],
"mechanism": "ингибитор Na-K-2Cl котранспортера"
}
}
def search_medical_kb(term):
"""Поиск в медицинской базе знаний"""
return MEDICAL_KB.get(term.lower(), {})
def calculate_dosage(weight_kg, drug, indication):
"""Расчет дозировки препарата"""
# Упрощенный расчет
dosages = {
"фуросемид": {"отёк": 0.5, "гипертензия": 0.25},
"спиронолактон": {"отёк": 1.0, "гипертензия": 0.5}
}
base_dose = dosages.get(drug, {}).get(indication, 0)
return base_dose * weight_kg
#+end_src
Теперь агент может использовать эти инструменты:
#+begin_src python :exports none :session main
def agent_with_tools(question):
"""Агент с доступом к инструментам"""
# Анализ вопроса
analysis = structured_analysis(question)
# Если в вопросе есть препараты - ищем в БЗ
tools_used = []
for term in analysis['key_terms']:
if term in MEDICAL_KB:
info = search_medical_kb(term)
tools_used.append({"tool": "medical_kb", "term": term, "result": info})
# Если есть дозировка - рассчитываем
if 'дозировка' in question.lower() or 'доза' in question.lower():
# Здесь был бы более сложный парсинг
# Для примера фиктивные значения
dosage = calculate_dosage(70, 'фуросемид', 'отёк')
tools_used.append({"tool": "dosage_calculator", "result": dosage})
return {
"analysis": analysis,
"tools_used": tools_used,
"has_sufficient_data": len(tools_used) > 0
}
#+end_src
Этот подход похож на архитектуру Plan-Code-Execute, но проще. Каждый инструмент - независимый блок. Легко добавлять новые, легко тестировать.
Параллельное выполнение: когда один агент - это мало
Некоторые задачи можно распараллелить:
- Один агент ищет в PubMed
- Второй проверяет клинические рекомендации
- Третий анализирует аналогичные случаи
В Org-mode это делается через таблицы и параллельное выполнение блоков:
* Параллельные агенты
#+name: parallel-tasks
| Задача | Агент | Входные данные |
|----------------------+------------+----------------|
| Поиск исследований | research | вопрос |
| Проверка руководств | guidelines | тема |
| Анализ случаев | cases | ключевые_термины |
#+begin_src python :var tasks=parallel-tasks :exports none
import concurrent.futures
def run_parallel_agents(tasks):
results = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
future_to_agent = {}
for task in tasks:
agent_name = task['Агент']
input_data = task['Входные данные']
if agent_name == 'research':
future = executor.submit(search_research, input_data)
elif agent_name == 'guidelines':
future = executor.submit(search_guidelines, input_data)
elif agent_name == 'cases':
future = executor.submit(search_cases, input_data)
future_to_agent[future] = agent_name
for future in concurrent.futures.as_completed(future_to_agent):
agent_name = future_to_agent[future]
try:
results[agent_name] = future.result()
except Exception as e:
results[agent_name] = {'error': str(e)}
return results
#+end_src
Подробнее о параллельных агентах читайте в статье про параллельное выполнение coding-агентов.
Ошибки, которые все совершают (и как их избежать)
| Ошибка | Почему происходит | Как исправить |
|---|---|---|
| Агент зацикливается | Нет проверки на максимальное количество шагов | Добавить счетчик итераций в контекст графа |
| LLM возвращает не JSON | Забыли response_format или модель "творческая" | Использовать response_format и парсить с try/except |
| Контекст переполняется | Каждый шаг добавляет данные, ничего не удаляет | Реализовать стратегию summarization или окно контекста |
| Инструменты не вызываются | Агент не понимает, когда нужен инструмент | Явно указывать в промпте условия вызова инструментов |
Собираем все вместе: полная система на одном листе
Красота Org-mode в том, что вся система помещается в один файл:
* Медицинская агентная система
:PROPERTIES:
:author: Ваше имя
:date: 2026-02-09
:version: 1.0
:END:
** Конфигурация
*** Настройки API
#+begin_src python ...
*** Модели и параметры
#+begin_src python ...
** Граф выполнения
*** Узлы и связи
*** Условия переходов
** Инструменты
*** База знаний
*** Калькуляторы
*** Поисковые системы
** Агенты
*** Основной аналитик
*** Специализированные агенты
*** Координатор
** Тесты
*** Примеры вопросов
*** Ожидаемые ответы
*** Фактические результаты
** Мониторинг
*** Логи выполнения
*** Метрики качества
*** Визуализация графов
Откройте этот файл через месяц. Вы сразу поймете, как работает система. Что изменилось? Какие узлы чаще всего выполняются? Где ошибки?
Попробуйте сделать то же самое с Python-файлами. Удачи.
Что дальше? Эволюция вместо революции
Вы построили агентную систему. Она работает. Что делать теперь?
Не переписывайте с нуля. Используйте эволюционный подход:
- Добавляйте метрики качества в каждый узел графа
- Записывайте, какие пути чаще приводят к правильным ответам
- Экспериментируйте с разными моделями для разных узлов
- Автоматизируйте тестирование на новых данных
Через месяц у вас будет не просто агент. У вас будет система, которая эволюционирует. Она подскажет, где слабые места. Где нужно добавить инструменты. Где упростить логику.
Это и есть будущее агентных систем. Не монолитные фреймворки. Не тысячи строк кода. А живые, документированные, эволюционирующие системы в одном Org-файле.
Начните с простого линейного агента. Добавьте один узел графа. Потом еще один. Через неделю у вас будет система, которая решает реальные задачи. Через месяц - система, которая учится на своих ошибках.
И все это без единого класса AgentFactoryManagerCoordinator.