АРЧ - Ассистент: отделить meta-followup по прошлому ответу от повторного запуска address lane

This commit is contained in:
2026-04-15 12:38:44 +03:00
parent 8866176be6
commit 70cc5a99f1
61 changed files with 4023 additions and 298 deletions
@@ -2453,6 +2453,62 @@ function findRecentAddressFilterValue(items, key) {
}
return null;
}
function isInventoryRootFrameIntent(intent) {
return intent === "inventory_on_hand_as_of_date";
}
function isInventoryDrilldownFrameIntent(intent) {
return intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date";
}
function extractAddressCarryoverAnchor(addressDebug) {
if (!isAddressLaneDebugPayload(addressDebug)) {
return {
anchorType: null,
anchorValue: null
};
}
return {
anchorType: toNonEmptyString(addressDebug.anchor_type),
anchorValue: toNonEmptyString(addressDebug.anchor_value_resolved) ??
toNonEmptyString(addressDebug.anchor_value_raw) ??
readAddressInventoryItemFilter(addressDebug) ??
readAddressFilterString(addressDebug, "counterparty") ??
readAddressFilterString(addressDebug, "contract") ??
readAddressFilterString(addressDebug, "account")
};
}
function findRecentInventoryRootFrame(items) {
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug) {
continue;
}
const debug = item.debug;
if (!isAddressLaneDebugPayload(debug)) {
continue;
}
const detectedIntent = toNonEmptyString(debug.detected_intent);
if (!isInventoryRootFrameIntent(detectedIntent)) {
continue;
}
const anchor = extractAddressCarryoverAnchor(debug);
const filtersRaw = debug.extracted_filters;
const filters = filtersRaw && typeof filtersRaw === "object"
? { ...filtersRaw }
: {};
return {
intent: detectedIntent,
filters,
anchorType: anchor.anchorType,
anchorValue: anchor.anchorValue,
messageId: toNonEmptyString(item.message_id)
};
}
return null;
}
const ADDRESS_FOLLOWUP_OFFER_BY_INTENT = {
list_documents_by_counterparty: ["bank_operations_by_counterparty", "list_contracts_by_counterparty"],
bank_operations_by_counterparty: ["list_documents_by_counterparty", "list_contracts_by_counterparty"],
@@ -2755,6 +2811,14 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
readAddressFilterString(previousAddressDebug, "counterparty") ??
readAddressFilterString(previousAddressDebug, "account") ??
readAddressFilterString(previousAddressDebug, "contract");
const inventoryRootFrame = findRecentInventoryRootFrame(items);
const currentFrameKind = inventoryRootFrame
? isInventoryDrilldownFrameIntent(sourceIntent)
? "inventory_drilldown"
: isInventoryRootFrameIntent(sourceIntent)
? "inventory_root"
: "generic"
: null;
let resolvedCounterpartyFromDisplay = false;
const previousFiltersRaw = previousAddressDebug.extracted_filters;
const previousFilters = previousFiltersRaw && typeof previousFiltersRaw === "object"
@@ -2814,7 +2878,12 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
previous_filters: previousFilters,
previous_anchor_type: previousAnchorType ?? undefined,
previous_anchor_value: previousAnchor,
resolved_counterparty_from_display: resolvedCounterpartyFromDisplay || undefined
resolved_counterparty_from_display: resolvedCounterpartyFromDisplay || undefined,
root_intent: inventoryRootFrame?.intent ?? undefined,
root_filters: inventoryRootFrame?.filters ?? undefined,
root_anchor_type: inventoryRootFrame?.anchorType ?? undefined,
root_anchor_value: inventoryRootFrame?.anchorValue ?? undefined,
current_frame_kind: currentFrameKind ?? undefined
},
previousAddressIntent: previousIntent,
previousAddressAnchor: previousAnchor,
@@ -2890,19 +2959,32 @@ function isAddressLlmPreDecomposeCandidate(userMessage) {
}
return /(?:\bдок\b|доки|документ|контрагент|договор|остаток|сч(?:е|ё)т|сальдо|банк|выписк|платеж|оплат|поступлен|поступлени|списан|реализац|сверк|взаиморасч|кто\s+должен|show|list|documents?|counterparty|contract|account|balance|bank\s+operations?|doki|dokument(?:y|ov|am|a)?|platezh|oplata|schet|saldo)/i.test(text);
}
function extractAddressQuestionFromNormalized(normalized) {
if (!normalized || typeof normalized !== "object") {
function normalizeAddressSemanticHintsFromFragment(fragment) {
if (!fragment || typeof fragment !== "object") {
return null;
}
const source = normalized;
const fragments = Array.isArray(source.fragments) ? source.fragments : [];
for (const item of fragments) {
const hints = fragment.semantic_hints;
if (!hints || typeof hints !== "object") {
return null;
}
const scopeTargetKind = toNonEmptyString(hints.scope_target_kind);
const dateScopeKind = toNonEmptyString(hints.date_scope_kind);
return {
scope_target_kind: scopeTargetKind ?? "none",
scope_target_text: toNonEmptyString(hints.scope_target_text),
date_scope_kind: dateScopeKind ?? "missing",
self_scope_detected: hints.self_scope_detected === true || scopeTargetKind === "self_scope",
selected_object_scope_detected: hints.selected_object_scope_detected === true || scopeTargetKind === "selected_object"
};
}
function extractAddressPredecomposeCandidateFromFragments(fragments) {
for (const item of Array.isArray(fragments) ? fragments : []) {
if (!item || typeof item !== "object") {
continue;
}
const fragment = item;
const domainRelevance = String(fragment.domain_relevance ?? "").trim().toLowerCase();
if (domainRelevance === "out_of_scope") {
if (domainRelevance === "out_of_scope" || domainRelevance === "offtopic") {
continue;
}
const normalizedText = toNonEmptyString(fragment.normalized_fragment_text);
@@ -2912,11 +2994,20 @@ function extractAddressQuestionFromNormalized(normalized) {
continue;
}
if (candidate.length >= 3 && candidate.length <= 500) {
return candidate;
return {
candidate,
semanticHints: normalizeAddressSemanticHintsFromFragment(fragment)
};
}
}
return null;
}
function extractAddressPredecomposeCandidateFromNormalized(normalized) {
if (!normalized || typeof normalized !== "object") {
return null;
}
return extractAddressPredecomposeCandidateFromFragments(normalized.fragments);
}
function stripMarkdownJsonFence(text) {
return String(text ?? "")
.trim()
@@ -2994,7 +3085,7 @@ function extractOutputTextFromRawNormalizerOutput(raw) {
}
return null;
}
function extractAddressQuestionFromRawNormalizerOutput(rawModelOutput) {
function extractAddressPredecomposeCandidateFromRawNormalizerOutput(rawModelOutput) {
const outputText = extractOutputTextFromRawNormalizerOutput(rawModelOutput);
if (!outputText) {
return null;
@@ -3003,31 +3094,7 @@ function extractAddressQuestionFromRawNormalizerOutput(rawModelOutput) {
if (!parsed || typeof parsed !== "object") {
return null;
}
const source = parsed;
const fragments = Array.isArray(source.fragments) ? source.fragments : [];
for (const item of fragments) {
if (!item || typeof item !== "object") {
continue;
}
const fragment = item;
const domainRelevance = fragment.domain_relevance;
if (typeof domainRelevance === "string" && domainRelevance.trim().toLowerCase() === "out_of_scope") {
continue;
}
if (domainRelevance === false) {
continue;
}
const normalizedText = toNonEmptyString(fragment.normalized_fragment_text);
const rawText = toNonEmptyString(fragment.raw_fragment_text);
const candidate = selectPreferredAddressFragmentCandidate(rawText ?? "", normalizedText ?? "");
if (!candidate) {
continue;
}
if (candidate.length >= 3 && candidate.length <= 500) {
return candidate;
}
}
return null;
return extractAddressPredecomposeCandidateFromFragments(parsed.fragments);
}
const ADDRESS_PREDECOMPOSE_LOW_QUALITY_COUNTERPARTY_TOKENS = new Set([
"есть",
@@ -3267,7 +3334,8 @@ function attachAddressPredecomposeContract(meta, sourceMessage) {
const canonicalMessage = toNonEmptyString(meta?.effectiveMessage) ?? String(sourceMessage ?? "");
const predecomposeContract = (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(sourceMessage ?? ""),
canonicalMessage
canonicalMessage,
semanticHints: meta?.semanticHints ?? null
});
const semanticExtractionContract = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
sourceMessage: String(sourceMessage ?? ""),
@@ -3332,31 +3400,34 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
};
try {
const normalized = await normalizerService.normalize(normalizePayload);
const candidateFromNormalized = extractAddressQuestionFromNormalized(normalized?.normalized);
const candidateFromRaw = candidateFromNormalized ? null : extractAddressQuestionFromRawNormalizerOutput(normalized?.raw_model_output);
const candidate = candidateFromNormalized ?? candidateFromRaw;
const candidateFromNormalized = extractAddressPredecomposeCandidateFromNormalized(normalized?.normalized);
const candidateFromRaw = candidateFromNormalized ? null : extractAddressPredecomposeCandidateFromRawNormalizerOutput(normalized?.raw_model_output);
const candidateMeta = candidateFromNormalized ?? candidateFromRaw;
const candidate = candidateMeta?.candidate ?? null;
if (!candidate) {
if (fallbackCandidate) {
const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase());
const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase());
const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact;
if (fallbackApplied) {
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: true,
traceId: normalized?.trace_id ?? null,
effectiveMessage: fallbackCandidate.candidate,
reason: "fallback_rule_applied_after_llm",
fallbackRuleHit: fallbackCandidate.rule
}, userMessage);
}
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: true,
traceId: normalized?.trace_id ?? null,
effectiveMessage: fallbackCandidate.candidate,
reason: "fallback_rule_applied_after_llm",
fallbackRuleHit: fallbackCandidate.rule,
semanticHints: null
}, userMessage);
}
}
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
traceId: normalized?.trace_id ?? null,
reason: normalized?.ok ? "no_usable_fragment" : "normalize_failed"
reason: normalized?.ok ? "no_usable_fragment" : "normalize_failed",
semanticHints: null
}, userMessage);
}
const repairedSourceMessage = repairAddressMojibake(userMessage);
@@ -3375,7 +3446,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_diagnostic_rewrite",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const intentConflict = sourceIntentKnown &&
@@ -3397,7 +3469,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
? "normalized_fragment_rejected_intent_drop"
: "normalized_fragment_rejected_intent_conflict",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sourceHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(repairedSourceMessage || userMessage);
@@ -3418,7 +3491,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_followup_intent_injection",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sourceHasSelectedObjectInventoryFollowup = hasSelectedObjectInventoryFollowupSignalForPredecompose(repairedSourceMessage || userMessage);
@@ -3438,7 +3512,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_selected_object_context_loss",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
@@ -3464,7 +3539,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_anchor_substitution",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const anchorDegradedByCandidate = sameIntentForAnchorSafety &&
@@ -3481,7 +3557,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_anchor_degradation",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
if (fallbackCandidate) {
@@ -3500,19 +3577,25 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: fallbackCandidate.candidate,
reason: "fallback_rule_preferred_over_llm_candidate_anchor_quality",
fallbackRuleHit: fallbackCandidate.rule,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
}
const semanticContractForCandidate = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate
canonicalMessage: candidate,
predecomposeContract: (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
semanticHints: candidateMeta?.semanticHints ?? null
})
});
if (!semanticContractForCandidate.apply_canonical_recommended) {
const sourceDataSignalDetected = Boolean(semanticContractForCandidate?.guard_hints?.source_data_signal_detected);
const rawFragmentCandidatePreferred = Boolean(sourceDataSignalDetected &&
candidateFromNormalized &&
candidateFromNormalized === candidate &&
candidateFromNormalized.candidate === candidate &&
toNonEmptyString(candidate));
if (rawFragmentCandidatePreferred) {
return attachAddressPredecomposeContract({
@@ -3524,7 +3607,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: candidate,
reason: "normalized_fragment_semantic_guard_raw_fragment_preferred",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
if (fallbackCandidate) {
@@ -3545,7 +3629,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: String(fallbackCandidate.candidate ?? ""),
reason: "fallback_rule_preferred_over_llm_candidate_semantic_guard",
fallbackRuleHit: fallbackCandidate.rule,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
}
@@ -3558,7 +3643,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_semantic_guard",
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase());
@@ -3585,7 +3671,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
reason,
llmCanonicalCandidateDetected: true,
fallbackRuleHit: null,
sanitizedUserMessage
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
catch (error) {
@@ -3933,7 +4020,11 @@ export function resolveAssistantOrchestrationDecision(input) {
hasOpenContractsAddressSignal(repairedEffectiveAddressUserMessage);
const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
const modeDetectionRaw = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedRawUserMessage || rawUserMessage);
const resolvedModeDetection = modeDetection.mode === "address_query" ? modeDetection : modeDetectionRaw;
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
const intentResolutionRaw = (0, addressIntentResolver_1.resolveAddressIntent)(repairedRawUserMessage || rawUserMessage);
const resolvedIntentResolution = intentResolution.intent !== "unknown" ? intentResolution : intentResolutionRaw;
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
const llmRuntimeUnavailableDetected = Boolean(llmPreDecomposeReason &&
@@ -3951,10 +4042,10 @@ export function resolveAssistantOrchestrationDecision(input) {
hasStrictDeepInvestigationCue(repairedRawUserMessage) ||
hasStrictDeepInvestigationCue(effectiveAddressUserMessage) ||
hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage);
const strictDeepInvestigationBypassAllowed = shouldBypassStrictDeepInvestigationCueForAddressIntent(intentResolution.intent) ||
const strictDeepInvestigationBypassAllowed = shouldBypassStrictDeepInvestigationCueForAddressIntent(resolvedIntentResolution.intent) ||
shouldBypassStrictDeepInvestigationCueForAddressIntent(llmContractIntent);
const keepAddressLaneByIntent = semanticApplyCanonicalRecommended &&
Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
openContractsAddressSignal) &&
(!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed);
@@ -3995,8 +4086,8 @@ export function resolveAssistantOrchestrationDecision(input) {
!capabilityMetaQuery &&
!dataRetrievalSignal &&
!effectiveAddressFollowupSignal &&
modeDetection.mode === "unsupported" &&
intentResolution.intent === "unknown");
resolvedModeDetection.mode === "unsupported" &&
resolvedIntentResolution.intent === "unknown");
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
deterministicNonDomainGuard &&
(llmFirstUnsupportedCandidate || llmContractMode === null) &&
@@ -4016,10 +4107,10 @@ export function resolveAssistantOrchestrationDecision(input) {
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "data_scope",
address_mode: modeDetection.mode,
address_mode_confidence: modeDetection.confidence,
address_intent: intentResolution.intent,
address_intent_confidence: intentResolution.confidence,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext),
@@ -4044,10 +4135,10 @@ export function resolveAssistantOrchestrationDecision(input) {
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "capability",
address_mode: modeDetection.mode,
address_mode_confidence: modeDetection.confidence,
address_intent: intentResolution.intent,
address_intent_confidence: intentResolution.confidence,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext),
@@ -4072,10 +4163,10 @@ export function resolveAssistantOrchestrationDecision(input) {
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "non_domain",
address_mode: modeDetection.mode,
address_mode_confidence: modeDetection.confidence,
address_intent: intentResolution.intent,
address_intent_confidence: intentResolution.confidence,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext),
@@ -4111,7 +4202,7 @@ export function resolveAssistantOrchestrationDecision(input) {
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage));
const supportedAddressIntentDetected = (!strictDeepInvestigationCueDetected || strictDeepInvestigationBypassAllowed) &&
Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) ||
Boolean((resolvedIntentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(resolvedIntentResolution.intent)) ||
(llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) ||
openContractsAddressSignal);
const semanticGuardHints = semanticExtractionContract?.guard_hints &&
@@ -4131,7 +4222,7 @@ export function resolveAssistantOrchestrationDecision(input) {
semanticAggregateShapeDetected ||
semanticDeepInvestigationHintDetected ||
!semanticApplyCanonicalRecommended));
const unsupportedIntentOrMode = (modeDetection.mode !== "address_query" && intentResolution.intent === "unknown") ||
const unsupportedIntentOrMode = (resolvedModeDetection.mode !== "address_query" && resolvedIntentResolution.intent === "unknown") ||
llmContractMode === "unsupported";
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
!llmRuntimeUnavailableDetected &&
@@ -4251,10 +4342,10 @@ export function resolveAssistantOrchestrationDecision(input) {
orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: null,
address_mode: modeDetection.mode,
address_mode_confidence: modeDetection.confidence,
address_intent: intentResolution.intent,
address_intent_confidence: intentResolution.confidence,
address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent,
address_intent_confidence: resolvedIntentResolution.confidence,
strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal,
semantic_contract_valid: semanticContractValid,