ЮИ - редактура вопросов в АВТОПРОГОНАХ
This commit is contained in:
+14
@@ -179,6 +179,20 @@ Still open after the accepted phase8 replay:
|
|||||||
- proactive organization authority at the very beginning of a new multi-company bookkeeping session is still weaker than the target product feel; the current system now clarifies honestly and cleanly, but it does not yet always pre-offer company selection early in the conversational flow;
|
- proactive organization authority at the very beginning of a new multi-company bookkeeping session is still weaker than the target product feel; the current system now clarifies honestly and cleanly, but it does not yet always pre-offer company selection early in the conversational flow;
|
||||||
- some user-facing inventory/counterparty labels inside business answers still deserve final presentation cleanup, but these are now post-stabilization quality refinements rather than continuity-authority blockers.
|
- some user-facing inventory/counterparty labels inside business answers still deserve final presentation cleanup, but these are now post-stabilization quality refinements rather than continuity-authority blockers.
|
||||||
|
|
||||||
|
Latest phase9 proactive-authority evidence after the fresh multi-company replay:
|
||||||
|
|
||||||
|
- a new live replay `address_truth_harness_phase9_proactive_scope_offer_live_20260418_rerun3` is accepted end-to-end with `5/5` steps green;
|
||||||
|
- on the very first smalltalk turn, the assistant now stays in normal living-chat mode but appends a business-first proactive organization offer instead of waiting for a later forced clarification;
|
||||||
|
- explicit company choice in the next turn is now fixed deterministically into session authority before the first accounting route, so later business turns inherit one stable `active organization`;
|
||||||
|
- the restored activity-age route for `ООО Альтернатива Плюс` is now proven again inside the same shared session, not only in isolated route checks;
|
||||||
|
- the previously broken same-date inventory pivot after receivables is now routed as `inventory_on_hand_as_of_date` with the carried date `31.03.2020` and the carried organization `ООО Альтернатива Плюс`, without falling back into repeated company clarification;
|
||||||
|
- this phase therefore closes the remaining gap called out at the end of phase8: proactive company authority is no longer purely reactive in fresh multi-company bookkeeping sessions.
|
||||||
|
|
||||||
|
Still open after the accepted phase9 replay:
|
||||||
|
|
||||||
|
- business answers are now semantically correct on this path, but some inventory list formatting still feels heavier and more mechanical than the target human style;
|
||||||
|
- the next architecture slice should keep expanding saved-session proof across additional real user chains, while separately tightening answer presentation so exact routes do not feel template-driven even when the truth path is already correct.
|
||||||
|
|
||||||
## Ready Signal
|
## Ready Signal
|
||||||
|
|
||||||
The project can leave the current breakpoint when:
|
The project can leave the current breakpoint when:
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "address_truth_harness_phase9_proactive_scope_offer",
|
||||||
|
"domain": "address_phase9_proactive_scope_offer",
|
||||||
|
"title": "Phase 9 proactive organization offer replay for fresh multi-company sessions",
|
||||||
|
"description": "Focused AGENT replay for the new living-chat authority layer. The scenario validates that a fresh smalltalk turn can proactively surface organization choice, that the selected company becomes active for the session, that organization activity age remains reachable, and that same-date inventory follow-up does not fall back into repeated company clarification.",
|
||||||
|
"bindings": {},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_smalltalk_with_scope_offer",
|
||||||
|
"title": "Fresh smalltalk offers organization scope without technical garbage",
|
||||||
|
"question": "привет, как дела?",
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)привет|дела|норм|на связи",
|
||||||
|
"(?i)организац|компан"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)tool_gate_reason",
|
||||||
|
"(?i)address_mode",
|
||||||
|
"(?i)living_reason",
|
||||||
|
"(?i)mcp",
|
||||||
|
"(?i)read_only",
|
||||||
|
"(?i)snapshot_items"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"meta_smalltalk",
|
||||||
|
"proactive_scope_offer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_choose_organization",
|
||||||
|
"title": "Bare company choice fixes active organization for the session",
|
||||||
|
"question": "Альтернатива Плюс",
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)фиксир|рабочую организац",
|
||||||
|
"(?i)Альтернатива Плюс"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)mcp",
|
||||||
|
"(?i)read_only",
|
||||||
|
"(?i)snapshot_items"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"company_selected",
|
||||||
|
"living_scope_selection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_company_activity_age",
|
||||||
|
"title": "Organization activity age is answered after proactive selection",
|
||||||
|
"question": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"expected_intents": [
|
||||||
|
"counterparty_activity_lifecycle"
|
||||||
|
],
|
||||||
|
"expected_recipe": "address_counterparty_activity_lifecycle_v1",
|
||||||
|
"required_direct_answer_patterns_any": [
|
||||||
|
"(?i)активност",
|
||||||
|
"(?i)первая подтвержденная|не удается точно определить|лет"
|
||||||
|
],
|
||||||
|
"forbidden_direct_answer_patterns": [
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)уточните точное наименование организации"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"organization_activity_age",
|
||||||
|
"company_selected"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_04_receivables_root",
|
||||||
|
"title": "Receivables root keeps March 2020 in the active organization scope",
|
||||||
|
"question": "кто нам должен на март 2020?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"expected_intents": [
|
||||||
|
"receivables_confirmed_as_of_date"
|
||||||
|
],
|
||||||
|
"required_filters": {
|
||||||
|
"as_of_date": "2020-03-31",
|
||||||
|
"period_from": "2020-03-01",
|
||||||
|
"period_to": "2020-03-31"
|
||||||
|
},
|
||||||
|
"required_direct_answer_patterns_any": [
|
||||||
|
"(?i)дебитор",
|
||||||
|
"31\\.03\\.2020"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"settlements_receivables",
|
||||||
|
"company_selected"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_05_inventory_same_date",
|
||||||
|
"title": "Inventory same-date follow-up does not ask for company again",
|
||||||
|
"question": "остатки по складу на эту же дату",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"expected_intents": [
|
||||||
|
"inventory_on_hand_as_of_date"
|
||||||
|
],
|
||||||
|
"required_filters": {
|
||||||
|
"as_of_date": "{{step_04_receivables_root.filters.as_of_date}}",
|
||||||
|
"period_from": "{{step_04_receivables_root.filters.period_from}}",
|
||||||
|
"period_to": "{{step_04_receivables_root.filters.period_to}}"
|
||||||
|
},
|
||||||
|
"required_direct_answer_patterns_all": [
|
||||||
|
"(?i)на складе",
|
||||||
|
"31\\.03\\.2020"
|
||||||
|
],
|
||||||
|
"forbidden_direct_answer_patterns": [
|
||||||
|
"(?i)уточните организац",
|
||||||
|
"(?i)по какой компании"
|
||||||
|
],
|
||||||
|
"required_filter_within_previous_step_period": {
|
||||||
|
"as_of_date": "step_04_receivables_root"
|
||||||
|
},
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"inventory_root",
|
||||||
|
"same_date_pivot",
|
||||||
|
"company_authority"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -66,6 +66,7 @@ async function runAssistantAddressAttemptRuntime(input) {
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -73,6 +74,7 @@ async function runAssistantAddressAttemptRuntime(input) {
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -51,6 +51,28 @@ function createAssistantBoundaryPolicy(deps) {
|
|||||||
"Если в нем несколько организаций, скажите, по какой смотреть данные."
|
"Если в нем несколько организаций, скажите, по какой смотреть данные."
|
||||||
].join(" ");
|
].join(" ");
|
||||||
}
|
}
|
||||||
|
function buildAssistantProactiveOrganizationOfferReply(scopeProbe = null) {
|
||||||
|
const organizations = Array.isArray(scopeProbe?.organizations)
|
||||||
|
? scopeProbe.organizations
|
||||||
|
.map((item) => normalizeSelectedOrganization(item, deps.normalizeOrganizationScopeValue))
|
||||||
|
.filter((item) => item.length > 0)
|
||||||
|
.filter((item, index, array) => array.indexOf(item) === index)
|
||||||
|
: [];
|
||||||
|
if (organizations.length === 1) {
|
||||||
|
return [
|
||||||
|
`Если дальше пойдём в данные 1С, могу сразу держать в контуре ${organizations[0]}.`,
|
||||||
|
"Можно просто писать вопрос по документам, остаткам, НДС или контрагентам."
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
if (organizations.length > 1) {
|
||||||
|
const preview = organizations.slice(0, 10).join(", ");
|
||||||
|
return [
|
||||||
|
`Если дальше пойдём в данные 1С, могу сразу зафиксировать организацию: ${preview}.`,
|
||||||
|
"Просто напишите название компании, и дальше буду держать её активной в этой сессии."
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
function buildAssistantDataScopeSelectionReply(organization) {
|
function buildAssistantDataScopeSelectionReply(organization) {
|
||||||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||||||
return [
|
return [
|
||||||
@@ -161,6 +183,7 @@ function createAssistantBoundaryPolicy(deps) {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
+2
@@ -23,6 +23,7 @@ function buildAssistantLivingChatAttemptRuntimeInput(input) {
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -30,6 +31,7 @@ function buildAssistantLivingChatAttemptRuntimeInput(input) {
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
+2
@@ -38,6 +38,7 @@ async function runAssistantLivingChatAttemptRuntime(input) {
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -46,6 +47,7 @@ async function runAssistantLivingChatAttemptRuntime(input) {
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
+2
@@ -33,6 +33,7 @@ function buildAssistantLivingChatHandlerRuntimeInput(input) {
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -41,6 +42,7 @@ function buildAssistantLivingChatHandlerRuntimeInput(input) {
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
+2
@@ -23,6 +23,7 @@ async function tryHandleAssistantLivingChatRuntime(input) {
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -31,6 +32,7 @@ async function tryHandleAssistantLivingChatRuntime(input) {
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ function findLastAddressDebugWithItem(items) {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function hasPriorAssistantTurn(items) {
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return items.some((item) => item && typeof item === "object" && item.role === "assistant");
|
||||||
|
}
|
||||||
function findLastAddressDebug(items) {
|
function findLastAddressDebug(items) {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -157,6 +163,7 @@ async function runAssistantLivingChatRuntime(input) {
|
|||||||
let livingChatScriptGuardReason = null;
|
let livingChatScriptGuardReason = null;
|
||||||
let livingChatGroundingGuardApplied = false;
|
let livingChatGroundingGuardApplied = false;
|
||||||
let livingChatGroundingGuardReason = null;
|
let livingChatGroundingGuardReason = null;
|
||||||
|
let livingChatProactiveScopeOfferApplied = false;
|
||||||
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
||||||
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
||||||
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
||||||
@@ -256,6 +263,31 @@ async function runAssistantLivingChatRuntime(input) {
|
|||||||
livingChatGroundingGuardReason = groundingGuard.reason;
|
livingChatGroundingGuardReason = groundingGuard.reason;
|
||||||
livingChatSource = "llm_chat_grounding_guard";
|
livingChatSource = "llm_chat_grounding_guard";
|
||||||
}
|
}
|
||||||
|
const shouldOfferProactiveOrganizationScope = !selectedOrganization &&
|
||||||
|
!activeOrganization &&
|
||||||
|
!hasPriorAssistantTurn(input.sessionItems) &&
|
||||||
|
input.modeDecision?.mode === "chat" &&
|
||||||
|
input.hasLivingChatSignal(userMessage);
|
||||||
|
if (shouldOfferProactiveOrganizationScope) {
|
||||||
|
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
||||||
|
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
||||||
|
...knownOrganizations,
|
||||||
|
...(Array.isArray(proactiveScopeProbe?.organizations) ? proactiveScopeProbe.organizations : [])
|
||||||
|
]);
|
||||||
|
knownOrganizations = mergedKnownOrganizations;
|
||||||
|
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
||||||
|
activeOrganization = mergedKnownOrganizations[0];
|
||||||
|
}
|
||||||
|
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||||
|
if (proactiveOffer) {
|
||||||
|
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
|
||||||
|
livingChatProactiveScopeOfferApplied = true;
|
||||||
|
livingChatSource = "llm_chat_with_proactive_scope_offer";
|
||||||
|
if (!dataScopeProbe) {
|
||||||
|
dataScopeProbe = proactiveScopeProbe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!chatText) {
|
if (!chatText) {
|
||||||
return {
|
return {
|
||||||
@@ -288,6 +320,7 @@ async function runAssistantLivingChatRuntime(input) {
|
|||||||
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
||||||
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
||||||
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
||||||
|
living_chat_proactive_scope_offer_applied: livingChatProactiveScopeOfferApplied,
|
||||||
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
||||||
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
||||||
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
||||||
|
|||||||
@@ -5023,6 +5023,7 @@ class AssistantService {
|
|||||||
hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||||
@@ -5030,6 +5031,7 @@ class AssistantService {
|
|||||||
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
||||||
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
||||||
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: assistantBoundaryPolicy.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
||||||
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
||||||
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
||||||
|
|||||||
@@ -35,6 +35,28 @@ function createAssistantTransitionPolicy(deps) {
|
|||||||
(hasRestatementCue || hasBareSnapshotSameDateCue || hasBareSnapshotSameDatePhraseCue) &&
|
(hasRestatementCue || hasBareSnapshotSameDateCue || hasBareSnapshotSameDatePhraseCue) &&
|
||||||
!deps.hasForeignAccountingPivotOverInventoryMessage(normalized));
|
!deps.hasForeignAccountingPivotOverInventoryMessage(normalized));
|
||||||
}
|
}
|
||||||
|
function hasExplicitInventorySameDatePivotSignal(userMessage) {
|
||||||
|
const normalized = deps
|
||||||
|
.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase())
|
||||||
|
.replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasInventoryLexeme = /(?:остат|склад|товар|номенклатур|позиц)/iu.test(normalized);
|
||||||
|
if (!hasInventoryLexeme) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const sameDatePhrases = [
|
||||||
|
"на ту же дат",
|
||||||
|
"на эту же дат",
|
||||||
|
"на эту дат",
|
||||||
|
"эту дат",
|
||||||
|
"та же дата",
|
||||||
|
"тот же период",
|
||||||
|
"этот же период"
|
||||||
|
];
|
||||||
|
return sameDatePhrases.some((phrase) => normalized.includes(phrase));
|
||||||
|
}
|
||||||
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
||||||
const normalized = deps.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
const normalized = deps.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||||
if (!normalized || deps.countTokens(normalized) > 4) {
|
if (!normalized || deps.countTokens(normalized) > 4) {
|
||||||
@@ -170,6 +192,10 @@ function createAssistantTransitionPolicy(deps) {
|
|||||||
const hasInventoryRootRestatementAlternate = deps.toNonEmptyString(alternateMessage)
|
const hasInventoryRootRestatementAlternate = deps.toNonEmptyString(alternateMessage)
|
||||||
? hasInventoryRootRestatementLikeSignal(String(alternateMessage ?? ""), sourceIntentHint, Boolean(recentInventoryRootFrame))
|
? hasInventoryRootRestatementLikeSignal(String(alternateMessage ?? ""), sourceIntentHint, Boolean(recentInventoryRootFrame))
|
||||||
: false;
|
: false;
|
||||||
|
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||||
|
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||||
|
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||||
|
: false;
|
||||||
let hasStrongFollowupReference = hasPrimaryIndexReferenceSignal ||
|
let hasStrongFollowupReference = hasPrimaryIndexReferenceSignal ||
|
||||||
hasAlternateIndexReferenceSignal ||
|
hasAlternateIndexReferenceSignal ||
|
||||||
hasOrganizationClarificationContinuation ||
|
hasOrganizationClarificationContinuation ||
|
||||||
@@ -451,6 +477,9 @@ function createAssistantTransitionPolicy(deps) {
|
|||||||
currentFrameKind === "generic") &&
|
currentFrameKind === "generic") &&
|
||||||
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
||||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
||||||
|
const explicitInventorySameDatePivot = Boolean(!inventoryRootFrame &&
|
||||||
|
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
|
||||||
|
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
|
||||||
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
||||||
if (rootScopedPivot) {
|
if (rootScopedPivot) {
|
||||||
previousIntent = null;
|
previousIntent = null;
|
||||||
@@ -540,7 +569,9 @@ function createAssistantTransitionPolicy(deps) {
|
|||||||
hasSelectedObjectInventorySignalAlternate));
|
hasSelectedObjectInventorySignalAlternate));
|
||||||
const carryoverTargetIntent = followupSelectionMode === "carry_root_context"
|
const carryoverTargetIntent = followupSelectionMode === "carry_root_context"
|
||||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
: explicitInventorySameDatePivot
|
||||||
|
? "inventory_on_hand_as_of_date"
|
||||||
|
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||||
return {
|
return {
|
||||||
followupContext: {
|
followupContext: {
|
||||||
previous_intent: previousIntent ?? undefined,
|
previous_intent: previousIntent ?? undefined,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
|||||||
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: deps.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
||||||
@@ -58,6 +59,7 @@ function buildAssistantAddressAttemptRuntimeInput(runtimeInput, deps) {
|
|||||||
applyGroundingGuard: deps.applyGroundingGuard,
|
applyGroundingGuard: deps.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: deps.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export interface RunAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||||||
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||||
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||||
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||||
|
hasLivingChatSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||||
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||||
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
||||||
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
||||||
@@ -69,6 +70,8 @@ export interface RunAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||||||
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
||||||
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||||
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||||
|
buildAssistantProactiveOrganizationOfferReply:
|
||||||
|
RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||||
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||||
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
||||||
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
||||||
@@ -179,6 +182,7 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -186,6 +190,7 @@ export async function runAssistantAddressAttemptRuntime<ResponseType = unknown>(
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface AssistantBoundaryPolicyGroundingGuardInput {
|
|||||||
|
|
||||||
export interface AssistantBoundaryPolicy {
|
export interface AssistantBoundaryPolicy {
|
||||||
buildAssistantDataScopeContractReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
buildAssistantDataScopeContractReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: (scopeProbe?: Record<string, unknown> | null) => string;
|
||||||
buildAssistantDataScopeSelectionReply: (organization: unknown) => string;
|
buildAssistantDataScopeSelectionReply: (organization: unknown) => string;
|
||||||
buildAssistantOrganizationFactBoundaryReply: (organization: unknown) => string;
|
buildAssistantOrganizationFactBoundaryReply: (organization: unknown) => string;
|
||||||
buildAssistantOperationalBoundaryReply: () => string;
|
buildAssistantOperationalBoundaryReply: () => string;
|
||||||
@@ -90,6 +91,32 @@ export function createAssistantBoundaryPolicy(deps: AssistantBoundaryPolicyDeps)
|
|||||||
].join(" ");
|
].join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAssistantProactiveOrganizationOfferReply(scopeProbe: Record<string, unknown> | null = null): string {
|
||||||
|
const organizations = Array.isArray(scopeProbe?.organizations)
|
||||||
|
? scopeProbe.organizations
|
||||||
|
.map((item) => normalizeSelectedOrganization(item, deps.normalizeOrganizationScopeValue))
|
||||||
|
.filter((item) => item.length > 0)
|
||||||
|
.filter((item, index, array) => array.indexOf(item) === index)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (organizations.length === 1) {
|
||||||
|
return [
|
||||||
|
`Если дальше пойдём в данные 1С, могу сразу держать в контуре ${organizations[0]}.`,
|
||||||
|
"Можно просто писать вопрос по документам, остаткам, НДС или контрагентам."
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (organizations.length > 1) {
|
||||||
|
const preview = organizations.slice(0, 10).join(", ");
|
||||||
|
return [
|
||||||
|
`Если дальше пойдём в данные 1С, могу сразу зафиксировать организацию: ${preview}.`,
|
||||||
|
"Просто напишите название компании, и дальше буду держать её активной в этой сессии."
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
function buildAssistantDataScopeSelectionReply(organization: unknown): string {
|
function buildAssistantDataScopeSelectionReply(organization: unknown): string {
|
||||||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||||||
return [
|
return [
|
||||||
@@ -219,6 +246,7 @@ export function createAssistantBoundaryPolicy(deps: AssistantBoundaryPolicyDeps)
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType =
|
|||||||
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
hasOperationalAdminActionRequestSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||||
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
hasOrganizationFactLookupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||||
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
hasOrganizationFactFollowupSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||||
|
hasLivingChatSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||||
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
shouldEmitOrganizationSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||||
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
hasAssistantCapabilityQuestionSignal: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["hasAssistantCapabilityQuestionSignal"];
|
||||||
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
resolveDataScopeProbe: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["resolveDataScopeProbe"];
|
||||||
@@ -29,6 +30,8 @@ export interface BuildAssistantLivingChatAttemptRuntimeInputInput<ResponseType =
|
|||||||
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
applyGroundingGuard: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["applyGroundingGuard"];
|
||||||
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
buildAssistantSafetyRefusalReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||||
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
buildAssistantDataScopeContractReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||||
|
buildAssistantProactiveOrganizationOfferReply:
|
||||||
|
RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||||
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
buildAssistantOrganizationFactBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||||
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
buildAssistantDataScopeSelectionReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeSelectionReply"];
|
||||||
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
buildAssistantOperationalBoundaryReply: RunAssistantLivingChatAttemptRuntimeInput<ResponseType>["buildAssistantOperationalBoundaryReply"];
|
||||||
@@ -73,6 +76,7 @@ export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unkno
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -80,6 +84,7 @@ export function buildAssistantLivingChatAttemptRuntimeInput<ResponseType = unkno
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -83,6 +84,7 @@ export async function runAssistantLivingChatAttemptRuntime<ResponseType = unknow
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unkno
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -64,6 +65,7 @@ export function buildAssistantLivingChatHandlerRuntimeInput<ResponseType = unkno
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface TryHandleAssistantLivingChatRuntimeInput<ResponseType = unknown
|
|||||||
hasOperationalAdminActionRequestSignal: AssistantLivingChatRuntimeInput["hasOperationalAdminActionRequestSignal"];
|
hasOperationalAdminActionRequestSignal: AssistantLivingChatRuntimeInput["hasOperationalAdminActionRequestSignal"];
|
||||||
hasOrganizationFactLookupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactLookupSignal"];
|
hasOrganizationFactLookupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactLookupSignal"];
|
||||||
hasOrganizationFactFollowupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactFollowupSignal"];
|
hasOrganizationFactFollowupSignal: AssistantLivingChatRuntimeInput["hasOrganizationFactFollowupSignal"];
|
||||||
|
hasLivingChatSignal: AssistantLivingChatRuntimeInput["hasLivingChatSignal"];
|
||||||
shouldEmitOrganizationSelectionReply: AssistantLivingChatRuntimeInput["shouldEmitOrganizationSelectionReply"];
|
shouldEmitOrganizationSelectionReply: AssistantLivingChatRuntimeInput["shouldEmitOrganizationSelectionReply"];
|
||||||
hasAssistantCapabilityQuestionSignal: AssistantLivingChatRuntimeInput["hasAssistantCapabilityQuestionSignal"];
|
hasAssistantCapabilityQuestionSignal: AssistantLivingChatRuntimeInput["hasAssistantCapabilityQuestionSignal"];
|
||||||
resolveDataScopeProbe: AssistantLivingChatRuntimeInput["resolveDataScopeProbe"];
|
resolveDataScopeProbe: AssistantLivingChatRuntimeInput["resolveDataScopeProbe"];
|
||||||
@@ -33,6 +34,7 @@ export interface TryHandleAssistantLivingChatRuntimeInput<ResponseType = unknown
|
|||||||
applyGroundingGuard: AssistantLivingChatRuntimeInput["applyGroundingGuard"];
|
applyGroundingGuard: AssistantLivingChatRuntimeInput["applyGroundingGuard"];
|
||||||
buildAssistantSafetyRefusalReply: AssistantLivingChatRuntimeInput["buildAssistantSafetyRefusalReply"];
|
buildAssistantSafetyRefusalReply: AssistantLivingChatRuntimeInput["buildAssistantSafetyRefusalReply"];
|
||||||
buildAssistantDataScopeContractReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeContractReply"];
|
buildAssistantDataScopeContractReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeContractReply"];
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: AssistantLivingChatRuntimeInput["buildAssistantProactiveOrganizationOfferReply"];
|
||||||
buildAssistantOrganizationFactBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOrganizationFactBoundaryReply"];
|
buildAssistantOrganizationFactBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOrganizationFactBoundaryReply"];
|
||||||
buildAssistantDataScopeSelectionReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeSelectionReply"];
|
buildAssistantDataScopeSelectionReply: AssistantLivingChatRuntimeInput["buildAssistantDataScopeSelectionReply"];
|
||||||
buildAssistantOperationalBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOperationalBoundaryReply"];
|
buildAssistantOperationalBoundaryReply: AssistantLivingChatRuntimeInput["buildAssistantOperationalBoundaryReply"];
|
||||||
@@ -76,6 +78,7 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
|
|||||||
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: input.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
resolveDataScopeProbe: input.resolveDataScopeProbe,
|
||||||
@@ -84,6 +87,7 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
|
|||||||
applyGroundingGuard: input.applyGroundingGuard,
|
applyGroundingGuard: input.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: input.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface AssistantLivingChatRuntimeInput {
|
|||||||
hasOperationalAdminActionRequestSignal: (message: string) => boolean;
|
hasOperationalAdminActionRequestSignal: (message: string) => boolean;
|
||||||
hasOrganizationFactLookupSignal: (message: string) => boolean;
|
hasOrganizationFactLookupSignal: (message: string) => boolean;
|
||||||
hasOrganizationFactFollowupSignal: (message: string, items: unknown[]) => boolean;
|
hasOrganizationFactFollowupSignal: (message: string, items: unknown[]) => boolean;
|
||||||
|
hasLivingChatSignal: (message: string) => boolean;
|
||||||
shouldEmitOrganizationSelectionReply: (message: string, activeOrganization: string | null) => boolean;
|
shouldEmitOrganizationSelectionReply: (message: string, activeOrganization: string | null) => boolean;
|
||||||
hasAssistantCapabilityQuestionSignal: (message: string) => boolean;
|
hasAssistantCapabilityQuestionSignal: (message: string) => boolean;
|
||||||
resolveDataScopeProbe: () => Promise<Record<string, unknown> | null>;
|
resolveDataScopeProbe: () => Promise<Record<string, unknown> | null>;
|
||||||
@@ -51,6 +52,7 @@ export interface AssistantLivingChatRuntimeInput {
|
|||||||
};
|
};
|
||||||
buildAssistantSafetyRefusalReply: () => string;
|
buildAssistantSafetyRefusalReply: () => string;
|
||||||
buildAssistantDataScopeContractReply: (scopeProbe: Record<string, unknown> | null) => string;
|
buildAssistantDataScopeContractReply: (scopeProbe: Record<string, unknown> | null) => string;
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => string;
|
||||||
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => string;
|
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => string;
|
||||||
buildAssistantDataScopeSelectionReply: (organization: string | null) => string;
|
buildAssistantDataScopeSelectionReply: (organization: string | null) => string;
|
||||||
buildAssistantOperationalBoundaryReply: () => string;
|
buildAssistantOperationalBoundaryReply: () => string;
|
||||||
@@ -134,6 +136,13 @@ function findLastAddressDebugWithItem(items: unknown[]): Record<string, unknown>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPriorAssistantTurn(items: unknown[]): boolean {
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return items.some((item) => item && typeof item === "object" && (item as { role?: string }).role === "assistant");
|
||||||
|
}
|
||||||
|
|
||||||
function findLastAddressDebug(items: unknown[]): Record<string, unknown> | null {
|
function findLastAddressDebug(items: unknown[]): Record<string, unknown> | null {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -252,6 +261,7 @@ export async function runAssistantLivingChatRuntime(
|
|||||||
let livingChatScriptGuardReason: string | null = null;
|
let livingChatScriptGuardReason: string | null = null;
|
||||||
let livingChatGroundingGuardApplied = false;
|
let livingChatGroundingGuardApplied = false;
|
||||||
let livingChatGroundingGuardReason: string | null = null;
|
let livingChatGroundingGuardReason: string | null = null;
|
||||||
|
let livingChatProactiveScopeOfferApplied = false;
|
||||||
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
|
||||||
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
||||||
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
||||||
@@ -348,6 +358,33 @@ export async function runAssistantLivingChatRuntime(
|
|||||||
livingChatGroundingGuardReason = groundingGuard.reason;
|
livingChatGroundingGuardReason = groundingGuard.reason;
|
||||||
livingChatSource = "llm_chat_grounding_guard";
|
livingChatSource = "llm_chat_grounding_guard";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldOfferProactiveOrganizationScope =
|
||||||
|
!selectedOrganization &&
|
||||||
|
!activeOrganization &&
|
||||||
|
!hasPriorAssistantTurn(input.sessionItems) &&
|
||||||
|
input.modeDecision?.mode === "chat" &&
|
||||||
|
input.hasLivingChatSignal(userMessage);
|
||||||
|
if (shouldOfferProactiveOrganizationScope) {
|
||||||
|
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
||||||
|
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
||||||
|
...knownOrganizations,
|
||||||
|
...(Array.isArray(proactiveScopeProbe?.organizations) ? (proactiveScopeProbe.organizations as unknown[]) : [])
|
||||||
|
]);
|
||||||
|
knownOrganizations = mergedKnownOrganizations;
|
||||||
|
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
||||||
|
activeOrganization = mergedKnownOrganizations[0];
|
||||||
|
}
|
||||||
|
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||||
|
if (proactiveOffer) {
|
||||||
|
chatText = [chatText, proactiveOffer].filter((part) => String(part ?? "").trim().length > 0).join(" ");
|
||||||
|
livingChatProactiveScopeOfferApplied = true;
|
||||||
|
livingChatSource = "llm_chat_with_proactive_scope_offer";
|
||||||
|
if (!dataScopeProbe) {
|
||||||
|
dataScopeProbe = proactiveScopeProbe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chatText) {
|
if (!chatText) {
|
||||||
@@ -385,6 +422,7 @@ export async function runAssistantLivingChatRuntime(
|
|||||||
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
||||||
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
||||||
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
||||||
|
living_chat_proactive_scope_offer_applied: livingChatProactiveScopeOfferApplied,
|
||||||
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
||||||
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
||||||
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
||||||
|
|||||||
@@ -4981,6 +4981,7 @@ export class AssistantService {
|
|||||||
hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(),
|
||||||
@@ -4988,6 +4989,7 @@ export class AssistantService {
|
|||||||
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
applyGroundingGuard: applyLivingChatGroundingGuardFromPolicy,
|
||||||
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
buildAssistantSafetyRefusalReply: buildAssistantSafetyRefusalReplyFromPolicy,
|
||||||
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
buildAssistantDataScopeContractReply: buildAssistantDataScopeContractReplyFromPolicy,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: assistantBoundaryPolicy.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
buildAssistantOrganizationFactBoundaryReply: buildAssistantOrganizationFactBoundaryReplyFromPolicy,
|
||||||
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
buildAssistantDataScopeSelectionReply: buildAssistantDataScopeSelectionReplyFromPolicy,
|
||||||
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
buildAssistantOperationalBoundaryReply: buildAssistantOperationalBoundaryReplyFromPolicy,
|
||||||
|
|||||||
@@ -43,6 +43,29 @@ export function createAssistantTransitionPolicy(deps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasExplicitInventorySameDatePivotSignal(userMessage) {
|
||||||
|
const normalized = deps
|
||||||
|
.compactWhitespace(deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase())
|
||||||
|
.replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasInventoryLexeme = /(?:остат|склад|товар|номенклатур|позиц)/iu.test(normalized);
|
||||||
|
if (!hasInventoryLexeme) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const sameDatePhrases = [
|
||||||
|
"на ту же дат",
|
||||||
|
"на эту же дат",
|
||||||
|
"на эту дат",
|
||||||
|
"эту дат",
|
||||||
|
"та же дата",
|
||||||
|
"тот же период",
|
||||||
|
"этот же период"
|
||||||
|
];
|
||||||
|
return sameDatePhrases.some((phrase) => normalized.includes(phrase));
|
||||||
|
}
|
||||||
|
|
||||||
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
function shouldKeepPreviousIntentForShortCounterpartyRetarget(userMessage, sourceIntent) {
|
||||||
const normalized = deps.compactWhitespace(
|
const normalized = deps.compactWhitespace(
|
||||||
deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase()
|
deps.repairAddressMojibake(String(userMessage ?? "")).toLowerCase()
|
||||||
@@ -227,6 +250,10 @@ export function createAssistantTransitionPolicy(deps) {
|
|||||||
Boolean(recentInventoryRootFrame)
|
Boolean(recentInventoryRootFrame)
|
||||||
)
|
)
|
||||||
: false;
|
: false;
|
||||||
|
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||||
|
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||||
|
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||||
|
: false;
|
||||||
let hasStrongFollowupReference =
|
let hasStrongFollowupReference =
|
||||||
hasPrimaryIndexReferenceSignal ||
|
hasPrimaryIndexReferenceSignal ||
|
||||||
hasAlternateIndexReferenceSignal ||
|
hasAlternateIndexReferenceSignal ||
|
||||||
@@ -550,6 +577,11 @@ export function createAssistantTransitionPolicy(deps) {
|
|||||||
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
|
||||||
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
||||||
);
|
);
|
||||||
|
const explicitInventorySameDatePivot = Boolean(
|
||||||
|
!inventoryRootFrame &&
|
||||||
|
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
|
||||||
|
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
|
||||||
|
);
|
||||||
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
|
||||||
if (rootScopedPivot) {
|
if (rootScopedPivot) {
|
||||||
previousIntent = null;
|
previousIntent = null;
|
||||||
@@ -651,7 +683,9 @@ export function createAssistantTransitionPolicy(deps) {
|
|||||||
const carryoverTargetIntent =
|
const carryoverTargetIntent =
|
||||||
followupSelectionMode === "carry_root_context"
|
followupSelectionMode === "carry_root_context"
|
||||||
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
|
||||||
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
: explicitInventorySameDatePivot
|
||||||
|
? "inventory_on_hand_as_of_date"
|
||||||
|
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
|
||||||
return {
|
return {
|
||||||
followupContext: {
|
followupContext: {
|
||||||
previous_intent: previousIntent ?? undefined,
|
previous_intent: previousIntent ?? undefined,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export interface AssistantTurnRuntimeBuilderDeps<ResponseType = unknown> {
|
|||||||
AddressAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
AddressAttemptRuntimeInput<ResponseType>["hasOperationalAdminActionRequestSignal"];
|
||||||
hasOrganizationFactLookupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
hasOrganizationFactLookupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactLookupSignal"];
|
||||||
hasOrganizationFactFollowupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
hasOrganizationFactFollowupSignal: AddressAttemptRuntimeInput<ResponseType>["hasOrganizationFactFollowupSignal"];
|
||||||
|
hasLivingChatSignal: AddressAttemptRuntimeInput<ResponseType>["hasLivingChatSignal"];
|
||||||
shouldEmitOrganizationSelectionReply:
|
shouldEmitOrganizationSelectionReply:
|
||||||
AddressAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
AddressAttemptRuntimeInput<ResponseType>["shouldEmitOrganizationSelectionReply"];
|
||||||
hasAssistantCapabilityQuestionSignal:
|
hasAssistantCapabilityQuestionSignal:
|
||||||
@@ -77,6 +78,8 @@ export interface AssistantTurnRuntimeBuilderDeps<ResponseType = unknown> {
|
|||||||
buildAssistantSafetyRefusalReply: AddressAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
buildAssistantSafetyRefusalReply: AddressAttemptRuntimeInput<ResponseType>["buildAssistantSafetyRefusalReply"];
|
||||||
buildAssistantDataScopeContractReply:
|
buildAssistantDataScopeContractReply:
|
||||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
AddressAttemptRuntimeInput<ResponseType>["buildAssistantDataScopeContractReply"];
|
||||||
|
buildAssistantProactiveOrganizationOfferReply:
|
||||||
|
AddressAttemptRuntimeInput<ResponseType>["buildAssistantProactiveOrganizationOfferReply"];
|
||||||
buildAssistantOrganizationFactBoundaryReply:
|
buildAssistantOrganizationFactBoundaryReply:
|
||||||
AddressAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
AddressAttemptRuntimeInput<ResponseType>["buildAssistantOrganizationFactBoundaryReply"];
|
||||||
buildAssistantDataScopeSelectionReply:
|
buildAssistantDataScopeSelectionReply:
|
||||||
@@ -164,6 +167,7 @@ export function buildAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||||||
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
hasOperationalAdminActionRequestSignal: deps.hasOperationalAdminActionRequestSignal,
|
||||||
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
hasOrganizationFactLookupSignal: deps.hasOrganizationFactLookupSignal,
|
||||||
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
hasOrganizationFactFollowupSignal: deps.hasOrganizationFactFollowupSignal,
|
||||||
|
hasLivingChatSignal: deps.hasLivingChatSignal,
|
||||||
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
shouldEmitOrganizationSelectionReply: deps.shouldEmitOrganizationSelectionReply,
|
||||||
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
hasAssistantCapabilityQuestionSignal: deps.hasAssistantCapabilityQuestionSignal,
|
||||||
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
resolveDataScopeProbe: deps.resolveDataScopeProbe,
|
||||||
@@ -171,6 +175,7 @@ export function buildAssistantAddressAttemptRuntimeInput<ResponseType = unknown>
|
|||||||
applyGroundingGuard: deps.applyGroundingGuard,
|
applyGroundingGuard: deps.applyGroundingGuard,
|
||||||
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
buildAssistantSafetyRefusalReply: deps.buildAssistantSafetyRefusalReply,
|
||||||
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
buildAssistantDataScopeContractReply: deps.buildAssistantDataScopeContractReply,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: deps.buildAssistantProactiveOrganizationOfferReply,
|
||||||
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
buildAssistantOrganizationFactBoundaryReply: deps.buildAssistantOrganizationFactBoundaryReply,
|
||||||
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
buildAssistantDataScopeSelectionReply: deps.buildAssistantDataScopeSelectionReply,
|
||||||
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
buildAssistantOperationalBoundaryReply: deps.buildAssistantOperationalBoundaryReply,
|
||||||
|
|||||||
@@ -56,6 +56,23 @@ describe("assistantBoundaryPolicy", () => {
|
|||||||
expect(reply).not.toContain("\\");
|
expect(reply).not.toContain("\\");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds proactive organization offer without technical labels", () => {
|
||||||
|
const policy = createPolicy();
|
||||||
|
|
||||||
|
const reply = policy.buildAssistantProactiveOrganizationOfferReply({
|
||||||
|
status: "resolved",
|
||||||
|
channel: "default",
|
||||||
|
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(reply).toContain("Если дальше пойдём в данные 1С");
|
||||||
|
expect(reply).toContain("ООО Альтернатива Плюс");
|
||||||
|
expect(reply).toContain("ООО Лайсвуд");
|
||||||
|
expect(reply).toContain("РАЙМ");
|
||||||
|
expect(reply).not.toContain("MCP");
|
||||||
|
expect(reply.toLowerCase()).not.toContain("snapshot");
|
||||||
|
});
|
||||||
|
|
||||||
it("strips unexpected CJK fragments from live chat reply", () => {
|
it("strips unexpected CJK fragments from live chat reply", () => {
|
||||||
const policy = createPolicy();
|
const policy = createPolicy();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function buildInput(overrides: Record<string, unknown> = {}) {
|
|||||||
hasOperationalAdminActionRequestSignal: () => false,
|
hasOperationalAdminActionRequestSignal: () => false,
|
||||||
hasOrganizationFactLookupSignal: () => false,
|
hasOrganizationFactLookupSignal: () => false,
|
||||||
hasOrganizationFactFollowupSignal: () => false,
|
hasOrganizationFactFollowupSignal: () => false,
|
||||||
|
hasLivingChatSignal: () => true,
|
||||||
shouldEmitOrganizationSelectionReply: () => false,
|
shouldEmitOrganizationSelectionReply: () => false,
|
||||||
hasAssistantCapabilityQuestionSignal: () => false,
|
hasAssistantCapabilityQuestionSignal: () => false,
|
||||||
resolveDataScopeProbe: async () => null,
|
resolveDataScopeProbe: async () => null,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
|||||||
hasOperationalAdminActionRequestSignal: () => false,
|
hasOperationalAdminActionRequestSignal: () => false,
|
||||||
hasOrganizationFactLookupSignal: () => false,
|
hasOrganizationFactLookupSignal: () => false,
|
||||||
hasOrganizationFactFollowupSignal: () => false,
|
hasOrganizationFactFollowupSignal: () => false,
|
||||||
|
hasLivingChatSignal: () => true,
|
||||||
shouldEmitOrganizationSelectionReply: () => false,
|
shouldEmitOrganizationSelectionReply: () => false,
|
||||||
hasAssistantCapabilityQuestionSignal: () => false,
|
hasAssistantCapabilityQuestionSignal: () => false,
|
||||||
resolveDataScopeProbe,
|
resolveDataScopeProbe,
|
||||||
@@ -53,6 +54,7 @@ function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
|||||||
}),
|
}),
|
||||||
buildAssistantSafetyRefusalReply: () => "safety",
|
buildAssistantSafetyRefusalReply: () => "safety",
|
||||||
buildAssistantDataScopeContractReply: () => "scope",
|
buildAssistantDataScopeContractReply: () => "scope",
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: () => "",
|
||||||
buildAssistantOrganizationFactBoundaryReply: () => "org-boundary",
|
buildAssistantOrganizationFactBoundaryReply: () => "org-boundary",
|
||||||
buildAssistantDataScopeSelectionReply: () => "org-selection",
|
buildAssistantDataScopeSelectionReply: () => "org-selection",
|
||||||
buildAssistantOperationalBoundaryReply: () => "ops",
|
buildAssistantOperationalBoundaryReply: () => "ops",
|
||||||
@@ -134,6 +136,56 @@ describe("assistant living chat runtime adapter", () => {
|
|||||||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("adds proactive organization offer on first smalltalk turn when multiple organizations are available", async () => {
|
||||||
|
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||||
|
status: "resolved",
|
||||||
|
channel: "default",
|
||||||
|
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"],
|
||||||
|
error: null
|
||||||
|
}));
|
||||||
|
const input = buildRuntimeInput({
|
||||||
|
userMessage: "привет, как дела?",
|
||||||
|
resolveDataScopeProbe,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => {
|
||||||
|
const organizations = Array.isArray(scopeProbe?.organizations) ? scopeProbe.organizations.join(", ") : "";
|
||||||
|
return `offer:${organizations}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = await runAssistantLivingChatRuntime(input);
|
||||||
|
|
||||||
|
expect(output.handled).toBe(true);
|
||||||
|
expect(output.chatText).toContain("llm-text");
|
||||||
|
expect(output.chatText).toContain("offer:ООО Альтернатива Плюс, ООО Лайсвуд, РАЙМ");
|
||||||
|
expect(output.debug?.living_chat_response_source).toBe("llm_chat_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(["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add proactive organization offer after the session already has assistant context", async () => {
|
||||||
|
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||||
|
status: "resolved",
|
||||||
|
channel: "default",
|
||||||
|
organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд"],
|
||||||
|
error: null
|
||||||
|
}));
|
||||||
|
const input = buildRuntimeInput({
|
||||||
|
userMessage: "привет еще раз",
|
||||||
|
sessionItems: [{ role: "assistant", text: "Ранее уже отвечал." }],
|
||||||
|
resolveDataScopeProbe,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: () => "offer"
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = await runAssistantLivingChatRuntime(input);
|
||||||
|
|
||||||
|
expect(output.handled).toBe(true);
|
||||||
|
expect(output.chatText).toBe("llm-text");
|
||||||
|
expect(output.debug?.living_chat_response_source).toBe("llm_chat");
|
||||||
|
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(false);
|
||||||
|
expect(resolveDataScopeProbe).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("builds deterministic memory recap for prior selected-object address context", async () => {
|
it("builds deterministic memory recap for prior selected-object address context", async () => {
|
||||||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||||
const input = buildRuntimeInput({
|
const input = buildRuntimeInput({
|
||||||
@@ -144,6 +196,9 @@ describe("assistant living chat runtime adapter", () => {
|
|||||||
role: "assistant",
|
role: "assistant",
|
||||||
debug: {
|
debug: {
|
||||||
execution_lane: "address_query",
|
execution_lane: "address_query",
|
||||||
|
answer_grounding_check: {
|
||||||
|
status: "grounded"
|
||||||
|
},
|
||||||
detected_intent: "inventory_purchase_provenance_for_item",
|
detected_intent: "inventory_purchase_provenance_for_item",
|
||||||
extracted_filters: {
|
extracted_filters: {
|
||||||
item: "Зеркало для инвалидов поворотное травмобезопасное",
|
item: "Зеркало для инвалидов поворотное травмобезопасное",
|
||||||
@@ -160,7 +215,7 @@ describe("assistant living chat runtime adapter", () => {
|
|||||||
|
|
||||||
expect(output.handled).toBe(true);
|
expect(output.handled).toBe(true);
|
||||||
expect(output.chatText).toContain("Зеркало для инвалидов поворотное травмобезопасное");
|
expect(output.chatText).toContain("Зеркало для инвалидов поворотное травмобезопасное");
|
||||||
expect(output.chatText).toContain("кто поставил");
|
expect(output.chatText).toContain("кто поставлял");
|
||||||
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
|
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
|
||||||
expect(executeLlmChat).not.toHaveBeenCalled();
|
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -212,6 +212,48 @@ describe("assistantTransitionPolicy", () => {
|
|||||||
expect(contract.decision).toBe("continue_previous");
|
expect(contract.decision).toBe("continue_previous");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retargets same-date inventory follow-up away from receivables intent", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => ({
|
||||||
|
text: "Подтвержденная дебиторская задолженность на 31.03.2020 собрана.",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "receivables_confirmed_as_of_date",
|
||||||
|
extracted_filters: {
|
||||||
|
organization: 'ООО "Альтернатива Плюс"',
|
||||||
|
as_of_date: "2020-03-31",
|
||||||
|
period_from: "2020-03-01",
|
||||||
|
period_to: "2020-03-31"
|
||||||
|
},
|
||||||
|
anchor_type: "organization",
|
||||||
|
anchor_value_resolved: 'ООО "Альтернатива Плюс"'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
hasAddressFollowupContextSignal: () => true,
|
||||||
|
hasReferentialPointer: () => true,
|
||||||
|
findRecentInventoryRootFrame: () => null,
|
||||||
|
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"остатки по складу на эту же дату",
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
|
||||||
|
expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date");
|
||||||
|
expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date");
|
||||||
|
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||||
|
organization: 'ООО "Альтернатива Плюс"',
|
||||||
|
as_of_date: "2020-03-31",
|
||||||
|
period_from: "2020-03-01",
|
||||||
|
period_to: "2020-03-31"
|
||||||
|
});
|
||||||
|
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
|
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => ({
|
findLastAddressAssistantItem: () => ({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"questions": [
|
"questions": [
|
||||||
"какие остатки на складе на март 2021",
|
"какие остатки на складе на март 2021",
|
||||||
"давай по Альтернативе Плюс",
|
"давай по Альтернативе Плюс",
|
||||||
"тогда покажи остатки на март 2021",
|
"тогда покажи остатки на июль2017",
|
||||||
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
"По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?",
|
||||||
"а по этой позиции когда была закупка?",
|
"а по этой позиции когда была закупка?",
|
||||||
"покажи документы по этой позиции",
|
"покажи документы по этой позиции",
|
||||||
|
|||||||
+2
-3
@@ -2,11 +2,10 @@
|
|||||||
"suite_id": "assistant_saved_session_gen-ag04171508-760111",
|
"suite_id": "assistant_saved_session_gen-ag04171508-760111",
|
||||||
"suite_version": "0.1.0",
|
"suite_version": "0.1.0",
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||||
"generated_at": "2026-04-17T15:08:06+00:00",
|
"generated_at": "2026-04-18T07:12:37.854Z",
|
||||||
"generation_id": "gen-ag04171508-760111",
|
"generation_id": "gen-ag04171508-760111",
|
||||||
"mode": "saved_user_sessions",
|
"mode": "saved_user_sessions",
|
||||||
"title": "AGENT replay for inventory clarification continuity and answer-shape cleanliness",
|
"title": "AGENT replay for inventory clarification continuity and answer-shape cleanliness",
|
||||||
"domain": "inventory_answer_shape_and_continuity",
|
|
||||||
"scenario_count": 1,
|
"scenario_count": 1,
|
||||||
"case_ids": [
|
"case_ids": [
|
||||||
"SAVED-001"
|
"SAVED-001"
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
"user_message": "давай по Альтернативе Плюс"
|
"user_message": "давай по Альтернативе Плюс"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user_message": "тогда покажи остатки на март 2021"
|
"user_message": "тогда покажи остатки на июль2017"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
|
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"suite_id": "assistant_saved_session_runtime_job-a4V7FoXsdC",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
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 charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>NDC AI Normalizer Playground</title>
|
<title>NDC AI Normalizer Playground</title>
|
||||||
<script type="module" crossorigin src="/assets/index-C8U6PD78.js"></script>
|
<script type="module" crossorigin src="/assets/index-3F56oUw0.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CfrZGsZo.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DNDajOYc.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState, type SyntheticEvent } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState, type DragEvent, type KeyboardEvent, type SyntheticEvent } from "react";
|
||||||
import { apiClient } from "../api/client";
|
import { apiClient } from "../api/client";
|
||||||
import type {
|
import type {
|
||||||
AssistantConversationItem,
|
AssistantConversationItem,
|
||||||
@@ -553,6 +553,22 @@ function CopyOutlineIcon() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function QuestionGripIcon() {
|
||||||
|
return (
|
||||||
|
<svg className="autoruns-question-grip-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
||||||
|
<circle cx="4" cy="4" r="1" />
|
||||||
|
<circle cx="8" cy="4" r="1" />
|
||||||
|
<circle cx="12" cy="4" r="1" />
|
||||||
|
<circle cx="4" cy="8" r="1" />
|
||||||
|
<circle cx="8" cy="8" r="1" />
|
||||||
|
<circle cx="12" cy="8" r="1" />
|
||||||
|
<circle cx="4" cy="12" r="1" />
|
||||||
|
<circle cx="8" cy="12" r="1" />
|
||||||
|
<circle cx="12" cy="12" r="1" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function AutoRunsHistoryPanel({
|
export function AutoRunsHistoryPanel({
|
||||||
connection,
|
connection,
|
||||||
modelOptions,
|
modelOptions,
|
||||||
@@ -605,6 +621,11 @@ export function AutoRunsHistoryPanel({
|
|||||||
const [autoGenHistory, setAutoGenHistory] = useState<AutoGenHistoryRecord[]>([]);
|
const [autoGenHistory, setAutoGenHistory] = useState<AutoGenHistoryRecord[]>([]);
|
||||||
const [selectedAutogenGenerationId, setSelectedAutogenGenerationId] = useState("");
|
const [selectedAutogenGenerationId, setSelectedAutogenGenerationId] = useState("");
|
||||||
const [editableGeneratedQuestions, setEditableGeneratedQuestions] = useState<string[]>([]);
|
const [editableGeneratedQuestions, setEditableGeneratedQuestions] = useState<string[]>([]);
|
||||||
|
const [generatedQuestionsBusy, setGeneratedQuestionsBusy] = useState(false);
|
||||||
|
const [editingQuestionIndex, setEditingQuestionIndex] = useState<number | null>(null);
|
||||||
|
const [editingQuestionDraft, setEditingQuestionDraft] = useState("");
|
||||||
|
const [draggingQuestionIndex, setDraggingQuestionIndex] = useState<number | null>(null);
|
||||||
|
const [dragOverQuestionIndex, setDragOverQuestionIndex] = useState<number | null>(null);
|
||||||
const [activeAsyncJob, setActiveAsyncJob] = useState<AsyncEvalRunJob | null>(null);
|
const [activeAsyncJob, setActiveAsyncJob] = useState<AsyncEvalRunJob | null>(null);
|
||||||
const [postAnalysis, setPostAnalysis] = useState<AutoRunPostAnalysisResponse | null>(null);
|
const [postAnalysis, setPostAnalysis] = useState<AutoRunPostAnalysisResponse | null>(null);
|
||||||
const [autoGenBusy, setAutoGenBusy] = useState(false);
|
const [autoGenBusy, setAutoGenBusy] = useState(false);
|
||||||
@@ -674,6 +695,7 @@ export function AutoRunsHistoryPanel({
|
|||||||
|
|
||||||
const initialLoadDoneRef = useRef(false);
|
const initialLoadDoneRef = useRef(false);
|
||||||
const asyncJobPollTimerRef = useRef<number | null>(null);
|
const asyncJobPollTimerRef = useRef<number | null>(null);
|
||||||
|
const questionEditorRef = useRef<HTMLInputElement | null>(null);
|
||||||
const isSavedUserSessionsMode = autoGenSettings.mode === "saved_user_sessions";
|
const isSavedUserSessionsMode = autoGenSettings.mode === "saved_user_sessions";
|
||||||
const selectedPersonality = useMemo(
|
const selectedPersonality = useMemo(
|
||||||
() => autogenPersonalities.find((item) => item.id === autoGenSettings.personalityId) ?? autogenPersonalities[0] ?? AUTOGEN_PERSONALITIES[0],
|
() => autogenPersonalities.find((item) => item.id === autoGenSettings.personalityId) ?? autogenPersonalities[0] ?? AUTOGEN_PERSONALITIES[0],
|
||||||
@@ -1772,6 +1794,192 @@ export function AutoRunsHistoryPanel({
|
|||||||
savedSessionQuestionDeleteModal.questionIndex
|
savedSessionQuestionDeleteModal.questionIndex
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const updateGeneratedQuestions = useCallback(
|
||||||
|
async (nextQuestions: string[], options?: { successLog?: string; revertQuestions?: string[] }) => {
|
||||||
|
const generationId = selectedAutogenGeneration?.generation_id ?? "";
|
||||||
|
const revertQuestions = options?.revertQuestions ?? editableGeneratedQuestions;
|
||||||
|
|
||||||
|
setEditableGeneratedQuestions(nextQuestions);
|
||||||
|
if (!generationId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGeneratedQuestionsBusy(true);
|
||||||
|
try {
|
||||||
|
const payload = await apiClient.updateAutoRunAutogenQuestions({
|
||||||
|
generation_id: generationId,
|
||||||
|
questions: nextQuestions
|
||||||
|
});
|
||||||
|
setAutoGenHistory((prev) =>
|
||||||
|
prev.map((item) => (item.generation_id === generationId ? payload.generation : item))
|
||||||
|
);
|
||||||
|
setEditableGeneratedQuestions([...(payload.generation.questions ?? [])]);
|
||||||
|
if (options?.successLog) {
|
||||||
|
log(options.successLog);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
setEditableGeneratedQuestions(revertQuestions);
|
||||||
|
setErrorText(`Вопросы к запуску: ${message}`);
|
||||||
|
log(`Autogen questions update error: ${message}`);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setGeneratedQuestionsBusy(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[editableGeneratedQuestions, log, selectedAutogenGeneration]
|
||||||
|
);
|
||||||
|
|
||||||
|
const startQuestionEdit = useCallback(
|
||||||
|
(questionIndex: number) => {
|
||||||
|
setEditingQuestionIndex(questionIndex);
|
||||||
|
setEditingQuestionDraft(editableGeneratedQuestions[questionIndex] ?? "");
|
||||||
|
},
|
||||||
|
[editableGeneratedQuestions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const stopQuestionEdit = useCallback(() => {
|
||||||
|
setEditingQuestionIndex(null);
|
||||||
|
setEditingQuestionDraft("");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const commitQuestionEdit = useCallback(
|
||||||
|
async (questionIndex: number | null) => {
|
||||||
|
if (questionIndex === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentQuestion = editableGeneratedQuestions[questionIndex] ?? "";
|
||||||
|
const nextText = editingQuestionDraft.trim();
|
||||||
|
if (!nextText || nextText === currentQuestion) {
|
||||||
|
stopQuestionEdit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextQuestions = editableGeneratedQuestions.map((item, index) => (index === questionIndex ? nextText : item));
|
||||||
|
const saved = await updateGeneratedQuestions(nextQuestions, {
|
||||||
|
successLog: `Список вопросов обновлен: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||||
|
revertQuestions: editableGeneratedQuestions
|
||||||
|
});
|
||||||
|
if (saved) {
|
||||||
|
stopQuestionEdit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[editableGeneratedQuestions, editingQuestionDraft, selectedAutogenGeneration, stopQuestionEdit, updateGeneratedQuestions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQuestionEditorBlur = useCallback(() => {
|
||||||
|
void commitQuestionEdit(editingQuestionIndex);
|
||||||
|
}, [commitQuestionEdit, editingQuestionIndex]);
|
||||||
|
|
||||||
|
const handleQuestionEditorKeyDown = useCallback(
|
||||||
|
(event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
void commitQuestionEdit(editingQuestionIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
stopQuestionEdit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[commitQuestionEdit, editingQuestionIndex, stopQuestionEdit]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddGeneratedQuestion = useCallback(async () => {
|
||||||
|
const nextQuestions = [...editableGeneratedQuestions, "Новый вопрос"];
|
||||||
|
const nextIndex = nextQuestions.length - 1;
|
||||||
|
const saved = await updateGeneratedQuestions(nextQuestions, {
|
||||||
|
successLog: `В список добавлен вопрос: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||||
|
revertQuestions: editableGeneratedQuestions
|
||||||
|
});
|
||||||
|
if (saved) {
|
||||||
|
setEditingQuestionIndex(nextIndex);
|
||||||
|
setEditingQuestionDraft(nextQuestions[nextIndex]);
|
||||||
|
}
|
||||||
|
}, [editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]);
|
||||||
|
|
||||||
|
const handleDeleteGeneratedQuestion = useCallback(
|
||||||
|
async (questionIndex: number) => {
|
||||||
|
if (editableGeneratedQuestions.length <= 1) {
|
||||||
|
setErrorText("В списке должен остаться хотя бы один вопрос.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextQuestions = editableGeneratedQuestions.filter((_, index) => index !== questionIndex);
|
||||||
|
const saved = await updateGeneratedQuestions(nextQuestions, {
|
||||||
|
successLog: `Из списка удален вопрос: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||||
|
revertQuestions: editableGeneratedQuestions
|
||||||
|
});
|
||||||
|
if (!saved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditingQuestionIndex((prev) => {
|
||||||
|
if (prev === null) return prev;
|
||||||
|
if (prev === questionIndex) return null;
|
||||||
|
if (prev > questionIndex) return prev - 1;
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
setEditingQuestionDraft("");
|
||||||
|
},
|
||||||
|
[editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQuestionDragStart = useCallback(
|
||||||
|
(event: DragEvent<HTMLButtonElement>, questionIndex: number) => {
|
||||||
|
if (generatedQuestionsBusy) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDraggingQuestionIndex(questionIndex);
|
||||||
|
setDragOverQuestionIndex(questionIndex);
|
||||||
|
event.dataTransfer.effectAllowed = "move";
|
||||||
|
event.dataTransfer.setData("text/plain", String(questionIndex));
|
||||||
|
},
|
||||||
|
[generatedQuestionsBusy]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQuestionDragOver = useCallback(
|
||||||
|
(event: DragEvent<HTMLDivElement>, questionIndex: number) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (dragOverQuestionIndex !== questionIndex) {
|
||||||
|
setDragOverQuestionIndex(questionIndex);
|
||||||
|
}
|
||||||
|
event.dataTransfer.dropEffect = "move";
|
||||||
|
},
|
||||||
|
[dragOverQuestionIndex]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQuestionDrop = useCallback(
|
||||||
|
async (event: DragEvent<HTMLDivElement>, questionIndex: number) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const fromIndex = draggingQuestionIndex;
|
||||||
|
setDragOverQuestionIndex(null);
|
||||||
|
setDraggingQuestionIndex(null);
|
||||||
|
if (fromIndex === null || fromIndex === questionIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextQuestions = [...editableGeneratedQuestions];
|
||||||
|
const [movedQuestion] = nextQuestions.splice(fromIndex, 1);
|
||||||
|
nextQuestions.splice(questionIndex, 0, movedQuestion);
|
||||||
|
await updateGeneratedQuestions(nextQuestions, {
|
||||||
|
successLog: `Порядок вопросов обновлен: ${selectedAutogenGeneration?.generation_id ?? "local"}`,
|
||||||
|
revertQuestions: editableGeneratedQuestions
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[draggingQuestionIndex, editableGeneratedQuestions, selectedAutogenGeneration, updateGeneratedQuestions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQuestionDragEnd = useCallback(() => {
|
||||||
|
setDraggingQuestionIndex(null);
|
||||||
|
setDragOverQuestionIndex(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const openAutoGenDeleteModal = useCallback((item: AutoGenHistoryRecord) => {
|
const openAutoGenDeleteModal = useCallback((item: AutoGenHistoryRecord) => {
|
||||||
setAutoGenDeleteModal({
|
setAutoGenDeleteModal({
|
||||||
open: true,
|
open: true,
|
||||||
@@ -1905,10 +2113,27 @@ export function AutoRunsHistoryPanel({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedAutogenGeneration) {
|
if (!selectedAutogenGeneration) {
|
||||||
setEditableGeneratedQuestions([]);
|
setEditableGeneratedQuestions([]);
|
||||||
|
stopQuestionEdit();
|
||||||
|
setDraggingQuestionIndex(null);
|
||||||
|
setDragOverQuestionIndex(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setEditableGeneratedQuestions([...selectedAutogenGeneration.questions]);
|
setEditableGeneratedQuestions([...selectedAutogenGeneration.questions]);
|
||||||
}, [selectedAutogenGeneration]);
|
stopQuestionEdit();
|
||||||
|
setDraggingQuestionIndex(null);
|
||||||
|
setDragOverQuestionIndex(null);
|
||||||
|
}, [selectedAutogenGeneration, stopQuestionEdit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editingQuestionIndex === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
questionEditorRef.current?.focus();
|
||||||
|
questionEditorRef.current?.select();
|
||||||
|
}, 0);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [editingQuestionIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLimitInput(String(filters.limit));
|
setLimitInput(String(filters.limit));
|
||||||
@@ -2403,6 +2628,9 @@ export function AutoRunsHistoryPanel({
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{false ? (
|
||||||
|
<>
|
||||||
|
{/* generated questions editor */}
|
||||||
<div className="autoruns-generated-questions">
|
<div className="autoruns-generated-questions">
|
||||||
<div className="autoruns-generated-questions-head">
|
<div className="autoruns-generated-questions-head">
|
||||||
<strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
|
<strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
|
||||||
@@ -2451,6 +2679,99 @@ export function AutoRunsHistoryPanel({
|
|||||||
? "Запуск воспроизводит сохраненную пользовательскую сессию как один последовательный multi-turn сценарий assistant_stage1."
|
? "Запуск воспроизводит сохраненную пользовательскую сессию как один последовательный multi-turn сценарий assistant_stage1."
|
||||||
: "Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}
|
: "Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}
|
||||||
</p>
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="autoruns-generated-questions">
|
||||||
|
<div className="autoruns-generated-questions-head">
|
||||||
|
<strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
|
||||||
|
</div>
|
||||||
|
{editableGeneratedQuestions.length === 0 ? (
|
||||||
|
<p className="muted">
|
||||||
|
{isSavedUserSessionsMode
|
||||||
|
? "Список вопросов пуст. Сначала сохраните живую пользовательскую сессию."
|
||||||
|
: "Список вопросов пуст. Сгенерируйте пачку или добавьте вопрос вручную."}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="autoruns-generated-questions-list">
|
||||||
|
{editableGeneratedQuestions.map((question, index) => (
|
||||||
|
<div
|
||||||
|
key={`${index}-${question.slice(0, 24)}`}
|
||||||
|
className={[
|
||||||
|
"autoruns-generated-question-item",
|
||||||
|
dragOverQuestionIndex === index ? "drag-over" : "",
|
||||||
|
draggingQuestionIndex === index ? "dragging" : "",
|
||||||
|
editingQuestionIndex === index ? "editing" : ""
|
||||||
|
].filter(Boolean).join(" ")}
|
||||||
|
onDragOver={(event) => handleQuestionDragOver(event, index)}
|
||||||
|
onDrop={(event) => void handleQuestionDrop(event, index)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="autoruns-question-grip-btn"
|
||||||
|
draggable={!generatedQuestionsBusy && editingQuestionIndex !== index}
|
||||||
|
disabled={generatedQuestionsBusy || editingQuestionIndex === index}
|
||||||
|
onDragStart={(event) => handleQuestionDragStart(event, index)}
|
||||||
|
onDragEnd={handleQuestionDragEnd}
|
||||||
|
title="Перетащить вопрос"
|
||||||
|
aria-label={`Перетащить вопрос ${index + 1}`}
|
||||||
|
>
|
||||||
|
<QuestionGripIcon />
|
||||||
|
</button>
|
||||||
|
{editingQuestionIndex === index ? (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
ref={questionEditorRef}
|
||||||
|
className="autoruns-generated-question-input"
|
||||||
|
value={editingQuestionDraft}
|
||||||
|
onChange={(event) => setEditingQuestionDraft(event.target.value)}
|
||||||
|
onBlur={handleQuestionEditorBlur}
|
||||||
|
onKeyDown={handleQuestionEditorKeyDown}
|
||||||
|
placeholder="Текст вопроса"
|
||||||
|
disabled={generatedQuestionsBusy}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="autoruns-remove-question-btn"
|
||||||
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
|
onClick={() => void handleDeleteGeneratedQuestion(index)}
|
||||||
|
title="Удалить вопрос"
|
||||||
|
aria-label={`Удалить вопрос ${index + 1}`}
|
||||||
|
disabled={generatedQuestionsBusy}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="autoruns-generated-question-text"
|
||||||
|
onDoubleClick={() => startQuestionEdit(index)}
|
||||||
|
title="Двойной клик для редактирования"
|
||||||
|
>
|
||||||
|
{index + 1}. {question}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="autoruns-add-question-btn"
|
||||||
|
onClick={() => void handleAddGeneratedQuestion()}
|
||||||
|
disabled={!selectedAutogenGeneration || generatedQuestionsBusy}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{isSavedUserSessionsMode ? (
|
||||||
|
<h4>Сохраненные пользовательские сессии</h4>
|
||||||
|
) : (
|
||||||
|
<p className="muted">Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету.</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="autoruns-autogen-list">
|
<div className="autoruns-autogen-list">
|
||||||
{autogenHistoryBusy ? (
|
{autogenHistoryBusy ? (
|
||||||
|
|||||||
@@ -1535,24 +1535,137 @@ button:disabled {
|
|||||||
|
|
||||||
.autoruns-generated-question-item {
|
.autoruns-generated-question-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: grid;
|
||||||
|
grid-template-columns: 22px minmax(0, 1fr) auto;
|
||||||
|
align-items: start;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
background: rgb(var(--rgb-surface-focus));
|
background: rgb(var(--rgb-surface-focus));
|
||||||
padding: 7px 30px 7px 8px;
|
padding: 7px 8px;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
|
transition: background 0.15s ease, outline-color 0.15s ease, opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoruns-generated-question-item span {
|
.autoruns-generated-question-item.drag-over {
|
||||||
display: block;
|
outline: 1px solid rgba(var(--rgb-active), 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.dragging {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing {
|
||||||
|
background: rgb(var(--rgb-active));
|
||||||
|
color: rgb(var(--rgb-active-text));
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-question-grip-btn {
|
||||||
|
width: 18px;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: grab;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-question-grip-btn:hover:not(:disabled) {
|
||||||
|
color: rgb(var(--rgb-text-main));
|
||||||
|
background: rgba(var(--rgb-background), 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing .autoruns-question-grip-btn {
|
||||||
|
color: rgba(var(--rgb-active-text), 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing .autoruns-question-grip-btn:hover:not(:disabled) {
|
||||||
|
color: rgb(var(--rgb-active-text));
|
||||||
|
background: rgba(var(--rgb-active-text), 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-question-grip-btn:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-question-grip-svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-text {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: rgb(var(--rgb-text-main));
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
font: inherit;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.4;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-text:hover {
|
||||||
|
color: rgb(var(--rgb-active));
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(var(--rgb-background), 0.55);
|
||||||
|
color: rgb(var(--rgb-text-main));
|
||||||
|
padding: 6px 8px;
|
||||||
|
font: inherit;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing .autoruns-generated-question-input {
|
||||||
|
background: rgba(var(--rgb-active-text), 0.14);
|
||||||
|
color: rgb(var(--rgb-active-text));
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing .autoruns-generated-question-input::placeholder {
|
||||||
|
color: rgba(var(--rgb-active-text), 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-add-question-btn {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
background: rgb(var(--rgb-surface-focus));
|
||||||
|
color: rgb(var(--rgb-text-main));
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-add-question-btn:hover:not(:disabled) {
|
||||||
|
background: rgb(var(--rgb-active));
|
||||||
|
color: rgb(var(--rgb-active-text));
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-add-question-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoruns-remove-question-btn {
|
.autoruns-remove-question-btn {
|
||||||
position: absolute;
|
|
||||||
top: 6px;
|
|
||||||
right: 6px;
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@@ -1570,6 +1683,7 @@ button:disabled {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transition: color 0.15s ease;
|
transition: color 0.15s ease;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoruns-remove-question-btn:hover {
|
.autoruns-remove-question-btn:hover {
|
||||||
@@ -1578,6 +1692,14 @@ button:disabled {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing .autoruns-remove-question-btn {
|
||||||
|
color: rgb(var(--rgb-active-text));
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoruns-generated-question-item.editing .autoruns-remove-question-btn:hover {
|
||||||
|
color: rgba(var(--rgb-active-text), 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
.autoruns-remove-question-btn:focus-visible {
|
.autoruns-remove-question-btn:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user