АДРЕСНЫЙ РЕЖИМ - авторан история - базовая версия
This commit is contained in:
@@ -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. Не пытаться сразу “умную автопочинку”
|
||||
|
||||
Сначала нужен **контур истины**.
|
||||
|
||||
Минимальный набор:
|
||||
|
||||
* 100–200 кейсов;
|
||||
* разбивка по типам;
|
||||
* единые поля 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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user