АРЧ ЮИ - остановка прогона через гуй
This commit is contained in:
+65
-12
@@ -355,7 +355,7 @@ function readReportedCaseIds(job) {
|
||||
return output;
|
||||
}
|
||||
function isTerminalCaseStatus(status) {
|
||||
return status === "completed" || status === "failed";
|
||||
return status === "completed" || status === "failed" || status === "canceled";
|
||||
}
|
||||
function syncJobWithSessions(job) {
|
||||
if (!job.run_id || !job.eval_target.startsWith("assistant_")) {
|
||||
@@ -473,6 +473,7 @@ function buildEvalRouter(services) {
|
||||
throw new http_1.ApiError("ASYNC_CASESET_EMPTY", "No runnable cases found in selected case-set.", 400);
|
||||
}
|
||||
const nowIso = new Date().toISOString();
|
||||
const abortController = new AbortController();
|
||||
const job = {
|
||||
job_id: jobId,
|
||||
status: "queued",
|
||||
@@ -491,7 +492,8 @@ function buildEvalRouter(services) {
|
||||
messages: []
|
||||
})),
|
||||
error: null,
|
||||
report: null
|
||||
report: null,
|
||||
abort_controller: abortController
|
||||
};
|
||||
ASYNC_JOBS.set(job.job_id, job);
|
||||
trimAsyncJobsStore();
|
||||
@@ -500,25 +502,48 @@ function buildEvalRouter(services) {
|
||||
const target = ASYNC_JOBS.get(job.job_id);
|
||||
if (!target)
|
||||
return;
|
||||
if (target.status === "canceled") {
|
||||
return;
|
||||
}
|
||||
target.status = "running";
|
||||
target.updated_at = new Date().toISOString();
|
||||
try {
|
||||
const report = await services.evalService.run({
|
||||
...payload,
|
||||
caseSetFile: runtimeCaseSetFile,
|
||||
runId
|
||||
runId,
|
||||
abortSignal: abortController.signal
|
||||
});
|
||||
target.report = report;
|
||||
syncJobWithSessions(target);
|
||||
target.completed_cases = target.total_cases;
|
||||
target.status = "completed";
|
||||
target.updated_at = new Date().toISOString();
|
||||
const latestAfterRun = ASYNC_JOBS.get(job.job_id);
|
||||
if (!latestAfterRun) {
|
||||
return;
|
||||
}
|
||||
if (latestAfterRun.status === "canceled") {
|
||||
latestAfterRun.updated_at = new Date().toISOString();
|
||||
return;
|
||||
}
|
||||
latestAfterRun.report = report;
|
||||
syncJobWithSessions(latestAfterRun);
|
||||
latestAfterRun.completed_cases = latestAfterRun.total_cases;
|
||||
latestAfterRun.status = "completed";
|
||||
latestAfterRun.updated_at = new Date().toISOString();
|
||||
latestAfterRun.abort_controller = null;
|
||||
}
|
||||
catch (error) {
|
||||
syncJobWithSessions(target);
|
||||
target.status = "failed";
|
||||
target.error = error instanceof Error ? error.message : String(error);
|
||||
target.updated_at = new Date().toISOString();
|
||||
const latestAfterError = ASYNC_JOBS.get(job.job_id);
|
||||
if (!latestAfterError) {
|
||||
return;
|
||||
}
|
||||
if (latestAfterError.status === "canceled") {
|
||||
latestAfterError.updated_at = new Date().toISOString();
|
||||
latestAfterError.abort_controller = null;
|
||||
return;
|
||||
}
|
||||
syncJobWithSessions(latestAfterError);
|
||||
latestAfterError.status = "failed";
|
||||
latestAfterError.error = error instanceof Error ? error.message : String(error);
|
||||
latestAfterError.updated_at = new Date().toISOString();
|
||||
latestAfterError.abort_controller = null;
|
||||
}
|
||||
})();
|
||||
});
|
||||
@@ -552,5 +577,33 @@ function buildEvalRouter(services) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
router.post("/api/eval/run-async/:job_id/cancel", (req, res, next) => {
|
||||
try {
|
||||
const jobId = String(req.params.job_id ?? "").trim();
|
||||
if (!jobId) {
|
||||
throw new http_1.ApiError("INVALID_ASYNC_JOB_ID", "job_id is required.", 400);
|
||||
}
|
||||
const job = ASYNC_JOBS.get(jobId);
|
||||
if (!job) {
|
||||
throw new http_1.ApiError("ASYNC_JOB_NOT_FOUND", `Async eval job not found: ${jobId}`, 404);
|
||||
}
|
||||
if (!isTerminalCaseStatus(job.status)) {
|
||||
job.status = "canceled";
|
||||
job.error = "Остановлено оператором.";
|
||||
job.updated_at = new Date().toISOString();
|
||||
job.abort_controller?.abort();
|
||||
job.abort_controller = null;
|
||||
job.cases = job.cases.map((item) => item.status === "completed" ? item : { ...item, status: "canceled" });
|
||||
syncJobWithSessions(job);
|
||||
}
|
||||
(0, http_1.ok)(res, {
|
||||
ok: true,
|
||||
job: snapshotJob(job)
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ async function runAssistantLivingChatLlmRuntime(input) {
|
||||
apiKey: String(input.payload.apiKey ?? input.defaultApiKey ?? ""),
|
||||
model: String(input.payload.model ?? input.defaultModel),
|
||||
baseUrl: input.payload.baseUrl ?? input.defaultBaseUrl,
|
||||
abortSignal: input.payload.abortSignal,
|
||||
temperature,
|
||||
maxOutputTokens
|
||||
}, {
|
||||
|
||||
@@ -72,6 +72,9 @@ function hasPriorAssistantTurn(items) {
|
||||
}
|
||||
return items.some((item) => item && typeof item === "object" && item.role === "assistant");
|
||||
}
|
||||
function buildDeterministicSmalltalkLeadReply() {
|
||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
||||
}
|
||||
function findLastAddressDebug(items) {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
@@ -295,9 +298,11 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
}
|
||||
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||
if (proactiveOffer) {
|
||||
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
|
||||
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
||||
.filter((part) => String(part ?? "").trim().length > 0)
|
||||
.join(" ");
|
||||
livingChatProactiveScopeOfferApplied = true;
|
||||
livingChatSource = "llm_chat_with_proactive_scope_offer";
|
||||
livingChatSource = "deterministic_smalltalk_with_proactive_scope_offer";
|
||||
if (!dataScopeProbe) {
|
||||
dataScopeProbe = proactiveScopeProbe;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ function createAssistantLivingModePolicy(deps) {
|
||||
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|склад|товар|номенклат|материал|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|warehouse|inventory|stock|item|организац|компан|контор|фирм)/i.test(lower);
|
||||
}
|
||||
function hasDataRetrievalRequestSignal(text) {
|
||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||
const lower = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
@@ -248,20 +248,33 @@ function createAssistantLivingModePolicy(deps) {
|
||||
return hasCapabilitySignal && !hasRetrievalSignal;
|
||||
}
|
||||
function hasLivingChatSignal(text) {
|
||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||
if (!lower) {
|
||||
const rawLower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||
const repairedLower = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
const variants = [rawLower, repairedLower].filter((value, index, items) => value.length > 0 && items.indexOf(value) === index);
|
||||
if (variants.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) {
|
||||
const hasDirectionalReply = variants.some((value) => /^(?:\u0430\s+)?(?:\u0442\u0443\u0442|\u0437\u0434\u0435\u0441\u044c|\u0442\u0430\u043c|\u0441\u044e\u0434\u0430|\u0442\u0443\u0434\u0430)[\s!?.,:;\-]*$/iu.test(value));
|
||||
if (hasDirectionalReply) {
|
||||
return true;
|
||||
}
|
||||
if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) {
|
||||
const hasShortGreeting = variants.some((value) => /^(?:\u0430\u0433\u0430|\u0443\u0433\u0443|\u043e\u043a|\u043e\u043a\u0435\u0439|\u044f\u0441\u043d\u043e|\u043f\u043e\u043d\u044f\u043b(?:\u0430)?|\u043f\u0440\u0438\u043d\u044f\u0442\u043e|\u0441\u043f\u0430\u0441\u0438\u0431\u043e|\u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e|\u0441\u0443\u043f\u0435\u0440|\u043a\u043b\u0430\u0441\u0441|\u043d\u043e\u0440\u043c|\u0433\u043e|\u0434\u0430\u0432\u0430\u0439|\u043f\u043e\u0433\u043d\u0430\u043b\u0438|\u043f\u0440\u0438\u0432\u0435\u0442(?:\u0438\u043a)?|\u0445\u0430\u0439|\u0439\u043e|yo|\u0447[её]\s+\u0442\u0430\u043c|\u0447[её]\s+\u043a\u0430\u043a|hello|hi|thanks?)$/iu.test(value));
|
||||
if (hasShortGreeting) {
|
||||
return true;
|
||||
}
|
||||
if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) {
|
||||
const hasGreetingWithFollowup = variants.some((value) => /(?:^|[\s,.!?;:()\-])(?:\u043f\u0440\u0438\u0432\u0435\u0442\p{L}*|\u0445\u0430\u0439|\u0439\u043e|hello|hi|yo)(?=$|[\s,.!?;:()\-]).*(?:^|[\s,.!?;:()\-])(?:\u043a\u0430\u043a|\u0434\u0435\u043b\u0430|\u0442\u0430\u043c|\u0447[её])(?=$|[\s,.!?;:()\-])/iu.test(value));
|
||||
if (hasGreetingWithFollowup) {
|
||||
return true;
|
||||
}
|
||||
return hasSmallTalkSignal(lower);
|
||||
const hasColloquialFollowup = variants.some((value) => /(?:^|[\s,.!?;:()\-])(?:\u0447[её]\s+\u043a\u0430\u043a|\u0447[её]\s+\u0442\u0430\u043c|\u043a\u0430\u043a\s+\u0442\u0430\u043c(?:\s+\u0434\u0435\u043b\u0430)?)(?=$|[\s,.!?;:()\-])/iu.test(value));
|
||||
if (hasColloquialFollowup) {
|
||||
return true;
|
||||
}
|
||||
const hasOpenSmalltalkPrompt = variants.some((value) => /(?:\u043a\u0430\u043a\s+\u0434\u0435\u043b\u0430|\u043a\u0430\u043a\s+\u0442\u044b|\u0447\u0442\u043e\s+\u043d\u043e\u0432\u043e\u0433\u043e|\u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0438\s+\u043e\s+\u0441\u0435\u0431\u0435|\u0447\u0435\u043c\s+\u043c\u043e\u0436\u0435\u0448\u044c\s+\u043f\u043e\u043c\u043e\u0447\u044c|\u0434\u0430\u0432\u0430\u0439\s+\u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c|\u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c|\u043e\u0431\u0441\u0443\u0434\u0438\u043c|\u043f\u043e\u0441\u043e\u0432\u0435\u0442\u0443\u0439|\u0447\u0442\u043e\s+\u0434\u0443\u043c\u0430\u0435\u0448\u044c)/iu.test(value));
|
||||
if (hasOpenSmalltalkPrompt) {
|
||||
return true;
|
||||
}
|
||||
return variants.some((value) => hasSmallTalkSignal(value));
|
||||
}
|
||||
function resolveLivingAssistantModeDecision(input) {
|
||||
const userMessage = String(input?.userMessage ?? "");
|
||||
|
||||
@@ -3335,6 +3335,7 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
apiKey: payload?.apiKey,
|
||||
model: payload?.model,
|
||||
baseUrl: payload?.baseUrl,
|
||||
abortSignal: payload?.abortSignal,
|
||||
temperature: 0,
|
||||
maxOutputTokens: payload?.maxOutputTokens,
|
||||
promptVersion: "normalizer_v2_0_2",
|
||||
|
||||
+14
-1
@@ -15,6 +15,11 @@ const http_1 = require("../utils/http");
|
||||
const assistantService_1 = require("./assistantService");
|
||||
const assistantSessionStore_1 = require("./assistantSessionStore");
|
||||
const files_1 = require("../utils/files");
|
||||
function throwIfAborted(signal) {
|
||||
if (signal?.aborted) {
|
||||
throw new Error("EVAL_RUN_ABORTED");
|
||||
}
|
||||
}
|
||||
const BASELINE_METRICS = {
|
||||
schema_validation_pass_rate: 100,
|
||||
intent_class_accuracy: 72.73,
|
||||
@@ -1601,17 +1606,20 @@ class EvalService {
|
||||
const diagnostics = [];
|
||||
let requestsTotal = 0;
|
||||
for (const suiteCase of suiteCases) {
|
||||
throwIfAborted(payload.abortSignal);
|
||||
const sessionId = `${runId}-${suiteCase.case_id}`;
|
||||
const turnResponses = [];
|
||||
const notes = [];
|
||||
const limitations = [];
|
||||
try {
|
||||
for (const turn of suiteCase.turns) {
|
||||
throwIfAborted(payload.abortSignal);
|
||||
const response = (await assistantService.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: turn.user_message,
|
||||
message: turn.user_message,
|
||||
mode: "assistant",
|
||||
abortSignal: payload.abortSignal,
|
||||
llmProvider: payload.normalizeConfig.llmProvider,
|
||||
apiKey: payload.normalizeConfig.apiKey,
|
||||
model: payload.normalizeConfig.model,
|
||||
@@ -1636,10 +1644,14 @@ class EvalService {
|
||||
}));
|
||||
turnResponses.push(response);
|
||||
requestsTotal += 1;
|
||||
throwIfAborted(payload.abortSignal);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (errorMessage === "EVAL_RUN_ABORTED") {
|
||||
throw error;
|
||||
}
|
||||
diagnostics.push({
|
||||
suite_case: suiteCase,
|
||||
session_id: sessionId,
|
||||
@@ -2254,7 +2266,8 @@ class EvalService {
|
||||
caseSetFile: payload.caseSetFile,
|
||||
compareWithReportFile: payload.compareWithReportFile,
|
||||
analysisDate: analysisDate ?? undefined,
|
||||
runId: payload.runId
|
||||
runId: payload.runId,
|
||||
abortSignal: payload.abortSignal
|
||||
});
|
||||
}
|
||||
if (evalTarget === "assistant_stage2") {
|
||||
|
||||
@@ -1363,6 +1363,7 @@ class NormalizerService {
|
||||
apiKey: String(apiKey ?? ""),
|
||||
model,
|
||||
baseUrl,
|
||||
abortSignal: payload.abortSignal,
|
||||
temperature,
|
||||
maxOutputTokens
|
||||
}, {
|
||||
@@ -1408,6 +1409,7 @@ class NormalizerService {
|
||||
apiKey: String(payload.apiKey ?? process.env.OPENAI_API_KEY ?? ""),
|
||||
model,
|
||||
baseUrl,
|
||||
abortSignal: payload.abortSignal,
|
||||
temperature,
|
||||
maxOutputTokens: retryMaxOutputTokens
|
||||
}, {
|
||||
|
||||
@@ -408,7 +408,8 @@ class OpenAIResponsesClient {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: method === "POST" ? JSON.stringify(payload ?? {}) : undefined
|
||||
body: method === "POST" ? JSON.stringify(payload ?? {}) : undefined,
|
||||
signal: config.abortSignal
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ApiError, ok } from "../utils/http";
|
||||
import type { EvalRunMode, NormalizeRequestPayload } from "../types/normalizer";
|
||||
import type { EvalTarget } from "../types/assistantEval";
|
||||
|
||||
type EvalAsyncStatus = "queued" | "running" | "completed" | "failed";
|
||||
type EvalAsyncStatus = "queued" | "running" | "completed" | "failed" | "canceled";
|
||||
|
||||
interface EvalAsyncCaseInfo {
|
||||
case_id: string;
|
||||
@@ -41,6 +41,7 @@ interface EvalAsyncJob {
|
||||
cases: EvalAsyncCaseInfo[];
|
||||
error: string | null;
|
||||
report: Record<string, unknown> | null;
|
||||
abort_controller?: AbortController | null;
|
||||
}
|
||||
|
||||
const ASYNC_JOBS = new Map<string, EvalAsyncJob>();
|
||||
@@ -428,7 +429,7 @@ function readReportedCaseIds(job: EvalAsyncJob): Set<string> {
|
||||
}
|
||||
|
||||
function isTerminalCaseStatus(status: EvalAsyncStatus): boolean {
|
||||
return status === "completed" || status === "failed";
|
||||
return status === "completed" || status === "failed" || status === "canceled";
|
||||
}
|
||||
|
||||
function syncJobWithSessions(job: EvalAsyncJob): void {
|
||||
@@ -559,6 +560,7 @@ export function buildEvalRouter(services: AppServices): Router {
|
||||
}
|
||||
|
||||
const nowIso = new Date().toISOString();
|
||||
const abortController = new AbortController();
|
||||
const job: EvalAsyncJob = {
|
||||
job_id: jobId,
|
||||
status: "queued",
|
||||
@@ -577,7 +579,8 @@ export function buildEvalRouter(services: AppServices): Router {
|
||||
messages: []
|
||||
})),
|
||||
error: null,
|
||||
report: null
|
||||
report: null,
|
||||
abort_controller: abortController
|
||||
};
|
||||
ASYNC_JOBS.set(job.job_id, job);
|
||||
trimAsyncJobsStore();
|
||||
@@ -586,24 +589,47 @@ export function buildEvalRouter(services: AppServices): Router {
|
||||
void (async () => {
|
||||
const target = ASYNC_JOBS.get(job.job_id);
|
||||
if (!target) return;
|
||||
if (target.status === "canceled") {
|
||||
return;
|
||||
}
|
||||
target.status = "running";
|
||||
target.updated_at = new Date().toISOString();
|
||||
try {
|
||||
const report = await services.evalService.run({
|
||||
...payload,
|
||||
caseSetFile: runtimeCaseSetFile,
|
||||
runId
|
||||
runId,
|
||||
abortSignal: abortController.signal
|
||||
});
|
||||
target.report = report;
|
||||
syncJobWithSessions(target);
|
||||
target.completed_cases = target.total_cases;
|
||||
target.status = "completed";
|
||||
target.updated_at = new Date().toISOString();
|
||||
const latestAfterRun = ASYNC_JOBS.get(job.job_id);
|
||||
if (!latestAfterRun) {
|
||||
return;
|
||||
}
|
||||
if (latestAfterRun.status === "canceled") {
|
||||
latestAfterRun.updated_at = new Date().toISOString();
|
||||
return;
|
||||
}
|
||||
latestAfterRun.report = report;
|
||||
syncJobWithSessions(latestAfterRun);
|
||||
latestAfterRun.completed_cases = latestAfterRun.total_cases;
|
||||
latestAfterRun.status = "completed";
|
||||
latestAfterRun.updated_at = new Date().toISOString();
|
||||
latestAfterRun.abort_controller = null;
|
||||
} catch (error) {
|
||||
syncJobWithSessions(target);
|
||||
target.status = "failed";
|
||||
target.error = error instanceof Error ? error.message : String(error);
|
||||
target.updated_at = new Date().toISOString();
|
||||
const latestAfterError = ASYNC_JOBS.get(job.job_id);
|
||||
if (!latestAfterError) {
|
||||
return;
|
||||
}
|
||||
if (latestAfterError.status === "canceled") {
|
||||
latestAfterError.updated_at = new Date().toISOString();
|
||||
latestAfterError.abort_controller = null;
|
||||
return;
|
||||
}
|
||||
syncJobWithSessions(latestAfterError);
|
||||
latestAfterError.status = "failed";
|
||||
latestAfterError.error = error instanceof Error ? error.message : String(error);
|
||||
latestAfterError.updated_at = new Date().toISOString();
|
||||
latestAfterError.abort_controller = null;
|
||||
}
|
||||
})();
|
||||
});
|
||||
@@ -638,5 +664,35 @@ export function buildEvalRouter(services: AppServices): Router {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/api/eval/run-async/:job_id/cancel", (req, res, next) => {
|
||||
try {
|
||||
const jobId = String(req.params.job_id ?? "").trim();
|
||||
if (!jobId) {
|
||||
throw new ApiError("INVALID_ASYNC_JOB_ID", "job_id is required.", 400);
|
||||
}
|
||||
const job = ASYNC_JOBS.get(jobId);
|
||||
if (!job) {
|
||||
throw new ApiError("ASYNC_JOB_NOT_FOUND", `Async eval job not found: ${jobId}`, 404);
|
||||
}
|
||||
if (!isTerminalCaseStatus(job.status)) {
|
||||
job.status = "canceled";
|
||||
job.error = "Остановлено оператором.";
|
||||
job.updated_at = new Date().toISOString();
|
||||
job.abort_controller?.abort();
|
||||
job.abort_controller = null;
|
||||
job.cases = job.cases.map((item) =>
|
||||
item.status === "completed" ? item : { ...item, status: "canceled" }
|
||||
);
|
||||
syncJobWithSessions(job);
|
||||
}
|
||||
ok(res, {
|
||||
ok: true,
|
||||
job: snapshotJob(job)
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface RunAssistantLivingChatLlmRuntimeInput {
|
||||
apiKey?: unknown;
|
||||
model?: unknown;
|
||||
baseUrl?: unknown;
|
||||
abortSignal?: AbortSignal;
|
||||
temperature?: number;
|
||||
maxOutputTokens?: number;
|
||||
};
|
||||
@@ -35,6 +36,7 @@ export interface RunAssistantLivingChatLlmRuntimeInput {
|
||||
apiKey: string;
|
||||
model: string;
|
||||
baseUrl: unknown;
|
||||
abortSignal?: AbortSignal;
|
||||
temperature: number;
|
||||
maxOutputTokens: number;
|
||||
},
|
||||
@@ -104,6 +106,7 @@ export async function runAssistantLivingChatLlmRuntime(
|
||||
apiKey: String(input.payload.apiKey ?? input.defaultApiKey ?? ""),
|
||||
model: String(input.payload.model ?? input.defaultModel),
|
||||
baseUrl: input.payload.baseUrl ?? input.defaultBaseUrl,
|
||||
abortSignal: input.payload.abortSignal,
|
||||
temperature,
|
||||
maxOutputTokens
|
||||
},
|
||||
|
||||
@@ -144,6 +144,10 @@ function hasPriorAssistantTurn(items: unknown[]): boolean {
|
||||
return items.some((item) => item && typeof item === "object" && (item as { role?: string }).role === "assistant");
|
||||
}
|
||||
|
||||
function buildDeterministicSmalltalkLeadReply(): string {
|
||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
||||
}
|
||||
|
||||
function findLastAddressDebug(items: unknown[]): Record<string, unknown> | null {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
@@ -392,9 +396,11 @@ export async function runAssistantLivingChatRuntime(
|
||||
}
|
||||
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||
if (proactiveOffer) {
|
||||
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
|
||||
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
||||
.filter((part) => String(part ?? "").trim().length > 0)
|
||||
.join(" ");
|
||||
livingChatProactiveScopeOfferApplied = true;
|
||||
livingChatSource = "llm_chat_with_proactive_scope_offer";
|
||||
livingChatSource = "deterministic_smalltalk_with_proactive_scope_offer";
|
||||
if (!dataScopeProbe) {
|
||||
dataScopeProbe = proactiveScopeProbe;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export function createAssistantLivingModePolicy(deps: AssistantLivingModePolicyD
|
||||
}
|
||||
|
||||
function hasDataRetrievalRequestSignal(text) {
|
||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||
const lower = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
@@ -319,20 +319,33 @@ export function createAssistantLivingModePolicy(deps: AssistantLivingModePolicyD
|
||||
}
|
||||
|
||||
function hasLivingChatSignal(text) {
|
||||
const lower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||
if (!lower) {
|
||||
const rawLower = compactWhitespace(String(text ?? "").toLowerCase());
|
||||
const repairedLower = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
const variants = [rawLower, repairedLower].filter((value, index, items) => value.length > 0 && items.indexOf(value) === index);
|
||||
if (variants.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) {
|
||||
const hasDirectionalReply = variants.some((value) => /^(?:\u0430\s+)?(?:\u0442\u0443\u0442|\u0437\u0434\u0435\u0441\u044c|\u0442\u0430\u043c|\u0441\u044e\u0434\u0430|\u0442\u0443\u0434\u0430)[\s!?.,:;\-]*$/iu.test(value));
|
||||
if (hasDirectionalReply) {
|
||||
return true;
|
||||
}
|
||||
if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) {
|
||||
const hasShortGreeting = variants.some((value) => /^(?:\u0430\u0433\u0430|\u0443\u0433\u0443|\u043e\u043a|\u043e\u043a\u0435\u0439|\u044f\u0441\u043d\u043e|\u043f\u043e\u043d\u044f\u043b(?:\u0430)?|\u043f\u0440\u0438\u043d\u044f\u0442\u043e|\u0441\u043f\u0430\u0441\u0438\u0431\u043e|\u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e|\u0441\u0443\u043f\u0435\u0440|\u043a\u043b\u0430\u0441\u0441|\u043d\u043e\u0440\u043c|\u0433\u043e|\u0434\u0430\u0432\u0430\u0439|\u043f\u043e\u0433\u043d\u0430\u043b\u0438|\u043f\u0440\u0438\u0432\u0435\u0442(?:\u0438\u043a)?|\u0445\u0430\u0439|\u0439\u043e|yo|\u0447[её]\s+\u0442\u0430\u043c|\u0447[её]\s+\u043a\u0430\u043a|hello|hi|thanks?)$/iu.test(value));
|
||||
if (hasShortGreeting) {
|
||||
return true;
|
||||
}
|
||||
if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) {
|
||||
const hasGreetingWithFollowup = variants.some((value) => /(?:^|[\s,.!?;:()\-])(?:\u043f\u0440\u0438\u0432\u0435\u0442\p{L}*|\u0445\u0430\u0439|\u0439\u043e|hello|hi|yo)(?=$|[\s,.!?;:()\-]).*(?:^|[\s,.!?;:()\-])(?:\u043a\u0430\u043a|\u0434\u0435\u043b\u0430|\u0442\u0430\u043c|\u0447[её])(?=$|[\s,.!?;:()\-])/iu.test(value));
|
||||
if (hasGreetingWithFollowup) {
|
||||
return true;
|
||||
}
|
||||
return hasSmallTalkSignal(lower);
|
||||
const hasColloquialFollowup = variants.some((value) => /(?:^|[\s,.!?;:()\-])(?:\u0447[её]\s+\u043a\u0430\u043a|\u0447[её]\s+\u0442\u0430\u043c|\u043a\u0430\u043a\s+\u0442\u0430\u043c(?:\s+\u0434\u0435\u043b\u0430)?)(?=$|[\s,.!?;:()\-])/iu.test(value));
|
||||
if (hasColloquialFollowup) {
|
||||
return true;
|
||||
}
|
||||
const hasOpenSmalltalkPrompt = variants.some((value) => /(?:\u043a\u0430\u043a\s+\u0434\u0435\u043b\u0430|\u043a\u0430\u043a\s+\u0442\u044b|\u0447\u0442\u043e\s+\u043d\u043e\u0432\u043e\u0433\u043e|\u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0438\s+\u043e\s+\u0441\u0435\u0431\u0435|\u0447\u0435\u043c\s+\u043c\u043e\u0436\u0435\u0448\u044c\s+\u043f\u043e\u043c\u043e\u0447\u044c|\u0434\u0430\u0432\u0430\u0439\s+\u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c|\u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c|\u043e\u0431\u0441\u0443\u0434\u0438\u043c|\u043f\u043e\u0441\u043e\u0432\u0435\u0442\u0443\u0439|\u0447\u0442\u043e\s+\u0434\u0443\u043c\u0430\u0435\u0448\u044c)/iu.test(value));
|
||||
if (hasOpenSmalltalkPrompt) {
|
||||
return true;
|
||||
}
|
||||
return variants.some((value) => hasSmallTalkSignal(value));
|
||||
}
|
||||
|
||||
function resolveLivingAssistantModeDecision(input) {
|
||||
|
||||
@@ -3290,6 +3290,7 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
apiKey: payload?.apiKey,
|
||||
model: payload?.model,
|
||||
baseUrl: payload?.baseUrl,
|
||||
abortSignal: payload?.abortSignal,
|
||||
temperature: 0,
|
||||
maxOutputTokens: payload?.maxOutputTokens,
|
||||
promptVersion: "normalizer_v2_0_2",
|
||||
|
||||
@@ -54,6 +54,12 @@ import { AssistantSessionStore } from "./assistantSessionStore";
|
||||
import { NormalizerService } from "./normalizerService";
|
||||
import { ensureDir, writeJsonFile } from "../utils/files";
|
||||
|
||||
function throwIfAborted(signal?: AbortSignal): void {
|
||||
if (signal?.aborted) {
|
||||
throw new Error("EVAL_RUN_ABORTED");
|
||||
}
|
||||
}
|
||||
|
||||
interface EvalCaseFile {
|
||||
case_id: string;
|
||||
raw_question: string;
|
||||
@@ -1925,6 +1931,7 @@ export class EvalService {
|
||||
compareWithReportFile?: string;
|
||||
analysisDate?: string;
|
||||
runId?: string;
|
||||
abortSignal?: AbortSignal;
|
||||
}): Promise<Record<string, unknown>> {
|
||||
if (!FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1) {
|
||||
throw new ApiError(
|
||||
@@ -1943,6 +1950,7 @@ export class EvalService {
|
||||
let requestsTotal = 0;
|
||||
|
||||
for (const suiteCase of suiteCases) {
|
||||
throwIfAborted(payload.abortSignal);
|
||||
const sessionId = `${runId}-${suiteCase.case_id}`;
|
||||
const turnResponses: AssistantMessageResponsePayload[] = [];
|
||||
const notes: string[] = [];
|
||||
@@ -1950,11 +1958,13 @@ export class EvalService {
|
||||
|
||||
try {
|
||||
for (const turn of suiteCase.turns) {
|
||||
throwIfAborted(payload.abortSignal);
|
||||
const response = (await assistantService.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: turn.user_message,
|
||||
message: turn.user_message,
|
||||
mode: "assistant",
|
||||
abortSignal: payload.abortSignal,
|
||||
llmProvider: payload.normalizeConfig.llmProvider,
|
||||
apiKey: payload.normalizeConfig.apiKey,
|
||||
model: payload.normalizeConfig.model,
|
||||
@@ -1979,9 +1989,13 @@ export class EvalService {
|
||||
})) as AssistantMessageResponsePayload;
|
||||
turnResponses.push(response);
|
||||
requestsTotal += 1;
|
||||
throwIfAborted(payload.abortSignal);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (errorMessage === "EVAL_RUN_ABORTED") {
|
||||
throw error;
|
||||
}
|
||||
diagnostics.push({
|
||||
suite_case: suiteCase,
|
||||
session_id: sessionId,
|
||||
@@ -2634,6 +2648,7 @@ export class EvalService {
|
||||
compareWithReportFile?: string;
|
||||
analysisDate?: string;
|
||||
runId?: string;
|
||||
abortSignal?: AbortSignal;
|
||||
}): Promise<Record<string, unknown>> {
|
||||
const mode = payload.mode ?? "standard";
|
||||
const evalTarget = payload.evalTarget ?? "normalizer";
|
||||
@@ -2648,7 +2663,8 @@ export class EvalService {
|
||||
caseSetFile: payload.caseSetFile,
|
||||
compareWithReportFile: payload.compareWithReportFile,
|
||||
analysisDate: analysisDate ?? undefined,
|
||||
runId: payload.runId
|
||||
runId: payload.runId,
|
||||
abortSignal: payload.abortSignal
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1608,6 +1608,7 @@ export class NormalizerService {
|
||||
apiKey: String(apiKey ?? ""),
|
||||
model,
|
||||
baseUrl,
|
||||
abortSignal: payload.abortSignal,
|
||||
temperature,
|
||||
maxOutputTokens
|
||||
},
|
||||
@@ -1656,6 +1657,7 @@ export class NormalizerService {
|
||||
apiKey: String(payload.apiKey ?? process.env.OPENAI_API_KEY ?? ""),
|
||||
model,
|
||||
baseUrl,
|
||||
abortSignal: payload.abortSignal,
|
||||
temperature,
|
||||
maxOutputTokens: retryMaxOutputTokens
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface OpenAIRequestConfig {
|
||||
apiKey: string;
|
||||
model: string;
|
||||
baseUrl?: string;
|
||||
abortSignal?: AbortSignal;
|
||||
temperature?: number;
|
||||
maxOutputTokens?: number;
|
||||
}
|
||||
@@ -491,7 +492,8 @@ export class OpenAIResponsesClient {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: method === "POST" ? JSON.stringify(payload ?? {}) : undefined
|
||||
body: method === "POST" ? JSON.stringify(payload ?? {}) : undefined,
|
||||
signal: config.abortSignal
|
||||
});
|
||||
} catch (error) {
|
||||
lastNetworkError = error;
|
||||
|
||||
@@ -325,6 +325,7 @@ export interface AssistantMessageRequestPayload {
|
||||
user_message?: string;
|
||||
message?: string;
|
||||
mode?: "assistant" | string;
|
||||
abortSignal?: AbortSignal;
|
||||
llmProvider?: NormalizeRequestPayload["llmProvider"];
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
|
||||
@@ -267,6 +267,7 @@ export interface NormalizeRequestPayload {
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
baseUrl?: string;
|
||||
abortSignal?: AbortSignal;
|
||||
temperature?: number;
|
||||
maxOutputTokens?: number;
|
||||
promptVersion?: PromptVersion | string;
|
||||
|
||||
@@ -155,9 +155,10 @@ describe("assistant living chat runtime adapter", () => {
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("llm-text");
|
||||
expect(output.chatText).toContain("Привет! Всё нормально.");
|
||||
expect(output.chatText).toContain("offer:ООО Альтернатива Плюс, ООО Лайсвуд, РАЙМ");
|
||||
expect(output.debug?.living_chat_response_source).toBe("llm_chat_with_proactive_scope_offer");
|
||||
expect(output.chatText).not.toContain("llm-text");
|
||||
expect(output.debug?.living_chat_response_source).toBe("deterministic_smalltalk_with_proactive_scope_offer");
|
||||
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(true);
|
||||
expect(output.debug?.living_chat_data_scope_probe_org_count).toBe(3);
|
||||
expect(output.debug?.assistant_known_organizations).toEqual(["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createAssistantLivingModePolicy } from "../src/services/assistantLivingModePolicy";
|
||||
|
||||
function buildPolicy() {
|
||||
function buildPolicy(options?: { repairAddressMojibake?: (text: string) => string }) {
|
||||
return createAssistantLivingModePolicy({
|
||||
featureAssistantLivingChatRouterV1: true,
|
||||
compactWhitespace: (text: string) => text.replace(/\s+/g, " ").trim(),
|
||||
repairAddressMojibake: (text: string) => text,
|
||||
repairAddressMojibake: options?.repairAddressMojibake ?? ((text: string) => text),
|
||||
toNonEmptyString: (value: unknown) => {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
@@ -34,6 +34,8 @@ function buildPolicy() {
|
||||
}
|
||||
|
||||
describe("assistantLivingModePolicy", () => {
|
||||
const colloquialGreeting = "\u043f\u0440\u0438\u0432\u0435\u0442\u0438\u043a - \u0447\u0435 \u043a\u0430\u043a \u0442\u0430\u043c \u0434\u0435\u043b\u0430";
|
||||
|
||||
it("detects explicit recap wording as memory signal even when selected-object words are present", () => {
|
||||
const policy = buildPolicy();
|
||||
|
||||
@@ -63,6 +65,21 @@ describe("assistantLivingModePolicy", () => {
|
||||
expect(decision.reason).toBe("living_chat_signal_detected");
|
||||
});
|
||||
|
||||
it("detects colloquial greeting with extra words as living chat signal", () => {
|
||||
const policy = buildPolicy();
|
||||
|
||||
expect(policy.hasLivingChatSignal(colloquialGreeting)).toBe(true);
|
||||
});
|
||||
|
||||
it("detects mojibake colloquial greeting after repair", () => {
|
||||
const policy = buildPolicy({
|
||||
repairAddressMojibake: (text: string) =>
|
||||
text === "приветик - че как там дела" ? colloquialGreeting : text
|
||||
});
|
||||
|
||||
expect(policy.hasLivingChatSignal("приветик - че как там дела")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps explicit inventory question in deep mode", () => {
|
||||
const policy = buildPolicy();
|
||||
|
||||
|
||||
@@ -1,4 +1,90 @@
|
||||
[
|
||||
{
|
||||
"generation_id": "gen-ag04181425-56e29e",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 12 wider saved-session replay for multi-trajectory organization authority",
|
||||
"count": 20,
|
||||
"domain": "address_phase12_wider_saved_session_pool",
|
||||
"questions": [
|
||||
"приветик - че как там дела",
|
||||
"Альтернатива Плюс",
|
||||
"расскажи что можешь интересного",
|
||||
"кайф - что там на складе по остаткам?",
|
||||
"а исторические остатки на другие даты умеешь?",
|
||||
"март 2016",
|
||||
"По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?",
|
||||
"а кому продали?",
|
||||
"ндс можешь прикинуть на дату покупки рабочей станции?",
|
||||
"кто у нас самый доходный клиент за все время",
|
||||
"кто нам должен денег на май 2017",
|
||||
"а какой ндс мы должны примерно заплатить за этот период?",
|
||||
"мы должны комуто денег на сегодня?",
|
||||
"а нам?",
|
||||
"ты умеешь считать дельту по договорам?",
|
||||
"по чепурнову покажи все доки",
|
||||
"а по свк",
|
||||
"хвосты покажи по счету 60 на август 2022",
|
||||
"Есть ли остатки товара, которые закупались очень давно",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260418142537_gen-ag04181425-56e29e.json",
|
||||
"context": {
|
||||
"llm_provider": null,
|
||||
"model": null,
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"autogen_personality_id": null,
|
||||
"autogen_personality_prompt": null,
|
||||
"source_session_id": null,
|
||||
"saved_session_file": "assistant_saved_session_20260418142537_gen-ag04181425-56e29e.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "Broad AGENT replay built from the repaired manual session contour and extended with explicit company selection. The scenario validates proactive multi-company entry, capability meta honesty, inventory roots and history, selected-object carryover, VAT bridges and period carryover, receivables/payables polarity flip, counterparty document follow-ups, account 60 tails, old-purchase inventory aging, and organization activity age in one long live session.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase12_wider_saved_session_pool.json",
|
||||
"scenario_id": "address_truth_harness_phase12_wider_saved_session_pool",
|
||||
"semantic_tags": [
|
||||
"aggregate_revenue",
|
||||
"bridge_inventory_to_vat",
|
||||
"capability_over_followup",
|
||||
"company_authority",
|
||||
"company_selected",
|
||||
"counterparty_followup",
|
||||
"counterparty_root",
|
||||
"cross_domain_pivot",
|
||||
"date_carryover",
|
||||
"display_label_integrity",
|
||||
"documents",
|
||||
"historical_date_anchor",
|
||||
"human_answer_quality",
|
||||
"inventory_aging",
|
||||
"inventory_provenance",
|
||||
"inventory_root",
|
||||
"inventory_sale_trace",
|
||||
"late_session_stability",
|
||||
"meta_capability",
|
||||
"meta_historical_capability",
|
||||
"meta_smalltalk",
|
||||
"organization_activity_age",
|
||||
"organization_authority",
|
||||
"proactive_scope_offer",
|
||||
"same_date_restore",
|
||||
"same_period_restore",
|
||||
"selected_object",
|
||||
"settlements_account_60",
|
||||
"settlements_mirror_followup",
|
||||
"settlements_payables",
|
||||
"settlements_receivables",
|
||||
"tail_authority_proof",
|
||||
"today_scope",
|
||||
"vat_followup"
|
||||
],
|
||||
"latest_acceptance": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag04171508-760111",
|
||||
"created_at": "2026-04-17T15:08:06+00:00",
|
||||
@@ -389,12 +475,13 @@
|
||||
"created_at": "2026-04-16T18:26:26.191Z",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"count": 31,
|
||||
"count": 33,
|
||||
"domain": null,
|
||||
"questions": [
|
||||
"приветик - че как там дела",
|
||||
"расскажи что можешь интересного",
|
||||
"кайф - что там на складе по остаткам?",
|
||||
"АЛЬТЕРНАТИВА",
|
||||
"а исторические остатки на другие даты умеешь?",
|
||||
"давай на июль 2017",
|
||||
"март 2016",
|
||||
@@ -422,7 +509,8 @@
|
||||
"хвосты покажи по счету 60 на август 2022",
|
||||
"Есть ли остатки товара, которые закупались очень давно",
|
||||
"Какие конкретно номенклатуры формируют остаток по складу на май 2020",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"Как ты оценишь деятельность компании?"
|
||||
],
|
||||
"generated_by": "manual_reviewer",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260416182626_gen-mo1t93wq-jy0453e.json",
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
{
|
||||
"saved_at": "2026-04-18T14:25:37+00:00",
|
||||
"generation_id": "gen-ag04181425-56e29e",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 12 wider saved-session replay for multi-trajectory organization authority",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"приветик - че как там дела",
|
||||
"Альтернатива Плюс",
|
||||
"расскажи что можешь интересного",
|
||||
"кайф - что там на складе по остаткам?",
|
||||
"а исторические остатки на другие даты умеешь?",
|
||||
"март 2016",
|
||||
"По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?",
|
||||
"а кому продали?",
|
||||
"ндс можешь прикинуть на дату покупки рабочей станции?",
|
||||
"кто у нас самый доходный клиент за все время",
|
||||
"кто нам должен денег на май 2017",
|
||||
"а какой ндс мы должны примерно заплатить за этот период?",
|
||||
"мы должны комуто денег на сегодня?",
|
||||
"а нам?",
|
||||
"ты умеешь считать дельту по договорам?",
|
||||
"по чепурнову покажи все доки",
|
||||
"а по свк",
|
||||
"хвосты покажи по счету 60 на август 2022",
|
||||
"Есть ли остатки товара, которые закупались очень давно",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Broad AGENT replay built from the repaired manual session contour and extended with explicit company selection. The scenario validates proactive multi-company entry, capability meta honesty, inventory roots and history, selected-object carryover, VAT bridges and period carryover, receivables/payables polarity flip, counterparty document follow-ups, account 60 tails, old-purchase inventory aging, and organization activity age in one long live session.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase12_wider_saved_session_pool.json",
|
||||
"scenario_id": "address_truth_harness_phase12_wider_saved_session_pool",
|
||||
"semantic_tags": [
|
||||
"aggregate_revenue",
|
||||
"bridge_inventory_to_vat",
|
||||
"capability_over_followup",
|
||||
"company_authority",
|
||||
"company_selected",
|
||||
"counterparty_followup",
|
||||
"counterparty_root",
|
||||
"cross_domain_pivot",
|
||||
"date_carryover",
|
||||
"display_label_integrity",
|
||||
"documents",
|
||||
"historical_date_anchor",
|
||||
"human_answer_quality",
|
||||
"inventory_aging",
|
||||
"inventory_provenance",
|
||||
"inventory_root",
|
||||
"inventory_sale_trace",
|
||||
"late_session_stability",
|
||||
"meta_capability",
|
||||
"meta_historical_capability",
|
||||
"meta_smalltalk",
|
||||
"organization_activity_age",
|
||||
"organization_authority",
|
||||
"proactive_scope_offer",
|
||||
"same_date_restore",
|
||||
"same_period_restore",
|
||||
"selected_object",
|
||||
"settlements_account_60",
|
||||
"settlements_mirror_followup",
|
||||
"settlements_payables",
|
||||
"settlements_receivables",
|
||||
"tail_authority_proof",
|
||||
"today_scope",
|
||||
"vat_followup"
|
||||
]
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "приветик - че как там дела",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "Альтернатива Плюс",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "расскажи что можешь интересного",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "кайф - что там на складе по остаткам?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "а исторические остатки на другие даты умеешь?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "март 2016",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "а кому продали?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "ндс можешь прикинуть на дату покупки рабочей станции?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "кто у нас самый доходный клиент за все время",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "кто нам должен денег на май 2017",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "а какой ндс мы должны примерно заплатить за этот период?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "мы должны комуто денег на сегодня?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-014",
|
||||
"role": "user",
|
||||
"text": "а нам?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-015",
|
||||
"role": "user",
|
||||
"text": "ты умеешь считать дельту по договорам?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-016",
|
||||
"role": "user",
|
||||
"text": "по чепурнову покажи все доки",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-017",
|
||||
"role": "user",
|
||||
"text": "а по свк",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-018",
|
||||
"role": "user",
|
||||
"text": "хвосты покажи по счету 60 на август 2022",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-019",
|
||||
"role": "user",
|
||||
"text": "Есть ли остатки товара, которые закупались очень давно",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-020",
|
||||
"role": "user",
|
||||
"text": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"created_at": "2026-04-18T14:25:37+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
}
|
||||
],
|
||||
"agent_run": true,
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Broad AGENT replay built from the repaired manual session contour and extended with explicit company selection. The scenario validates proactive multi-company entry, capability meta honesty, inventory roots and history, selected-object carryover, VAT bridges and period carryover, receivables/payables polarity flip, counterparty document follow-ups, account 60 tails, old-purchase inventory aging, and organization activity age in one long live session.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase12_wider_saved_session_pool.json",
|
||||
"scenario_id": "address_truth_harness_phase12_wider_saved_session_pool",
|
||||
"semantic_tags": [
|
||||
"aggregate_revenue",
|
||||
"bridge_inventory_to_vat",
|
||||
"capability_over_followup",
|
||||
"company_authority",
|
||||
"company_selected",
|
||||
"counterparty_followup",
|
||||
"counterparty_root",
|
||||
"cross_domain_pivot",
|
||||
"date_carryover",
|
||||
"display_label_integrity",
|
||||
"documents",
|
||||
"historical_date_anchor",
|
||||
"human_answer_quality",
|
||||
"inventory_aging",
|
||||
"inventory_provenance",
|
||||
"inventory_root",
|
||||
"inventory_sale_trace",
|
||||
"late_session_stability",
|
||||
"meta_capability",
|
||||
"meta_historical_capability",
|
||||
"meta_smalltalk",
|
||||
"organization_activity_age",
|
||||
"organization_authority",
|
||||
"proactive_scope_offer",
|
||||
"same_date_restore",
|
||||
"same_period_restore",
|
||||
"selected_object",
|
||||
"settlements_account_60",
|
||||
"settlements_mirror_followup",
|
||||
"settlements_payables",
|
||||
"settlements_receivables",
|
||||
"tail_authority_proof",
|
||||
"today_scope",
|
||||
"vat_followup"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -2,7 +2,7 @@
|
||||
"suite_id": "assistant_saved_session_gen-mo1t93wq-jy0453e",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-04-18T11:29:49.384Z",
|
||||
"generated_at": "2026-04-18T14:55:46.799Z",
|
||||
"generation_id": "gen-mo1t93wq-jy0453e",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
@@ -27,6 +27,9 @@
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag04181425-56e29e",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-04-18T14:25:37+00:00",
|
||||
"generation_id": "gen-ag04181425-56e29e",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 12 wider saved-session replay for multi-trajectory organization authority",
|
||||
"domain": "address_phase12_wider_saved_session_pool",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Phase 12 wider saved-session replay for multi-trajectory organization authority",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "Альтернатива Плюс"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-IXCkeIOaTz",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-W3Bqj1Q5Ar",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-lHMIky2oa-",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "AGENT | Phase 6 provider/runtime replay across chat, meta, and address boundaries",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "AGENT | Phase 6 provider/runtime replay across chat, meta, and address boundaries",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет, как дела?"
|
||||
},
|
||||
{
|
||||
"user_message": "по какой компании мы сейчас работаем?"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты можешь по 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на март 2021"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки тоже можешь?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-uh5xR80M-d",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "shared_llm_connection_v1",
|
||||
"updated_at": "2026-04-17T10:55:35.860Z",
|
||||
"updated_at": "2026-04-18T14:51:55.518Z",
|
||||
"connection": {
|
||||
"llmProvider": "local",
|
||||
"model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NDC AI Normalizer Playground</title>
|
||||
<script type="module" crossorigin src="/assets/index-CLCo6iY2.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DkvyfMZP.css">
|
||||
<script type="module" crossorigin src="/assets/index-DWgwfAPc.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-sbJz56_k.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -270,6 +270,12 @@ export const apiClient = {
|
||||
return request(`/eval/run-async/${encodeURIComponent(jobId)}`);
|
||||
},
|
||||
|
||||
async cancelEvalRunAsync(jobId: string): Promise<AsyncEvalRunStatusResponse> {
|
||||
return request(`/eval/run-async/${encodeURIComponent(jobId)}/cancel`, {
|
||||
method: "POST"
|
||||
});
|
||||
},
|
||||
|
||||
async startRun(): Promise<{ ok: boolean; run: RuntimeRun; runId: string; sessionId: string; status: string }> {
|
||||
return request("/accounting-agent/v1/runs/start", {
|
||||
method: "POST",
|
||||
|
||||
@@ -612,6 +612,14 @@ function CardLaunchIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function CardStopIcon() {
|
||||
return (
|
||||
<svg className="autoruns-card-stop-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
||||
<rect x="4.2" y="4.2" width="7.6" height="7.6" rx="0.8" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function GroupChevronIcon({ expanded }: { expanded: boolean }) {
|
||||
return (
|
||||
<svg className={expanded ? "autoruns-group-chevron-svg expanded" : "autoruns-group-chevron-svg"} viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
||||
@@ -702,6 +710,8 @@ export function AutoRunsHistoryPanel({
|
||||
const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit));
|
||||
const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count));
|
||||
const [autogenPersonalityPromptHeight, setAutogenPersonalityPromptHeight] = useState(160);
|
||||
const [runningAutogenGenerationId, setRunningAutogenGenerationId] = useState("");
|
||||
const [autogenStopBusy, setAutogenStopBusy] = useState(false);
|
||||
const [filtersGroupExpanded, setFiltersGroupExpanded] = useState(true);
|
||||
const [generationContextGroupExpanded, setGenerationContextGroupExpanded] = useState(true);
|
||||
const [autogenGroupExpanded, setAutogenGroupExpanded] = useState(true);
|
||||
@@ -1507,6 +1517,8 @@ export function AutoRunsHistoryPanel({
|
||||
if (payload.job.status === "completed") {
|
||||
stopAsyncJobPolling();
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
const finalRunId = payload.job.report_summary?.run_id ?? payload.job.run_id;
|
||||
await loadHistory({
|
||||
keepSelection: true,
|
||||
@@ -1521,11 +1533,25 @@ export function AutoRunsHistoryPanel({
|
||||
if (payload.job.status === "failed") {
|
||||
stopAsyncJobPolling();
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
setErrorText(`Запуск прогонов: ${payload.job.error ?? "неизвестная ошибка"}`);
|
||||
log(`Autogen async run failed: ${payload.job.error ?? "unknown error"}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.job.status === "canceled") {
|
||||
stopAsyncJobPolling();
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
setActiveAsyncJob(null);
|
||||
await loadHistory({ keepSelection: false });
|
||||
await loadAutoGenHistory();
|
||||
log(`Autogen async run canceled: job=${payload.job.job_id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
stopAsyncJobPolling();
|
||||
asyncJobPollTimerRef.current = window.setTimeout(() => {
|
||||
void pollAsyncJobStatus(jobId);
|
||||
@@ -1533,6 +1559,8 @@ export function AutoRunsHistoryPanel({
|
||||
} catch (error) {
|
||||
stopAsyncJobPolling();
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setErrorText(`Запуск прогонов: ${message}`);
|
||||
log(`Autogen async status error: ${message}`);
|
||||
@@ -1579,6 +1607,8 @@ export function AutoRunsHistoryPanel({
|
||||
});
|
||||
|
||||
const liveJob = payload.job;
|
||||
setRunningAutogenGenerationId(generation.generation_id);
|
||||
setAutogenStopBusy(false);
|
||||
setActiveAsyncJob(liveJob);
|
||||
const liveRunId = toLiveRunId(liveJob.job_id);
|
||||
const live = buildLiveRunDetail(liveJob, ALL_CASES_ID);
|
||||
@@ -1602,6 +1632,8 @@ export function AutoRunsHistoryPanel({
|
||||
setErrorText(`Запуск прогонов: ${message}`);
|
||||
log(`Autogen run error: ${message}`);
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
}
|
||||
}, [
|
||||
analysisDate,
|
||||
@@ -1616,6 +1648,36 @@ export function AutoRunsHistoryPanel({
|
||||
stopAsyncJobPolling
|
||||
]);
|
||||
|
||||
const stopAutogenCampaign = useCallback(async () => {
|
||||
const jobId = activeAsyncJob?.job_id ?? "";
|
||||
if (!jobId) {
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
setActiveAsyncJob(null);
|
||||
stopAsyncJobPolling();
|
||||
return;
|
||||
}
|
||||
setAutogenStopBusy(true);
|
||||
setErrorText("");
|
||||
try {
|
||||
const payload = await apiClient.cancelEvalRunAsync(jobId);
|
||||
stopAsyncJobPolling();
|
||||
setActiveAsyncJob(null);
|
||||
setAutogenRunBusy(false);
|
||||
setAutogenStopBusy(false);
|
||||
setRunningAutogenGenerationId("");
|
||||
await loadHistory({ keepSelection: false });
|
||||
await loadAutoGenHistory();
|
||||
log(`Autogen async run stopped: job=${payload.job.job_id}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setAutogenStopBusy(false);
|
||||
setErrorText(`Остановка прогона: ${message}`);
|
||||
log(`Autogen stop error: ${message}`);
|
||||
}
|
||||
}, [activeAsyncJob, loadAutoGenHistory, loadHistory, log, stopAsyncJobPolling]);
|
||||
|
||||
const openCommentModal = useCallback((message: AutoRunDialogMessage) => {
|
||||
if (message.role !== "assistant") return;
|
||||
const resolvedCaseId = message.case_id ?? selectedCaseId;
|
||||
@@ -2750,10 +2812,13 @@ export function AutoRunsHistoryPanel({
|
||||
type="button"
|
||||
className="autoruns-run-launch-btn"
|
||||
style={isSavedUserSessionsMode ? { display: "none" } : undefined}
|
||||
disabled={autogenRunBusy || editableGeneratedQuestions.length === 0 || !selectedAutogenGeneration}
|
||||
onClick={() => void runAutogenCampaign()}
|
||||
disabled={
|
||||
autogenStopBusy ||
|
||||
(!autogenRunBusy && (editableGeneratedQuestions.length === 0 || !selectedAutogenGeneration))
|
||||
}
|
||||
onClick={() => void (autogenRunBusy ? stopAutogenCampaign() : runAutogenCampaign())}
|
||||
>
|
||||
{autogenRunBusy ? "Запускаю..." : "Запустить прогон"}
|
||||
{autogenRunBusy ? (autogenStopBusy ? "Останавливаю..." : "Остановить прогон") : "Запустить прогон"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2955,7 +3020,10 @@ export function AutoRunsHistoryPanel({
|
||||
{isSavedUserSessionsMode ? "Сохраненные пользовательские сессии пока пусты." : "История автогенераций пока пустая."}
|
||||
</p>
|
||||
) : null}
|
||||
{visibleAutoGenHistory.slice(0, 30).map((item) => (
|
||||
{visibleAutoGenHistory.slice(0, 30).map((item) => {
|
||||
const isRunningThisGeneration = autogenRunBusy && runningAutogenGenerationId === item.generation_id;
|
||||
const isAnotherGenerationRunning = autogenRunBusy && runningAutogenGenerationId !== item.generation_id;
|
||||
return (
|
||||
<article
|
||||
key={item.generation_id}
|
||||
className={[
|
||||
@@ -2971,20 +3039,24 @@ export function AutoRunsHistoryPanel({
|
||||
<button
|
||||
type="button"
|
||||
className="autoruns-saved-session-icon-btn"
|
||||
disabled={autogenRunBusy}
|
||||
disabled={autogenStopBusy || isAnotherGenerationRunning}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (isRunningThisGeneration) {
|
||||
void stopAutogenCampaign();
|
||||
return;
|
||||
}
|
||||
setSelectedAutogenGenerationId(item.generation_id);
|
||||
void runAutogenCampaign(
|
||||
item,
|
||||
selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions
|
||||
);
|
||||
}}
|
||||
title="Запустить прогон"
|
||||
aria-label={`Запустить прогон для ${formatAutoGenGenerationTitle(item)}`}
|
||||
title={isRunningThisGeneration ? "Остановить прогон" : "Запустить прогон"}
|
||||
aria-label={`${isRunningThisGeneration ? "Остановить прогон" : "Запустить прогон"} для ${formatAutoGenGenerationTitle(item)}`}
|
||||
>
|
||||
<CardLaunchIcon />
|
||||
{isRunningThisGeneration ? <CardStopIcon /> : <CardLaunchIcon />}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -3182,7 +3254,8 @@ export function AutoRunsHistoryPanel({
|
||||
</>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -392,13 +392,13 @@ export interface AutoGenPersonalityCatalogResponse {
|
||||
export interface AsyncEvalRunCase {
|
||||
case_id: string;
|
||||
turns_total: number;
|
||||
status: "queued" | "running" | "completed" | "failed";
|
||||
status: "queued" | "running" | "completed" | "failed" | "canceled";
|
||||
messages: AutoRunDialogMessage[];
|
||||
}
|
||||
|
||||
export interface AsyncEvalRunJob {
|
||||
job_id: string;
|
||||
status: "queued" | "running" | "completed" | "failed";
|
||||
status: "queued" | "running" | "completed" | "failed" | "canceled";
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
eval_target: AutoRunTarget;
|
||||
|
||||
@@ -1627,6 +1627,13 @@ button:disabled {
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.autoruns-card-stop-svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.autoruns-card-chevron-svg.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user