АДРЕСНЫЙ РЕЖИМ - стабилизация LLM fallback якорей и периода
This commit is contained in:
@@ -6,6 +6,7 @@ import { extractAddressFilters } from "../src/services/addressFilterExtractor";
|
||||
import { AddressQueryService } from "../src/services/addressQueryService";
|
||||
import { buildAddressRecipePlan, selectAddressRecipe } from "../src/services/addressRecipeCatalog";
|
||||
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||||
import { composeFactualReply } from "../src/services/address_runtime/composeStage";
|
||||
|
||||
describe("address query shape classifier", () => {
|
||||
it("classifies explain question as deep-shape", () => {
|
||||
@@ -35,6 +36,36 @@ describe("address query shape classifier", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("address compose stage utf8 headers", () => {
|
||||
it("renders readable russian header for contract document list", () => {
|
||||
const reply = composeFactualReply("list_documents_by_contract", [
|
||||
{
|
||||
period: "2020-10-15T13:34:53Z",
|
||||
registrator: "Списание с расчетного счета 00000000246",
|
||||
account_dt: "66.02",
|
||||
account_kt: "51",
|
||||
amount: 30819.47,
|
||||
analytics: []
|
||||
}
|
||||
]);
|
||||
expect(reply.text).toContain("Собран список документов по договору (live address lane).");
|
||||
});
|
||||
|
||||
it("renders readable russian header for contract bank operations", () => {
|
||||
const reply = composeFactualReply("bank_operations_by_contract", [
|
||||
{
|
||||
period: "2020-10-15T13:34:53Z",
|
||||
registrator: "Списание с расчетного счета 00000000246",
|
||||
account_dt: "66.02",
|
||||
account_kt: "51",
|
||||
amount: 30819.47,
|
||||
analytics: []
|
||||
}
|
||||
]);
|
||||
expect(reply.text).toContain("Собран список банковских операций по договору (live address lane).");
|
||||
});
|
||||
});
|
||||
|
||||
describe("address intent resolver expansion (M2.3a)", () => {
|
||||
it("resolves documents by counterparty intent", () => {
|
||||
const result = resolveAddressIntent("show documents by counterparty Alfa from 2020-07-01 to 2020-07-31");
|
||||
@@ -66,6 +97,23 @@ describe("address intent resolver expansion (M2.3a)", () => {
|
||||
expect(result.intent).toBe("bank_operations_by_counterparty");
|
||||
});
|
||||
|
||||
it("resolves documents by contract intent", () => {
|
||||
const result = resolveAddressIntent("Покажи документы по договору 15/24 за 2020");
|
||||
expect(result.intent).toBe("list_documents_by_contract");
|
||||
});
|
||||
|
||||
it("resolves bank operations by contract intent", () => {
|
||||
const result = resolveAddressIntent("Покажи банковские операции по договору 15/24");
|
||||
expect(result.intent).toBe("bank_operations_by_contract");
|
||||
});
|
||||
|
||||
it("resolves bank operations by contract for normalized phrase with linked contract wording", () => {
|
||||
const result = resolveAddressIntent(
|
||||
"Показать банковские операции (счета 51, 60, 62) связанные с договором 15/24."
|
||||
);
|
||||
expect(result.intent).toBe("bank_operations_by_contract");
|
||||
});
|
||||
|
||||
it("keeps bank_operations_by_counterparty even when account hints are present", () => {
|
||||
const result = resolveAddressIntent("Показать банковские операции (счета 51, 62) для контрагента СВК за 2020 год");
|
||||
expect(result.intent).toBe("bank_operations_by_counterparty");
|
||||
@@ -287,6 +335,49 @@ describe("address filter extraction for balance drilldown", () => {
|
||||
expect(result.warnings).toContain("period_derived_from_year_range_phrase");
|
||||
});
|
||||
|
||||
it("extracts contract and year period for contract document list", () => {
|
||||
const result = extractAddressFilters(
|
||||
"Покажи документы по договору 15/24 за 2020 год",
|
||||
"list_documents_by_contract"
|
||||
);
|
||||
expect(result.extracted_filters.contract).toBe("15/24");
|
||||
expect(result.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result.extracted_filters.period_to).toBe("2020-12-31");
|
||||
expect(result.warnings).toContain("period_derived_from_year_phrase");
|
||||
});
|
||||
|
||||
it("cuts trailing as-of date from contract anchor", () => {
|
||||
const result = extractAddressFilters(
|
||||
"Покажи документы по договору 1-ПМ/2020 на дату 31.07.2020",
|
||||
"list_documents_by_contract"
|
||||
);
|
||||
expect(result.extracted_filters.contract).toBe("1-ПМ/2020");
|
||||
expect(result.extracted_filters.period_from).toBe("2020-07-01");
|
||||
expect(result.extracted_filters.period_to).toBe("2020-07-31");
|
||||
});
|
||||
|
||||
it("does not force 90-day default window for by-contract query without explicit period", () => {
|
||||
const result = extractAddressFilters(
|
||||
"Покажи документы по договору 15/24",
|
||||
"list_documents_by_contract"
|
||||
);
|
||||
expect(result.extracted_filters.contract).toBe("15/24");
|
||||
expect(result.extracted_filters.period_from).toBeUndefined();
|
||||
expect(result.extracted_filters.period_to).toBeUndefined();
|
||||
expect(result.warnings).not.toContain("period_defaulted_last_90_days");
|
||||
});
|
||||
|
||||
it("extracts heuristic contract token for noisy contract phrase", () => {
|
||||
const result = extractAddressFilters(
|
||||
"доки 15/24 за 2020",
|
||||
"list_documents_by_contract"
|
||||
);
|
||||
expect(result.extracted_filters.contract).toBe("15/24");
|
||||
expect(result.warnings).toContain("contract_anchor_derived_from_heuristic_token");
|
||||
expect(result.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result.extracted_filters.period_to).toBe("2020-12-31");
|
||||
});
|
||||
|
||||
it("extracts multiline year range period from phrase", () => {
|
||||
const result = extractAddressFilters(
|
||||
"Какие документы по СВК за 2000 - 2025\n год?",
|
||||
@@ -334,13 +425,24 @@ describe("address query limited taxonomy and stage diagnostics", () => {
|
||||
expect(result?.debug.mcp_call_status).toBe("skipped");
|
||||
});
|
||||
|
||||
it("returns unsupported for not-implemented contract document list intent", async () => {
|
||||
it("routes contract document list intent into address recipe", async () => {
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle("show documents by contract 15/24");
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.response_type).toBe("LIMITED_WITH_REASON");
|
||||
expect(result?.debug.limited_reason_category).toBe("unsupported");
|
||||
expect(result?.debug.mcp_call_status).toBe("skipped");
|
||||
expect(result?.debug.detected_intent).toBe("list_documents_by_contract");
|
||||
expect(result?.debug.selected_recipe).toBe("address_documents_by_contract_v1");
|
||||
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
|
||||
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
||||
});
|
||||
|
||||
it("routes bank operations by contract intent into address recipe", async () => {
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle("Покажи банковские операции по договору 15/24");
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.debug.detected_intent).toBe("bank_operations_by_contract");
|
||||
expect(result?.debug.selected_recipe).toBe("address_bank_operations_by_contract_v1");
|
||||
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
|
||||
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
||||
});
|
||||
|
||||
it("includes resolver and row-stage diagnostics", async () => {
|
||||
@@ -461,6 +563,24 @@ describe("address decompose stage follow-up carryover", () => {
|
||||
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
||||
expect(result?.baseReasons).toContain("address_followup_context_applied");
|
||||
});
|
||||
|
||||
it("does not downgrade inherited follow-up anchor to missing_anchor when period has no rows", async () => {
|
||||
const service = new AddressQueryService();
|
||||
const seed = await service.tryHandle("покажи документы по свк за 2020");
|
||||
expect(seed?.handled).toBe(true);
|
||||
const followup = await service.tryHandle("а теперь только за май 2020", {
|
||||
followupContext: {
|
||||
previous_intent: (seed?.debug.detected_intent as any) ?? "list_documents_by_counterparty",
|
||||
previous_filters: seed?.debug.extracted_filters,
|
||||
previous_anchor_type: (seed?.debug.anchor_type as any) ?? "counterparty",
|
||||
previous_anchor_value: seed?.debug.anchor_value_resolved ?? seed?.debug.anchor_value_raw ?? null
|
||||
}
|
||||
});
|
||||
expect(followup?.handled).toBe(true);
|
||||
if (followup?.reply_type === "partial_coverage") {
|
||||
expect(followup?.debug.limited_reason_category).not.toBe("missing_anchor");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("address recipe catalog counterparty filtering", () => {
|
||||
|
||||
@@ -215,4 +215,391 @@ describe("assistant address llm pre-decompose candidate preference", () => {
|
||||
expect(response.debug?.sanitized_user_message).toContain("свк");
|
||||
expect(response.debug?.tool_gate_decision).toBe("run_address_lane");
|
||||
});
|
||||
|
||||
it("keeps contract anchor in deterministic fallback when llm output is unusable", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-contract-docs",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "Покажи документы по договору 15/24 за 2020",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-contract-docs-${Date.now()}`,
|
||||
user_message: "Покажи документы по договору 15/24 за 2020",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe("документы по договору 15/24 за 2020 год");
|
||||
expect(response.debug?.llm_decomposition_applied).toBe(true);
|
||||
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||
expect(response.debug?.fallback_rule_hit).toBe("documents_contract_year_rewrite");
|
||||
});
|
||||
|
||||
it("keeps bank-by-contract intent in deterministic fallback when llm output is unusable", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-contract-bank",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "Покажи банковские операции по договору 15/24",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-contract-bank-${Date.now()}`,
|
||||
user_message: "Покажи банковские операции по договору 15/24",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe("банковские операции по договору 15/24");
|
||||
expect(response.debug?.llm_decomposition_applied).toBe(true);
|
||||
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||
expect(response.debug?.fallback_rule_hit).toBe("bank_operations_contract_rewrite");
|
||||
});
|
||||
|
||||
it("keeps month scope for balance fallback in 'year month' phrasing", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-balance-year-month",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "Какой остаток по счету 60 на 2020 май",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-balance-year-month-${Date.now()}`,
|
||||
user_message: "Какой остаток по счету 60 на 2020 май",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe("остаток по счету 60 на 2020-05");
|
||||
expect(response.debug?.llm_decomposition_applied).toBe(true);
|
||||
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||
expect(response.debug?.fallback_rule_hit).toBe("balance_month_period_rewrite");
|
||||
});
|
||||
|
||||
it("does not pick service words as counterparty anchor in noisy docs query", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-noisy-counterparty",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "что по свк за 2020 год выведи все доки плиз что есть",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-noisy-counterparty-${Date.now()}`,
|
||||
user_message: "что по свк за 2020 год выведи все доки плиз что есть",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe("документы по контрагенту свк за 2020 год");
|
||||
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||
expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite");
|
||||
});
|
||||
|
||||
it("rewrites payment-style counterparty phrasing to bank operations", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-bank-counterparty",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "какие платежи были по свк в 2020",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-bank-counterparty-${Date.now()}`,
|
||||
user_message: "какие платежи были по свк в 2020",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe("банковские операции по контрагенту свк за 2020 год");
|
||||
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||
expect(response.debug?.fallback_rule_hit).toBe("bank_operations_counterparty_year_rewrite");
|
||||
});
|
||||
|
||||
it("normalizes short ordinal year like '20й' in noisy docs phrasing", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-short-ordinal-year",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "бля епт покажи доки по свк за 20й",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-short-ordinal-year-${Date.now()}`,
|
||||
user_message: "бля епт покажи доки по свк за 20й",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe("документы по контрагенту свк за 2020 год");
|
||||
expect(response.debug?.llm_decomposition_reason).toBe("fallback_rule_applied_after_llm");
|
||||
expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite");
|
||||
});
|
||||
|
||||
it("does not treat date fragments as account in balance fallback", async () => {
|
||||
const calls: Array<{ message: string }> = [];
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string) => {
|
||||
calls.push({ message });
|
||||
return buildAddressLaneResult(message);
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
trace_id: "norm-predecompose-date-not-account",
|
||||
ok: true,
|
||||
normalized: {
|
||||
schema_version: "normalized_query_v2_0_2",
|
||||
user_message_raw: "есть ли долг по договору 1-ПМ/2020 на 2020-07-31",
|
||||
message_in_scope: true,
|
||||
scope_confidence: "medium",
|
||||
contains_multiple_tasks: false,
|
||||
fragments: []
|
||||
},
|
||||
raw_model_output: null,
|
||||
validation: { passed: true, errors: [] },
|
||||
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
|
||||
latency_ms: 10,
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
request_count_for_case: 1
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: `asst-predecompose-date-not-account-${Date.now()}`,
|
||||
user_message: "есть ли долг по договору 1-ПМ/2020 на 2020-07-31",
|
||||
llmProvider: "local",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).not.toContain("остаток по счету 07");
|
||||
expect(calls[0].message.toLowerCase()).toContain("договору 1-пм/2020");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user