Files
ln-bi/src/server/routes/asset-assistant/privacy.ts
T

99 lines
2.9 KiB
TypeScript

import {
DIMENSIONS,
type Dimension,
type QueryPlan,
} from "../../../shared/assets/assistant.js";
import type { Vehicle } from "../../types.js";
import { toRow } from "./model.js";
// Kept as an input/context adapter for existing callers. Natural language is no longer
// reduced to a closed vocabulary: the conversation planner needs the actual question.
export type SafeInput = {
message: string;
tokens: string[];
entities: { token: string; dimension: Dimension; value: string }[];
catalog: Partial<Record<Dimension, string[]>>;
demand: string;
unknown: boolean;
sensitive: boolean;
};
export function projectQuestion(
message: string,
vehicles: Vehicle[],
previous?: QueryPlan,
): SafeInput {
const catalog: SafeInput["catalog"] = {};
for (const dimension of DIMENSIONS) {
const values = [
...new Set(vehicles.map((v) => toRow(v)[dimension]).filter(Boolean)),
];
// Prefer mentioned/current values if a large catalog must be truncated.
values.sort(
(a, b) =>
Number(
message.includes(b) || previous?.filters[dimension]?.includes(b),
) -
Number(
message.includes(a) || previous?.filters[dimension]?.includes(a),
),
);
catalog[dimension] = values.slice(0, 150);
}
const entities: SafeInput["entities"] = [];
for (const dimension of DIMENSIONS)
for (const value of catalog[dimension] || []) {
if (
message.includes(value) ||
(dimension === "city" &&
value.endsWith("市") &&
message.includes(value.slice(0, -1))) ||
previous?.filters[dimension]?.includes(value)
) {
entities.push({ token: `E${entities.length + 1}`, dimension, value });
}
}
const mentioned = entities.filter(
(e) =>
message.includes(e.value) ||
(e.dimension === "city" &&
e.value.endsWith("市") &&
message.includes(e.value.slice(0, -1))),
);
return {
message,
tokens: [
message,
...mentioned.map((e) => e.token),
...(message.match(/20\d\d-\d{2}-\d{2}/g) || []),
],
entities,
catalog,
// Demand feedback records the request instead of silently dropping unknown words.
demand: message.replace(/sk-[a-z0-9]{8,}/gi, "[凭据已隐藏]").slice(0, 500),
unknown: false,
sensitive: false,
};
}
export function externalContext(safe: SafeInput, previous?: QueryPlan) {
return {
message: safe.message,
catalog: safe.catalog,
previous: previous || null,
};
}
export function restoreEntities(plan: QueryPlan, safe: SafeInput): QueryPlan {
return {
...plan,
filters: Object.fromEntries(
Object.entries(plan.filters).map(([key, values]) => [
key,
values.map(
(value) =>
safe.entities.find((e) => e.token === value && e.dimension === key)
?.value || value,
),
]),
),
};
}