АДРЕСНЫЙ РЕЖИМ - авторан история - базовая версия

This commit is contained in:
2026-04-09 12:34:10 +03:00
parent df29798fa2
commit edfa09c9af
31 changed files with 5261 additions and 2392 deletions
+372
View File
@@ -0,0 +1,372 @@
Да. И ключевая мысль тут такая: **эта система не заменяет ваш продуктовый контур**, а вешается **рядом** с ним как внешний тестовый раннер и слой контроля качества.
То есть не так: “перенесли ассистента в Promptfoo/DeepEval и теперь он там живёт”.
А так: **ваш ассистент остаётся у вас в коде**, со всеми маршрутами, настройками, тулзами и ограничениями. Eval-система просто **вызывает его**, подаёт тестовые сценарии и проверяет, что он ответил правильно. Promptfoo умеет ходить в произвольный HTTP endpoint, а также подключать кастомный Python provider; у него же есть поддержка system/user/assistant сообщений и multi-turn chat threads. ([Promptfoo][1])
Как это выглядит по-человечески.
## Что именно вы разворачиваете
Самый приземлённый вариант:
* ваш текущий backend ассистента;
* рядом Promptfoo **или** DeepEval;
* желательно ещё Langfuse **или** Phoenix для трассировок и накопления провалов.
Langfuse умеет хранить traces/sessions, собирать datasets, в том числе из production traces, и запускать experiments по этим датасетам. Phoenix тоже self-hosted, с tracing/evals/datasets/experiments и умеет гонять evals поверх уже собранных trace-данных. ([langfuse.com][2])
## Вы минуете интерфейс или нет
**Для основных eval-тестов — да, интерфейс лучше миновать.**
И это нормально.
Почему:
* интерфейс даёт лишний шум;
* вам важнее проверить не кнопку и не рендер, а **маршрут, параметры, tool use, числа, память диалога и финальный ответ**;
* если тест идёт прямо в backend-контур, вы проверяете именно логику ассистента.
Практически я бы делал так:
**Слой 1. Основной eval-контур — мимо UI.**
Promptfoo или DeepEval бьёт прямо в ваш backend endpoint, например `/assistant/chat` или специальный `/assistant/test-run`.
**Слой 2. Маленький smoke-набор через UI.**
5–10 сценариев, просто чтобы не развалился фронт, история сообщений, отображение ответа, кнопки и т.д.
То есть **95% качества ассистента** проверяется не через UI, а через его реальный backend contract.
## Что именно туда “подключается”
Есть 3 рабочих способа.
### Вариант A. Через HTTP endpoint
Самый простой.
У вас уже есть endpoint, который принимает:
* system context,
* историю диалога,
* user message,
* maybe org/company/period,
* maybe debug flags.
Тогда Promptfoo просто шлёт туда POST-запрос. Это его штатный режим. Для OpenAI-compatible или вообще любых HTTP endpoint он умеет собирать body, headers и доставать ответ из нужного поля JSON. ([Promptfoo][1])
### Вариант B. Через Python wrapper
Если ассистент лучше вызывать не по HTTP, а прямо функцией внутри кода, Promptfoo умеет кастомный Python provider, а DeepEval вообще изначально Python-first и ближе к pytest-стилю. ([Promptfoo][3])
### Вариант C. Через “test harness” endpoint
Часто это лучший вариант для сложных ассистентов.
Вы делаете отдельную ручку, условно:
`POST /internal/eval/run`
И она:
* принимает сообщение и историю,
* запускает **ровно тот же** роутер и те же тулзы, что и боевой контур,
* но дополнительно возвращает debug:
* какой маршрут выбрался,
* какие параметры извлеклись,
* какие тулзы вызвались,
* какие SQL/MCP/1C-запросы ушли,
* какие evidence вернулись,
* почему был выбран именно этот ответ.
Вот это уже идеальная почва для нормальных evals.
## Где хранится промпт
Тут важный момент.
Есть два режима:
### 1) Тестировать “изолированный промпт”
Это когда system prompt реально хранится в Promptfoo/DeepEval-конфиге. Такой режим полезен, если вы хотите сравнивать версии системного промпта отдельно от продукта. Promptfoo поддерживает prompt files, сообщения в chat-формате и эксперименты с разными prompt/provider комбинациями. Langfuse тоже умеет prompt management, versioning и experiments against datasets. ([Promptfoo][4])
### 2) Тестировать реальный продуктовый контур
Это когда системный промпт живёт у вас в коде, а eval-система просто вызывает ваш ассистент как чёрный ящик.
**Для вашего случая я бы начинал именно со второго.**
Потому что у вас не “просто промпт”, а целая продуктовая логика: маршруты, ограничения, диалог, бухгалтерские сценарии, tool calling.
Иначе вы получите ложную картину: “в Promptfoo всё хорошо”, а в реальном продукте всё поплыло.
## Что считать корректным ответом
Вот тут и находится главный узел.
У вас **не может быть одного типа проверки** для всех кейсов. Нужен гибрид.
### Тип 1. Жёсткая проверка
Для кейсов вроде:
* посчитать НДС,
* показать остатки,
* топ контрагентов,
* сколько заплатили подрядчикам,
* остатки по счёту 51,
* прогноз НДС на период.
Тут правильность — это:
* правильный маршрут;
* правильные фильтры;
* правильный период;
* правильная организация;
* правильные числа;
* правильная сортировка.
То есть тут нужен не “похоже на хороший ответ”, а почти unit/integration test.
Пример:
* expected route = `vat_forecast`
* expected entity = `ООО Ромашка`
* expected period = `2026-03`
* expected total = `1_245_330.17`
* допуск по числам = 0.01
### Тип 2. Структурная проверка
Например:
* ответ должен содержать таблицу по контрагентам;
* не должен выдумывать документы;
* должен явно указать период;
* если данных нет, должен честно сказать, что не хватает данных;
* должен дать evidence/source refs.
### Тип 3. Rubric / LLM-as-a-judge
Для свободного диалога.
Например:
* объяснил ли понятным языком;
* не ушёл ли за рамки бухгалтерского домена;
* не потерял ли ограничение;
* не начал ли советовать что-то юридически/налогово опасное без оговорок;
* корректно ли обработал follow-up.
Promptfoo для этого использует `llm-rubric`, а DeepEval — G-Eval и conversational G-Eval для whole-conversation оценки. Promptfoo отдельно предупреждает, что у model-graded assertions PASS/FAIL зависит от `pass` и `threshold`, и без порога можно получить “зелёный” тест даже при низком score. DeepEval даёт кастомные judge-метрики и отдельно умеет conversational G-Eval для оценки целого диалога, а не одного ответа. ([Promptfoo][5])
И вот это очень похоже на вашу проблему:
**“кодекс сказал зелёное, а руками тестишь — пиздец”**
часто означает одно из трёх:
1. критерии слишком общие;
2. judge-модель оценивает “по стилю”, а не по факту;
3. нет жёсткого threshold и нет проверки маршрута/чисел/tool use.
## Как я бы устроил это у вас пошагово
### Шаг 1. Не пытаться сразу “умную автопочинку”
Сначала нужен **контур истины**.
Минимальный набор:
* 100200 кейсов;
* разбивка по типам;
* единые поля expected behavior.
Пример структуры кейса:
```json
{
"id": "vat_001",
"history": [],
"input": "Посчитай НДС к уплате за март 2026 по ООО Альфа",
"expected": {
"route": "vat_summary",
"entities": {
"company": "ООО Альфа",
"period": "2026-03"
},
"must_call_tools": ["vat_turnover_query"],
"must_not_call_tools": ["counterparty_top_query"],
"answer_checks": {
"contains_period": true,
"must_include_total": true
},
"numeric_targets": {
"vat_due": 1245330.17,
"tolerance": 0.01
}
}
}
```
### Шаг 2. Подключить ассистента как black box
Лучше всего — через HTTP.
То есть eval-система не знает, как внутри устроены ваши маршруты.
Она просто шлёт запрос и получает:
* final answer,
* trace/debug json.
### Шаг 3. Разбить проверки на 4 уровня
Я бы делал именно так:
**A. Router correctness**
Правильно ли выбран маршрут.
**B. Tool / query correctness**
Те ли инструменты/запросы пошли.
**C. Factual correctness**
Правильные ли числа и факты.
**D. Dialogue quality**
Не потерялся ли контекст, не ушёл ли в болтовню, не выдумал ли лишнего.
### Шаг 4. Собирать реальные фейлы из жизни
Вот тут очень полезен Langfuse или Phoenix.
Идея такая:
* боевые диалоги пишутся в traces;
* хорошие/плохие помечаются;
* из них рождается dataset;
* dataset пополняется не вручную с нуля, а из реальных провалов.
Langfuse это прямо позиционирует как сценарий: datasets, experiments, traces, production feedback. Phoenix тоже умеет запускать evals на traces. ([langfuse.com][2])
### Шаг 5. Только после этого впрягать Codex/Aider/OpenHands
И тут принцип очень важный:
**Codex не должен быть судьёй.**
Он должен быть:
* генератором кейсов,
* генератором follow-up вопросов,
* генератором adversarial сценариев,
* фиксером кода/промпта/роутера,
* но не финальным источником истины.
Иначе он начинает сам себя хвалить.
Aider, например, умеет автоматически гонять линтеры и тесты после своих изменений и пытаться чинить найденные проблемы. OpenHands позиционируется как open-source coding-agent платформа/SDK с локальным запуском агентов и CLI. ([aider.chat][6])
## Как подключить “мощь кодекса”, чтобы он сам задавал вопросы
Вот это уже реально хорошая идея. Но делать это надо не “кодекс сам всё решит”, а в двух ролях:
### Роль 1. Генератор тестов
Codex берёт:
* ваши маршруты,
* текущие кейсы,
* реальные trace-провалы,
* описание домена,
и генерирует новые вопросы:
* пограничные;
* двусмысленные;
* с пропущенным периодом;
* с несколькими организациями;
* со сменой темы внутри диалога;
* с follow-up типа “а по прошлому месяцу?”;
* с конфликтующими параметрами.
То есть он расширяет ваш eval suite.
### Роль 2. Фиксер
После падения тестов Codex получает:
* failing cases,
* expected behavior,
* actual trace,
* diff последних изменений,
и правит:
* router,
* extraction,
* system prompt,
* guardrails,
* answer composer,
* test suite.
А потом снова гоняется eval.
То есть получается петля:
**реальные traces / ручные кейсы → eval suite → падения → codex fixer → повторный прогон → merge only if green**
## Что я бы рекомендовал конкретно вам
Для вашего кейса я бы не делал слишком жирную схему сразу.
### Стартовый стек
**Promptfoo + Langfuse + Codex/Aider**
Почему:
* Promptfoo быстро подключается к HTTP endpoint и удобен для регрессионных прогонов, rubric-assertions и multi-turn сценариев. ([Promptfoo][1])
* Langfuse хорошо подходит как накопитель traces/datasets/experiments. ([langfuse.com][2])
* Codex/Aider использовать не как judge, а как test generator + fixer. ([aider.chat][6])
### Когда брать DeepEval
Если захотите больше Python-first логики и метрик уровня:
* component tests,
* conversational metrics,
* custom LLM judge,
* synthetic dataset generation.
DeepEval прямо под это и заточен: unit-test style, end-to-end/component level, custom LLMs for evaluation, conversational G-Eval для многоходовых диалогов. ([DeepEval][7])
## Самый важный практический вывод
Вам не нужна “система, куда вставил промпт и она magically поняла, что хорошо”.
Вам нужен **контракт качества**:
* какой маршрут должен быть выбран;
* какие параметры должны быть извлечены;
* какие тулзы должны вызваться;
* какие числа должны получиться;
* как ассистент должен вести себя в follow-up;
* где он обязан честно сказать “не знаю / не хватает данных”.
Пока этого слоя нет, любой Codex, хоть очень умный, будет красить вам зелёное “по ощущению”.
А когда этот слой появится, уже можно сделать очень сильную штуку:
* Codex сам генерит новые кейсы,
* сам находит слабые места,
* сам правит код,
* но **истину проверяет не он**, а ваш eval harness.
Если хочешь, следующим сообщением я могу сразу дать **конкретную схему под ваш стек**:
`Qwen 2.5 локально + backend ассистента + Promptfoo + Langfuse + Codex`,
с прямым пошаговым сценарием “что ставить, какой endpoint сделать, какой JSON возвращать и как начать с первых 20 кейсов”.
[1]: https://www.promptfoo.dev/docs/providers/http/ "HTTP/HTTPS API | Promptfoo"
[2]: https://langfuse.com/docs/evaluation/experiments/datasets "Datasets - Langfuse"
[3]: https://www.promptfoo.dev/docs/providers/python/ "Python Provider | Promptfoo"
[4]: https://www.promptfoo.dev/docs/configuration/chat/ "Chat Conversations and Multi-Turn Threads | Promptfoo"
[5]: https://www.promptfoo.dev/docs/configuration/expected-outputs/model-graded/llm-rubric/ "LLM Rubric | Promptfoo"
[6]: https://aider.chat/docs/usage/lint-test.html?utm_source=chatgpt.com "Linting and testing"
[7]: https://deepeval.com/docs/getting-started "Quick Introduction | DeepEval by Confident AI - The LLM Evaluation Framework"
+245 -2305
View File
File diff suppressed because it is too large Load Diff