Да. И ключевая мысль тут такая: **эта система не заменяет ваш продуктовый контур**, а вешается **рядом** с ним как внешний тестовый раннер и слой контроля качества.

То есть не так: “перенесли ассистента в 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"
