ARCH: детерминировать ответ на неподдержанный текущий смысл
This commit is contained in:
parent
d9a85c1619
commit
b339a8f8ca
|
|
@ -12,6 +12,37 @@ function hasPriorAssistantTurn(items) {
|
||||||
function buildDeterministicSmalltalkLeadReply() {
|
function buildDeterministicSmalltalkLeadReply() {
|
||||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
||||||
}
|
}
|
||||||
|
function asRecord(value) {
|
||||||
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
||||||
|
}
|
||||||
|
function firstMeaningEntityLabel(assistantTurnMeaning) {
|
||||||
|
const candidates = Array.isArray(assistantTurnMeaning?.explicit_entity_candidates)
|
||||||
|
? assistantTurnMeaning?.explicit_entity_candidates
|
||||||
|
: [];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const record = asRecord(candidate);
|
||||||
|
const value = typeof record?.value === "string" ? record.value.trim() : "";
|
||||||
|
if (value.length > 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function buildUnsupportedCurrentTurnMeaningBoundaryReply(input) {
|
||||||
|
const family = typeof input.assistantTurnMeaning?.unsupported_but_understood_family === "string"
|
||||||
|
? input.assistantTurnMeaning.unsupported_but_understood_family
|
||||||
|
: null;
|
||||||
|
const entityLabel = firstMeaningEntityLabel(input.assistantTurnMeaning);
|
||||||
|
if (family === "counterparty_value_or_turnover") {
|
||||||
|
const entityPart = entityLabel ? ` \u043f\u043e \u00ab${entityLabel}\u00bb` : "";
|
||||||
|
return [
|
||||||
|
`\u042f \u043f\u043e\u043d\u044f\u043b \u0432\u043e\u043f\u0440\u043e\u0441: \u043d\u0443\u0436\u0435\u043d \u043e\u0431\u043e\u0440\u043e\u0442${entityPart}.`,
|
||||||
|
"\u0422\u043e\u0447\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0440\u0430\u0441\u0447\u0451\u0442\u0430 \u0435\u0449\u0451 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u043e\u0448\u043b\u044b\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0438\u043b\u0438 \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430.",
|
||||||
|
"\u041c\u043e\u0433\u0443 \u043f\u043e\u043a\u0430 \u043d\u0430\u0434\u0451\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u0438\u043b\u0438 \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443."
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
return "\u042f \u043f\u043e\u043d\u044f\u043b \u0441\u043c\u044b\u0441\u043b \u043d\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u0430, \u043d\u043e \u0442\u043e\u0447\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0435\u0449\u0451 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d. \u041d\u0435 \u0431\u0443\u0434\u0443 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u044d\u0442\u043e \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435.";
|
||||||
|
}
|
||||||
async function runAssistantLivingChatRuntime(input) {
|
async function runAssistantLivingChatRuntime(input) {
|
||||||
const userMessage = String(input.userMessage ?? "");
|
const userMessage = String(input.userMessage ?? "");
|
||||||
const organizationAuthority = (0, assistantContinuityPolicy_1.resolveAssistantOrganizationAuthority)({
|
const organizationAuthority = (0, assistantContinuityPolicy_1.resolveAssistantOrganizationAuthority)({
|
||||||
|
|
@ -43,6 +74,13 @@ async function runAssistantLivingChatRuntime(input) {
|
||||||
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
||||||
let selectedOrganization = organizationAuthority.selectedOrganization;
|
let selectedOrganization = organizationAuthority.selectedOrganization;
|
||||||
let activeOrganization = organizationAuthority.activeOrganization;
|
let activeOrganization = organizationAuthority.activeOrganization;
|
||||||
|
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
||||||
|
? input.addressRuntimeMeta
|
||||||
|
: {});
|
||||||
|
const orchestrationContract = asRecord(addressRuntimeMeta.orchestrationContract);
|
||||||
|
const assistantTurnMeaning = asRecord(orchestrationContract?.assistant_turn_meaning);
|
||||||
|
const unsupportedCurrentTurnMeaningBoundary = Boolean(input.modeDecision?.reason === "unsupported_current_turn_meaning_boundary" ||
|
||||||
|
orchestrationContract?.unsupported_current_turn_meaning_boundary === true);
|
||||||
const memoryRecapContext = (0, assistantMemoryRecapPolicy_1.resolveAssistantLivingChatMemoryContext)({
|
const memoryRecapContext = (0, assistantMemoryRecapPolicy_1.resolveAssistantLivingChatMemoryContext)({
|
||||||
modeDecisionReason: input.modeDecision?.reason ?? null,
|
modeDecisionReason: input.modeDecision?.reason ?? null,
|
||||||
sessionItems: input.sessionItems
|
sessionItems: input.sessionItems
|
||||||
|
|
@ -72,6 +110,12 @@ async function runAssistantLivingChatRuntime(input) {
|
||||||
? "deterministic_data_scope_contract_live"
|
? "deterministic_data_scope_contract_live"
|
||||||
: "deterministic_data_scope_contract";
|
: "deterministic_data_scope_contract";
|
||||||
}
|
}
|
||||||
|
else if (unsupportedCurrentTurnMeaningBoundary) {
|
||||||
|
chatText = buildUnsupportedCurrentTurnMeaningBoundaryReply({
|
||||||
|
assistantTurnMeaning
|
||||||
|
});
|
||||||
|
livingChatSource = "deterministic_unsupported_current_turn_boundary";
|
||||||
|
}
|
||||||
else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
|
else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
|
||||||
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
||||||
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
||||||
|
|
@ -184,9 +228,6 @@ async function runAssistantLivingChatRuntime(input) {
|
||||||
debug: null
|
debug: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
|
||||||
? input.addressRuntimeMeta
|
|
||||||
: {});
|
|
||||||
const predecomposeContract = addressRuntimeMeta.predecomposeContract && typeof addressRuntimeMeta.predecomposeContract === "object"
|
const predecomposeContract = addressRuntimeMeta.predecomposeContract && typeof addressRuntimeMeta.predecomposeContract === "object"
|
||||||
? addressRuntimeMeta.predecomposeContract
|
? addressRuntimeMeta.predecomposeContract
|
||||||
: null;
|
: null;
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,43 @@ function buildDeterministicSmalltalkLeadReply(): string {
|
||||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||||
|
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstMeaningEntityLabel(assistantTurnMeaning: Record<string, unknown> | null): string | null {
|
||||||
|
const candidates = Array.isArray(assistantTurnMeaning?.explicit_entity_candidates)
|
||||||
|
? assistantTurnMeaning?.explicit_entity_candidates
|
||||||
|
: [];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const record = asRecord(candidate);
|
||||||
|
const value = typeof record?.value === "string" ? record.value.trim() : "";
|
||||||
|
if (value.length > 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUnsupportedCurrentTurnMeaningBoundaryReply(input: {
|
||||||
|
assistantTurnMeaning: Record<string, unknown> | null;
|
||||||
|
}): string {
|
||||||
|
const family =
|
||||||
|
typeof input.assistantTurnMeaning?.unsupported_but_understood_family === "string"
|
||||||
|
? input.assistantTurnMeaning.unsupported_but_understood_family
|
||||||
|
: null;
|
||||||
|
const entityLabel = firstMeaningEntityLabel(input.assistantTurnMeaning);
|
||||||
|
if (family === "counterparty_value_or_turnover") {
|
||||||
|
const entityPart = entityLabel ? ` \u043f\u043e \u00ab${entityLabel}\u00bb` : "";
|
||||||
|
return [
|
||||||
|
`\u042f \u043f\u043e\u043d\u044f\u043b \u0432\u043e\u043f\u0440\u043e\u0441: \u043d\u0443\u0436\u0435\u043d \u043e\u0431\u043e\u0440\u043e\u0442${entityPart}.`,
|
||||||
|
"\u0422\u043e\u0447\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0440\u0430\u0441\u0447\u0451\u0442\u0430 \u0435\u0449\u0451 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u043e\u0448\u043b\u044b\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0438\u043b\u0438 \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430.",
|
||||||
|
"\u041c\u043e\u0433\u0443 \u043f\u043e\u043a\u0430 \u043d\u0430\u0434\u0451\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u0438\u043b\u0438 \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443."
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
return "\u042f \u043f\u043e\u043d\u044f\u043b \u0441\u043c\u044b\u0441\u043b \u043d\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u0430, \u043d\u043e \u0442\u043e\u0447\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0435\u0449\u0451 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d. \u041d\u0435 \u0431\u0443\u0434\u0443 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u044d\u0442\u043e \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435.";
|
||||||
|
}
|
||||||
|
|
||||||
export async function runAssistantLivingChatRuntime(
|
export async function runAssistantLivingChatRuntime(
|
||||||
input: AssistantLivingChatRuntimeInput
|
input: AssistantLivingChatRuntimeInput
|
||||||
): Promise<AssistantLivingChatRuntimeOutput> {
|
): Promise<AssistantLivingChatRuntimeOutput> {
|
||||||
|
|
@ -112,6 +149,15 @@ export async function runAssistantLivingChatRuntime(
|
||||||
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
||||||
let selectedOrganization = organizationAuthority.selectedOrganization;
|
let selectedOrganization = organizationAuthority.selectedOrganization;
|
||||||
let activeOrganization = organizationAuthority.activeOrganization;
|
let activeOrganization = organizationAuthority.activeOrganization;
|
||||||
|
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
||||||
|
? input.addressRuntimeMeta
|
||||||
|
: {}) as Record<string, unknown>;
|
||||||
|
const orchestrationContract = asRecord(addressRuntimeMeta.orchestrationContract);
|
||||||
|
const assistantTurnMeaning = asRecord(orchestrationContract?.assistant_turn_meaning);
|
||||||
|
const unsupportedCurrentTurnMeaningBoundary = Boolean(
|
||||||
|
input.modeDecision?.reason === "unsupported_current_turn_meaning_boundary" ||
|
||||||
|
orchestrationContract?.unsupported_current_turn_meaning_boundary === true
|
||||||
|
);
|
||||||
const memoryRecapContext = resolveAssistantLivingChatMemoryContext({
|
const memoryRecapContext = resolveAssistantLivingChatMemoryContext({
|
||||||
modeDecisionReason: input.modeDecision?.reason ?? null,
|
modeDecisionReason: input.modeDecision?.reason ?? null,
|
||||||
sessionItems: input.sessionItems
|
sessionItems: input.sessionItems
|
||||||
|
|
@ -142,6 +188,11 @@ export async function runAssistantLivingChatRuntime(
|
||||||
dataScopeProbe?.status === "resolved"
|
dataScopeProbe?.status === "resolved"
|
||||||
? "deterministic_data_scope_contract_live"
|
? "deterministic_data_scope_contract_live"
|
||||||
: "deterministic_data_scope_contract";
|
: "deterministic_data_scope_contract";
|
||||||
|
} else if (unsupportedCurrentTurnMeaningBoundary) {
|
||||||
|
chatText = buildUnsupportedCurrentTurnMeaningBoundaryReply({
|
||||||
|
assistantTurnMeaning
|
||||||
|
});
|
||||||
|
livingChatSource = "deterministic_unsupported_current_turn_boundary";
|
||||||
} else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
|
} else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
|
||||||
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
||||||
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
||||||
|
|
@ -254,9 +305,6 @@ export async function runAssistantLivingChatRuntime(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
|
||||||
? input.addressRuntimeMeta
|
|
||||||
: {}) as Record<string, unknown>;
|
|
||||||
const predecomposeContract =
|
const predecomposeContract =
|
||||||
addressRuntimeMeta.predecomposeContract && typeof addressRuntimeMeta.predecomposeContract === "object"
|
addressRuntimeMeta.predecomposeContract && typeof addressRuntimeMeta.predecomposeContract === "object"
|
||||||
? (addressRuntimeMeta.predecomposeContract as Record<string, unknown>)
|
? (addressRuntimeMeta.predecomposeContract as Record<string, unknown>)
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,42 @@ describe("assistant living chat runtime adapter", () => {
|
||||||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds deterministic boundary for unsupported current-turn business meaning", async () => {
|
||||||
|
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||||
|
const input = buildRuntimeInput({
|
||||||
|
userMessage:
|
||||||
|
"\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a",
|
||||||
|
modeDecision: { mode: "chat", reason: "unsupported_current_turn_meaning_boundary" },
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
toolGateReason: "unsupported_current_turn_meaning_boundary",
|
||||||
|
orchestrationContract: {
|
||||||
|
unsupported_current_turn_meaning_boundary: true,
|
||||||
|
assistant_turn_meaning: {
|
||||||
|
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||||
|
explicit_entity_candidates: [
|
||||||
|
{
|
||||||
|
type: "counterparty",
|
||||||
|
value: "\u0441\u0432\u043a"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
executeLlmChat
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = await runAssistantLivingChatRuntime(input);
|
||||||
|
|
||||||
|
expect(output.handled).toBe(true);
|
||||||
|
expect(output.chatText).toContain("\u043d\u0443\u0436\u0435\u043d \u043e\u0431\u043e\u0440\u043e\u0442");
|
||||||
|
expect(output.chatText).toContain("\u00ab\u0441\u0432\u043a\u00bb");
|
||||||
|
expect(output.chatText).toContain("\u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c");
|
||||||
|
expect(output.debug?.living_chat_response_source).toBe(
|
||||||
|
"deterministic_unsupported_current_turn_boundary"
|
||||||
|
);
|
||||||
|
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("adds proactive organization offer on first smalltalk turn when multiple organizations are available", async () => {
|
it("adds proactive organization offer on first smalltalk turn when multiple organizations are available", async () => {
|
||||||
const resolveDataScopeProbe = vi.fn(async () => ({
|
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||||
status: "resolved",
|
status: "resolved",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue