ARCH: добавить answer adapter MCP discovery
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import type { AssistantMcpDiscoveryPilotExecutionContract } from "./assistantMcpDiscoveryPilotExecutor";
|
||||
|
||||
export const ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION =
|
||||
"assistant_mcp_discovery_answer_draft_v1" as const;
|
||||
|
||||
export type AssistantMcpDiscoveryAnswerMode =
|
||||
| "confirmed_with_bounded_inference"
|
||||
| "bounded_inference_only"
|
||||
| "checked_sources_only"
|
||||
| "needs_clarification"
|
||||
| "blocked";
|
||||
|
||||
export interface AssistantMcpDiscoveryAnswerDraftContract {
|
||||
schema_version: typeof ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION;
|
||||
policy_owner: "assistantMcpDiscoveryAnswerAdapter";
|
||||
answer_mode: AssistantMcpDiscoveryAnswerMode;
|
||||
headline: string;
|
||||
confirmed_lines: string[];
|
||||
inference_lines: string[];
|
||||
unknown_lines: string[];
|
||||
limitation_lines: string[];
|
||||
next_step_line: string | null;
|
||||
internal_mechanics_allowed: false;
|
||||
must_not_claim: string[];
|
||||
reason_codes: string[];
|
||||
}
|
||||
|
||||
function normalizeReasonCode(value: string): string | null {
|
||||
const normalized = value
|
||||
.trim()
|
||||
.replace(/[^\p{L}\p{N}_.:-]+/gu, "_")
|
||||
.replace(/^_+|_+$/g, "")
|
||||
.toLowerCase();
|
||||
return normalized.length > 0 ? normalized.slice(0, 120) : null;
|
||||
}
|
||||
|
||||
function pushReason(target: string[], value: string): void {
|
||||
const normalized = normalizeReasonCode(value);
|
||||
if (normalized && !target.includes(normalized)) {
|
||||
target.push(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
function uniqueStrings(values: string[]): string[] {
|
||||
const result: string[] = [];
|
||||
for (const value of values) {
|
||||
const text = String(value ?? "").trim();
|
||||
if (text && !result.includes(text)) {
|
||||
result.push(text);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isInternalMechanicsLine(value: string): boolean {
|
||||
const text = value.toLowerCase();
|
||||
return (
|
||||
text.includes("primitive") ||
|
||||
text.includes("query_documents") ||
|
||||
text.includes("query_movements") ||
|
||||
text.includes("resolve_entity_reference") ||
|
||||
text.includes("probe_coverage") ||
|
||||
text.includes("explain_evidence_basis") ||
|
||||
text.includes("pilot_only_executes") ||
|
||||
text.includes("runtime_") ||
|
||||
text.includes("planner_") ||
|
||||
text.includes("catalog_")
|
||||
);
|
||||
}
|
||||
|
||||
function userFacingLimitations(values: string[]): string[] {
|
||||
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
|
||||
}
|
||||
|
||||
function modeFor(pilot: AssistantMcpDiscoveryPilotExecutionContract): AssistantMcpDiscoveryAnswerMode {
|
||||
if (pilot.pilot_status === "blocked") {
|
||||
return "blocked";
|
||||
}
|
||||
if (pilot.pilot_status === "skipped_needs_clarification") {
|
||||
return "needs_clarification";
|
||||
}
|
||||
if (pilot.evidence.answer_permission === "confirmed_answer") {
|
||||
return "confirmed_with_bounded_inference";
|
||||
}
|
||||
if (pilot.evidence.answer_permission === "bounded_inference") {
|
||||
return "bounded_inference_only";
|
||||
}
|
||||
return "checked_sources_only";
|
||||
}
|
||||
|
||||
function headlineFor(mode: AssistantMcpDiscoveryAnswerMode): string {
|
||||
if (mode === "confirmed_with_bounded_inference") {
|
||||
return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк.";
|
||||
}
|
||||
if (mode === "bounded_inference_only") {
|
||||
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
||||
}
|
||||
if (mode === "needs_clarification") {
|
||||
return "Нужно уточнить контекст перед поиском в 1С.";
|
||||
}
|
||||
if (mode === "blocked") {
|
||||
return "Поиск в 1С заблокирован runtime-политикой до выполнения.";
|
||||
}
|
||||
return "Я проверил доступный контур, но подтвержденного факта для ответа не получил.";
|
||||
}
|
||||
|
||||
function nextStepFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
if (mode === "needs_clarification") {
|
||||
return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С.";
|
||||
}
|
||||
if (mode === "checked_sources_only" && pilot.query_limitations.length > 0) {
|
||||
return "Можно повторить проверку после восстановления MCP-доступа или сузить вопрос до конкретного контрагента/периода.";
|
||||
}
|
||||
if (mode === "blocked") {
|
||||
return "Нужно сначала снять policy/blocking причину, иначе данные 1С использовать нельзя.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
const claims = [
|
||||
"Do not claim legal registration age unless a legal registration source is confirmed.",
|
||||
"Do not present inferred activity duration as a formally confirmed legal fact.",
|
||||
"Do not expose MCP primitive names, query text, debug ids, or internal execution mechanics in the user answer.",
|
||||
"Do not claim rows were checked when mcp_execution_performed=false."
|
||||
];
|
||||
if (pilot.evidence.confirmed_facts.length === 0) {
|
||||
claims.push("Do not claim a confirmed business fact when confirmed_facts is empty.");
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
pilot: AssistantMcpDiscoveryPilotExecutionContract
|
||||
): AssistantMcpDiscoveryAnswerDraftContract {
|
||||
const mode = modeFor(pilot);
|
||||
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
||||
pushReason(reasonCodes, `answer_mode_${mode}`);
|
||||
if (pilot.evidence.unknown_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
|
||||
}
|
||||
if (pilot.evidence.inferred_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
|
||||
return {
|
||||
schema_version: ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
||||
answer_mode: mode,
|
||||
headline: headlineFor(mode),
|
||||
confirmed_lines: uniqueStrings(pilot.evidence.confirmed_facts),
|
||||
inference_lines: uniqueStrings(pilot.evidence.inferred_facts),
|
||||
unknown_lines: uniqueStrings(pilot.evidence.unknown_facts),
|
||||
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
|
||||
next_step_line: nextStepFor(mode, pilot),
|
||||
internal_mechanics_allowed: false,
|
||||
must_not_claim: buildMustNotClaim(pilot),
|
||||
reason_codes: uniqueStrings(reasonCodes)
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user