Бот не только читает KB и отвечает — он может вызывать инструменты (agentic actions). Поверх RAG-ответа крутится tool-loop: LLM решает вызвать инструмент, получает результат и продолжает, пока не сформирует финальный ответ.
packages/kb/src/tool-loop.ts — интерфейс RagTool:
interface RagTool<TParams extends z.ZodTypeAny> {
name: string; // имя функции для LLM
description: string; // когда вызывать (читает модель)
parameters: TParams; // Zod-схема → JSON schema аргументов
execute: (args) => Promise<unknown>; // выполнение
}Zod-схема конвертится в OpenAI function-формат (toolToOpenAIFunction()),
аргументы валидируются перед execute.
runToolLoop() (tool-loop.ts), макс. циклов DEFAULT_MAX_TOOL_CYCLES = 4:
повтор до maxCycles:
1. chat.completeWithTools(messages, toolDefs)
2. нет tool-calls → return { content, exhausted: false } ← финал
3. добавить assistant-сообщение с вызовами
4. выполнить инструменты параллельно (Promise.allSettled)
5. результат каждого → message { role: "tool", tool_call_id, content }
6. записать ToolCallRecord (name, args, result/ошибка, cycle)
лимит исчерпан → return { content: null, exhausted: true }
Устойчивость к ошибкам: неизвестный инструмент и брошенная execute-ошибка
возвращаются модели как { error } — loop никогда не падает на ошибке
инструмента, модель может попробовать иначе.
| Инструмент | Файл | Что делает |
|---|---|---|
offer_booking_link |
packages/kb/src/built-in-tools/calendly.ts |
makeBookingLinkTool(url) — когда лид хочет записаться/созвониться, возвращает { url } (Calendly/Cal.com/Tidycal). Без аргументов. |
| Exchange-инструменты | apps/api/src/lib/exchange/tools.ts |
makeExchangeTools() — 7 инструментов: compute_exchange_quote, check_exchange_verification (KYC-гейт), create_exchange_order, fetch_exchange_requisites, verify_exchange_payment, issue_exchange_payout, get_exchange_business_info (подключаются если у тенанта есть активные курсы). См. EXCHANGE.md. |
RagReplyStrategy.resolveTools({ tenantId, conversationId })(packages/conversation-engine/src/reply-strategy/rag-reply.ts) вызывается раз на входящее сообщение и возвращает списокRagTool[]; они передаются вanswerWithRag(). Нет резолвера → пустой список → tool-loop не активен.- Если tool-loop реально вызвал инструменты,
RagReplyStrategy.recordToolCallsиLlmReplyStrategy.recordToolCallsсохраняют trace вagent_tool_callsчерезAgentToolCallsRepo: tool name, args/result JSON, error flag, cycle/index, conversation/contact. Это основа для self-learning/coach/outcome анализа; запись идёт после LLM/tool execution через отдельную короткуюwithTenantтранзакцию. - Admin quality API даёт read/review слой поверх traces:
GET /api/admin/quality/tool-callsфильтрует вызовы по tenant/conversation/ message/outbound/tool/error/source, аPOST /api/admin/quality/tool-calls/:id/feedbackпишет human label вagent_tool_call_feedback(good_reply,wrong_tool,missing_tool,bad_args,other).GET /api/admin/quality/tool-call-feedback/summaryпоказывает label counts и top failing tools,GET /api/admin/quality/tool-call-feedback/proposalsгруппирует actionable labels (wrong_tool,missing_tool,bad_args) в operator-facing improvement proposals, а JSONL export даёт разметку для offline анализа. - Tracked improvement proposals живут в
agent_tool_call_improvement_proposals: оператор может оставить proposal pending, dismiss, apply с конкретным artifact reference (pull_request,prompt_patch,tool_schema_patch,regression_case, и т.д.) или нажать Case, чтобы сохранить пример вagent_tool_call_regression_cases. Regression case фиксирует исходные args/result, reviewer label, expected behavior и context; proposal при этом закрывается какappliedсresolution.ref = REG-<id>. Cases можно архивировать/восстанавливать и выгружать какtool-call-regression-cases.jsonlдля offline regression/eval пайплайна. - Coach proposals (
POST /api/admin/quality/coach/proposals) подтягивают последние actionable feedback labels по тому жеstyleIdи передают их в CoachAnalyzer как human-reviewed defects. Coach не меняет tool contracts автоматически: он выражает эти сигналы через style guidance, examples, skill attach/detach suggestions или rationale для operator follow-up. - Резолвер собирается в
apps/api/src/llm-bootstrap.ts: booking (из секретаtool_booking_url) + exchange-инструменты (если активны курсы). Кеши сбрасываютсяinvalidateToolsFor(tenantId)после правок в админке.
apps/api/src/routes/admin-tools.ts, хранение в tenant_secrets
(tool_booking_url, encrypted):
GET /api/admin/tools — список инструментов + enabled-флаги
GET /api/admin/tools/booking — { enabled, url }
POST /api/admin/tools/booking — { url } → валидирует, шифрует, hot-reload
DELETE /api/admin/tools/booking — отключить
UI: страница «Инструменты» (/tools).
Экспортированные из Quality Lab cases можно прогонять локально или в CI:
bun run quality:tool-regressions -- --file tool-call-regression-cases.jsonlRunner читает JSONL-файл или сам забирает export из Quality API, валидирует
recordType, toolName, input.args, expected.behavior, reviewer label и
shape исходного tool-call result/error. На schema/regression failures он
печатает line/case/tool/path и возвращает non-zero exit code.
Для CI/cron можно не скачивать файл вручную:
QUALITY_LAB_TOKEN=<admin-bearer-token> \
bun run quality:tool-regressions -- \
--api-base https://api.example.com \
--status active \
--limit 500--token <token> тоже поддерживается, но env-переменная безопаснее для shell
history. По умолчанию API-mode берёт status=active&limit=500 с
/api/admin/quality/tool-call-regression-cases/export.jsonl.
GitHub Actions workflow Tool-call Regressions
(.github/workflows/tool-regressions.yml) запускает тот же runner вручную
(workflow_dispatch) или ежедневно по schedule. Для scheduled runs настройте:
- secret
QUALITY_LAB_TOKEN— bearer token админа/сервисного админа; - repo variable
QUALITY_TOOL_REGRESSION_API_BASE— напримерhttps://api.example.com; - optional variables:
QUALITY_TOOL_REGRESSION_STATUS,QUALITY_TOOL_REGRESSION_LIMIT,QUALITY_TOOL_REGRESSION_TOOLS,QUALITY_TOOL_REGRESSION_SKIP_UNSUPPORTED.
Если token/base не настроены, workflow завершается skip-ом с notice. Когда
runner выполняется, он загружает JSON report artifact tool-call-regressions;
regression failures дают красный job.
Archived cases по умолчанию не валидируются, а явно считаются skipped. Если нужно проверить весь local export:
bun run quality:tool-regressions -- \
--file tool-call-regression-cases.jsonl \
--include-archivedДля CI можно зафиксировать allowlist реально поддержанных инструментов. Кейс с toolName вне allowlist считается failure; чтобы явно отложить такие cases:
bun run quality:tool-regressions -- \
--file tool-call-regression-cases.jsonl \
--tools offer_booking_link,quote_exchange_rate \
--skip-unsupportedМашиночитаемый отчёт:
bun run quality:tool-regressions -- --file tool-call-regression-cases.jsonl --jsonДля CI можно писать отчёты сразу в файлы:
bun run quality:tool-regressions -- \
--file tool-call-regression-cases.jsonl \
--out-json artifacts/tool-call-regressions.json \
--out-junit artifacts/tool-call-regressions.junit.xml \
--out-md artifacts/tool-call-regressions.mdJUnit содержит один testcase на regression case; failed cases попадают в
<failure>, archived/unsupported skipped cases — в <skipped>. Markdown
summary подходит для GITHUB_STEP_SUMMARY.
- Реализовать
RagTool(name / description / Zodparameters/execute). - Вернуть его из резолвера в
RagReplyStrategy(через опциюresolveTools). - Если конфигурируется тенантом — завести секрет/конфиг, ручки в admin-API и
подключить в
llm-bootstrap.tsс кешем +onReload-инвалидацией.
Образец — booking-инструмент выше.
Дальнейшие инструменты (CRM create-lead, calendar book-slot, payment invoice, operator alert) — см. strategy/ROADMAP.md (M7).