- BrainTools - https://www.braintools.ru -
Давайте попробую пояснить. Толстый харнесс окружает модель программным контуром и берёт на себя большую часть работы за кулисами: маршрутизацию запроса, сбор контекста, проверку разрешений, выполнение сценария, долговременное состояние и верификацию результата.
В отличие от толстого, в тонком варианте контур может в основном передавать сообщения и предоставлять инструменты, а дальше модель сама решает, как ей быть. В толстом приложение дополнительно определяет, какие операции поддерживаются, как они исполняются и где должны остановиться для проверки или подтверждения. А сама модель нужна в первую очередь для понимания человеческого текста и выбора соответствующего пути решения, но не становится единственным источником истины. Часть решений принимает харнесс: он получает состояние продукта через код и применяет правила, которые нельзя надёжно обеспечить одним текстовым инструктажем. А там, где не решает сам, подсказывает модели, куда двигаться дальше.
Программный контур толстого харнесса позволяет явно задать ограничения, проверить их тестами и отделить предложение модели от реального изменения данных. Чтобы не быть голословным, давайте рассмотрим конкретный вариант: модель распознаёт намерение и заполняет параметры, а приложение выбирает зарегистрированную операцию или готовый workflow.
Когда пользователь просто отправляет сообщение внутри харнесса и ждет ответ, то его сообщение обрабатывается по следующей программе.
Пользователь → нормализация запроса
→ event/exact/semantic routing
→ intent и параметры
→ context requirements
→ capability
→ workflow
→ policy и approval
→ tool
→ verifier и audit
Пользователь → нормализация запроса
Что происходит: Сырой ввод человека (текст, голос или нажатие кнопки) очищается и подготавливается к анализу.
Суть: Исправляются опечатки, удаляется лишний «шум» (приветствия, смайлики, если они мешают), текст приводится к единому регистру. Если это голос — он превращается в текст (Speech-to-Text).
2. Event / Exact / Semantic routing (Маршрутизация)
Что происходит: Система решает, какому «мозговому центру» отдать этот запрос. Используется три уровня логики:
Event (Событийный): Реакция [1] на системные триггеры (например, пользователь нажал кнопку «Выход»).
Exact (Точный): Поиск по жестким правилам или регулярным выражениям (например, если написано /help, сразу открывается справка).
Semantic (Семантический): Анализ смысла с помощью LLM, если пользователь пишет своими словами (например: «Слушай, переведи сотку Сане»).
3. Intent (Намерение) и параметры
Что происходит: Из понятого смысла извлекается цель (Intent) и переменные (Parameters / Entities) для её выполнения.
Пример:
Intent: send_money (перевести деньги).
Parameters: amount = 100, recipient = "Саня".
4. Context requirements (Требования к контексту)
Что происходит: Система определяет, каких данных ей не хватает для выполнения этого намерения, и запрашивает их из памяти [2] сессии или баз данных.
Пример: Чтобы перевести деньги Сане, агенту нужно узнать: Кто такой Саня? (его ID или номер телефона) и Есть ли у текущего пользователя 100 рублей на счету?
5. Capability (Возможности / Навыки)
Что происходит: Проверка того, умеет ли в принципе данный агент решать такую задачу.
Суть: Система сопоставляет Intent со списком своих модулей и плагинов. Если у агента нет банковского плагина, он на этом этапе честно скажет: «Я не умею переводить деньги».
6. Workflow (Рабочий процесс / Сценарий)
Что происходит: Строится пошаговый план (граф) выполнения задачи.
Суть: Для простых задач это один шаг. Для сложных — это цепочка действий. Например: [Заблокировать сумму] -> [Отправить запрос в банк] -> [Дождаться ответа] -> [Списать сумму].
7. Policy и Approval (Политики безопасности и Одобрение)
Что происходит: Проверка операции на легальность и безопасность (Guardrails).
Суть:
Policy: Проверяются лимиты (не пытается ли пользователь перевести больше, чем разрешено) и права доступа.
Approval: Если операция критическая (списание денег, удаление данных), workflow встает на паузу, и у пользователя на экране появляется кнопка: «Вы подтверждаете перевод 100 рублей Сане?».
8. Tool (Инструмент / Функция)
Что происходит: Непосредственное выполнение действия во внешнем мире.
Суть: Агент вызывает программную функцию (API, базу данных, внешнюю утилиту или калькулятор). В нашем примере — происходит реальный API-запрос к банковскому шлюзу для проведения транзакции.
9. Verifier и Audit (Верификация и Аудит)
Что происходит: Финальный контроль качества и логирование.
Суть:
Verifier: Система проверяет ответ от инструмента. (Действительно ли банк вернул статус Success? Не упала ли ошибка [3]? Не выдала ли нейросеть бред?).
Audit: Факт успешного (или неуспешного) действия записывается в неизменяемый лог (базу данных аудита). Это нужно для безопасности, разбора инцидентов и для того, чтобы агент помнил этот опыт [4] в следующих шагах.
Толстый харнесс почти всегда начинается с интента. Он отвечает на локальный вопрос: какое действие пользователь хочет начать. У модели есть список интентов и, при попадании на сементик роут, она должна выбрать один из путей, по которому будет обработано сообщение. Он не обязан описывать весь план. Параметры и составное поведение [5] должны жить отдельно.
Intent (интент) — это формализованное представление того, чего пользователь хочет добиться в приложении.
Пользователь выражает желание обычными словами, а приложение преобразует его в понятную системе цель, которую можно проверить, сопоставить с доступными возможностями и передать на выполнение.
Например, пользователь пишет:
«Проверь, хватает ли денег в проекте на два монитора MON-24, и если хватает — подготовь заказ».
Система должна определить не просто слова «проект», «мониторы» и «заказ», а смысл запроса:
● Цель: подготовить заказ на покупку.
● Объект: два монитора MON-24 для конкретного проекта.
● Предварительное условие: проверить доступный бюджет.
● Условие выполнения: готовить заказ только при достаточном бюджете.
● Ограничение: не совершать покупку без подтверждения пользователя.
В результате свободная формулировка превращается в структурированное описание задачи.
Чем intent отличается от текста пользователя
Пользователи могут по-разному сформулировать одну и ту же цель:
|
Сообщение пользователя |
Intent |
|
«Покажи бюджет проекта» |
get_project_budget |
|
«Сколько денег осталось?» |
get_project_budget |
|
«Какой у нас остаток по проекту?» |
get_project_budget |
|
«Подготовь покупку мониторов» |
prepare_purchase_order |
|
«Собери заказ на два монитора» |
prepare_purchase_order |
В первых трёх случаях формулировки разные, но цель одинаковая — получить информацию о бюджете.
Таким образом, intent описывает не конкретную фразу, а смысл действия, которое пользователь хочет выполнить.
3.2. Из чего состоит описание intent
В простом приложении intent может быть всего лишь строковым идентификатором:
{
"intent": "get_project_budget"
}
Но для сложных запросов одного имени недостаточно.
Полезно различать несколько элементов:
|
Элемент |
Назначение |
Пример |
|
intent |
Основная цель |
prepare_purchase_order |
|
parameters |
Над чем выполнить действие |
Проект, товар, количество |
|
conditions |
При каких условиях |
Если хватает бюджета |
|
constraints |
Какие ограничения соблюдать |
Не превышать бюджет |
|
requested_outcome |
Какой результат ожидается |
Черновик заказа |
|
authorization |
Что разрешил пользователь |
Подготовка, но не оплата |
Необязательно хранить всё именно в этих полях. Это пример логического разделения информации.
Пример распознавания intent
Сообщение пользователя:
«Проверь бюджет проекта 42 и, если денег хватит, подготовь покупку двух мониторов 24 дюйма».
Результат определения intent – структурированное представление запроса
|
Intent |
prepare_purchase_order |
|
Проект |
project_42 |
|
Товар |
MON-24 |
|
Количество |
2 |
|
Условие |
Бюджет достаточен |
Следующий этап: выбор операции или workflow
Система проверяет доступные возможности и определяет, как достичь цели.
В JSON такое описание могло бы выглядеть следующим образом:
{
"intent": "prepare_purchase_order",
"parameters": {
"project_id": "project_42",
"product_id": "MON-24",
"quantity": 2
},
"conditions": {
"budget_must_be_sufficient": true
},
"constraints": {
"execute_purchase": false
}
}
Это ещё не команда на покупку. Это структурированное описание желания пользователя, которое должно пройти дальнейшие проверки.
Intent сам по себе не функция и не workflow
Intent отвечает на вопрос «Чего хочет пользователь?», а workflow – «Как система должна этого добиться?».
Например: INTENT
prepare_purchase_order
Подготовить покупку, если позволяет бюджет
WORKFLOW
Получить бюджет проекта
Получить цену мониторов
Сравнить сумму с бюджетом
Подготовить черновик заказа
РЕЗУЛЬТАТ
Черновик заказа или объяснение, почему он не подготовлен. При этом один intent может запускать несколько операций. И наоборот, одна операция может использоваться для реализации разных intent.
Что делать, если пользователь просит несколько действий.
Не каждый запрос можно корректно выразить одним простым intent.
Например: «Покажи остаток бюджета, найди три подходящих монитора и подготовь заказ на самый дешёвый, если бюджет позволяет». Здесь присутствуют три взаимосвязанные цели: получить бюджет, подобрать товары и подготовить заказ.
Есть два распространенных подхода: Первый – составной intent. Создаётся высокоуровневая цель prepare budget_constrained purchase, для которой определен workflow с необходимыми шагами.
Второй – несколько intent с зависимостями. Система выделяет get_project_budget, find_products, prepare_purchase_order и сохраняет порядок, ограничения и условия переходов.
Выбор зависит от архитектуры приложения. Главное – не потерять зависимости между действиями и не превратить условную просьбу в безусловную.
Как система определяет intent
Возможны разные источники:
● Event: пользователь нажал кнопку «Создать заказ», поэтому тип намерения известен из события интерфейса.
● Exact: команда /budget project_42 соответствует зарегистрированному правилу.
● Semantic: LLM анализирует свободный текст и предлагает подходящий intent из разрешенного каталога.
При semantic routing модель может ошибиться, поэтому её результат нельзя автоматически считать корректной или разрешенной командой.
После определения intent сервер проверяет, существует ли такой intent, соответствует ли структура параметров схеме, доступны ли указанные объекты и разрешено ли пользователю запрошенное действие. Если не хватает важных параметров или намерение неоднозначно, система уточняет запрос либо выбирает безопасный сценарий без необратимых действий.
Хороший intent — project_report или purchase_order.
class IntentDecision(BaseModel):
# Literal ограничивает intent зарегистрированным конечным набором.
intent: Literal[
"project_report",
"retry_job",
"update_profile",
"purchase_order",
"unknown",
]
# Field с ge (Greater than or Equal) - больше или равно и le (Less than or Equal)
confidence: float = Field(ge=0.0, le=1.0)
project_id: str | None = None
# DateRange и object_reference являются параметрами,
# поэтому один intent может обслуживать разные периоды и объекты без взрыва числа названий.
period: DateRange | None = None
object_reference: str | None = None
# само принятие решения выглядит так
decision = IntentDecision(
intent="project_report",
confidence=0.94,
project_id="project_42",
period=DateRange(start="2026-09-01", end="2026-09-07"),
)
Важно, что валидная структура ещё не означает разрешённое действие: дальше работают context и policy.
Сам процесс принятия решения может быть описан следующим образом. Опишем класс IntentDesicion и нужные функции
from datetime import date
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, model_validator
EXISTING_PROJECTS = ["project_42", "project_77"]
CONFIDENCE_THRESHOLD = 0.90
class DateRange(BaseModel):
start: date
end: date
@model_validator(mode="after")
def validate_dates(self):
if self.start > self.end:
raise ValueError("Дата начала не может быть позже даты окончания")
return self
class IntentDecision(BaseModel):
model_config = ConfigDict(extra="forbid")
intent: str | None = None
arguments: dict[str, Any] = Field(default_factory=dict)
clarification: str | None = None
confidence: float | None = Field(default=None, ge=0, le=1)
project_id: str | None = None
period: DateRange | None = None
@model_validator(mode="after")
def action_or_question(self):
if self.clarification is not None:
if not self.clarification.strip() or self.intent is not None or self.arguments:
raise ValueError("Уточнение не должно содержать действие")
elif not self.intent:
raise ValueError("Для действия нужен intent")
elif self.confidence is None:
raise ValueError("Для действия нужна уверенность")
return self
def generate_report(project_id: str, period: DateRange) -> str:
return f"Успешно сгенерирован отчет по {project_id} за период с {period.start} по {period.end}"
def process_intent_decision(decision: IntentDecision) -> dict:
if decision.clarification is not None:
return {"status": "PENDING_CONFIRMATION", "action": "ask_user",
"message": decision.clarification}
if decision.project_id is None or decision.period is None:
return {"status": "REJECTED", "action": "show_error",
"message": "Не хватает проекта или периода."}
if decision.project_id not in EXISTING_PROJECTS:
return {"status": "REJECTED", "action": "show_error",
"message": f"Ошибка: проект {decision.project_id} не найден в системе."}
if decision.confidence >= CONFIDENCE_THRESHOLD:
return {"status": "EXECUTED", "action": "deliver_payload",
"payload": generate_report(decision.project_id, decision.period)}
return {"status": "PENDING_CONFIRMATION", "action": "ask_user",
"message": (f"Вы имели в виду отчёт по проекту {decision.project_id} "
f"за период {decision.period.start} - {decision.period.end}?")}
decision_ok = IntentDecision(
intent="project_report",
confidence=0.94,
project_id="project_42",
period=DateRange(start="2026-09-01", end="2026-09-07"),
)
print("А:", process_intent_decision(decision_ok))
decision_low = decision_ok.model_copy(update={"confidence": 0.85})
print("Б:", process_intent_decision(decision_low))
decision_wrong = decision_ok.model_copy(update={"project_id": "project_999"})
print("В:", process_intent_decision(decision_wrong))
И напоследок разберем один хода агента в толстом харнессе. Рассмотрим функцию, которая управляет жизненным циклом одного хода: от получения сообщения до исполнения и записи исхода.
def run_agent_turn(message, session):
try:
#request = normalize_message(message) создаёт внутреннее представление ввода,
#сохраняя оригинал.
request = normalize_message(message)
# Событие confirm_purchase идёт в отдельную ветку
if request.event and request.event.get("type") == "confirm_purchase":
#pending_id указывает на уже сохранённую заявку,
#confirm_purchase повторно проверяет владельца, организацию, права и состояние
result = confirm_purchase(request.event["pending_id"], session)
# Результат этой ветки тоже проходит record_outcome;
# ошибка попадает во внешний обработчик
return record_outcome(result, session)
# Обычный запрос затем передаётся в resolve_request.
#resolve_request объединяет детерминированный маршрут и обращение к модели.
#В обоих случаях получается предложение из каталога, которое нужно проверить.
# У него может оказаться не действие,
action = resolve_request(request, session, session.llm)
#ClarificationRequest. isinstance проверяет тип объекта;
if isinstance(action, ClarificationRequest):
return record_outcome(action, session)
context = require_context(action, session) # Только после авторизации.
policy = evaluate_policy(action, context, session.actor)
if policy.blocked: # Запрет проверяем раньше подтверждения.
return record_outcome(explain_blocked(policy), session)
# Подготовка покупки сама создаёт одну заявку, но не заказ.
if policy.requires_approval:
raise ValueError("Изменяющий вызов должен идти через pending workflow")
result = execute_action(action, context, policy)
verified = verify_result(result, action, context)
return record_outcome(verified, session)
except Exception as error:
# Адаптер не раскрывает внутреннюю ошибку пользователю.
return record_failure(error, session)
Продолжение следует.
Автор: kobubu
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/37931
URLs in this post:
[1] Реакция: http://www.braintools.ru/article/1549
[2] памяти: http://www.braintools.ru/article/4140
[3] ошибка: http://www.braintools.ru/article/4192
[4] опыт: http://www.braintools.ru/article/6952
[5] поведение: http://www.braintools.ru/article/9372
[6] Источник: https://habr.com/ru/articles/1084834/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1084834
Нажмите здесь для печати.