209 lines
8.2 KiB
JavaScript
209 lines
8.2 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.AssistantSessionLogger = void 0;
|
|
const path_1 = __importDefault(require("path"));
|
|
const config_1 = require("../config");
|
|
const files_1 = require("../utils/files");
|
|
function unique(values) {
|
|
return Array.from(new Set(values.filter((item) => typeof item === "string" && item.length > 0)));
|
|
}
|
|
function toObject(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
function toStringOrNull(value) {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
const trimmed = value.trim();
|
|
return trimmed ? trimmed : null;
|
|
}
|
|
function extractFragments(assistantItem) {
|
|
if (!assistantItem.debug || !Array.isArray(assistantItem.debug.fragments)) {
|
|
return [];
|
|
}
|
|
return assistantItem.debug.fragments
|
|
.map((item) => toObject(item))
|
|
.filter((item) => item !== null);
|
|
}
|
|
function extractNormalizedQuestion(userText, assistantItem) {
|
|
const normalized = toObject(assistantItem.debug?.normalized);
|
|
if (normalized) {
|
|
const fromUserMessageRaw = toStringOrNull(normalized.user_message_raw);
|
|
if (fromUserMessageRaw)
|
|
return fromUserMessageRaw;
|
|
const fromUserQuestionRaw = toStringOrNull(normalized.user_question_raw);
|
|
if (fromUserQuestionRaw)
|
|
return fromUserQuestionRaw;
|
|
const fromNormalizedQuestion = toStringOrNull(normalized.normalized_question);
|
|
if (fromNormalizedQuestion)
|
|
return fromNormalizedQuestion;
|
|
}
|
|
const fragments = extractFragments(assistantItem);
|
|
if (fragments.length > 0) {
|
|
const joined = fragments
|
|
.map((fragment) => toStringOrNull(fragment.normalized_fragment_text) ?? toStringOrNull(fragment.raw_fragment_text))
|
|
.filter((item) => Boolean(item))
|
|
.join(" | ");
|
|
if (joined) {
|
|
return joined;
|
|
}
|
|
}
|
|
return userText;
|
|
}
|
|
function buildRouteLookup(assistantItem) {
|
|
const output = new Map();
|
|
if (!assistantItem.debug || !Array.isArray(assistantItem.debug.routes)) {
|
|
return output;
|
|
}
|
|
for (const route of assistantItem.debug.routes) {
|
|
const routeObject = toObject(route);
|
|
if (!routeObject)
|
|
continue;
|
|
const fragmentId = toStringOrNull(routeObject.fragment_id);
|
|
if (!fragmentId)
|
|
continue;
|
|
output.set(fragmentId, routeObject);
|
|
}
|
|
return output;
|
|
}
|
|
function buildDecompositionLines(assistantItem) {
|
|
const fragments = extractFragments(assistantItem);
|
|
if (fragments.length === 0) {
|
|
return ["Фрагменты декомпозиции не выделены."];
|
|
}
|
|
const routeLookup = buildRouteLookup(assistantItem);
|
|
return fragments.map((fragment, index) => {
|
|
const fragmentId = toStringOrNull(fragment.fragment_id) ?? `F${index + 1}`;
|
|
const fragmentText = toStringOrNull(fragment.normalized_fragment_text) ??
|
|
toStringOrNull(fragment.raw_fragment_text) ??
|
|
"текст фрагмента отсутствует";
|
|
const executionReadiness = toStringOrNull(fragment.execution_readiness);
|
|
const routeStatus = toStringOrNull(fragment.route_status);
|
|
const routeObject = routeLookup.get(fragmentId);
|
|
const route = toStringOrNull(routeObject?.route);
|
|
const noRouteReason = toStringOrNull(fragment.no_route_reason) ?? toStringOrNull(routeObject?.no_route_reason);
|
|
const parts = [`${fragmentId}: ${fragmentText}`];
|
|
if (executionReadiness)
|
|
parts.push(`execution_readiness=${executionReadiness}`);
|
|
if (routeStatus)
|
|
parts.push(`route_status=${routeStatus}`);
|
|
if (route)
|
|
parts.push(`route=${route}`);
|
|
if (noRouteReason)
|
|
parts.push(`no_route_reason=${noRouteReason}`);
|
|
return parts.join("; ");
|
|
});
|
|
}
|
|
function toHumanBlock(input) {
|
|
const lines = [];
|
|
lines.push(`Вопрос: ${input.questionRaw}`);
|
|
lines.push(`Понято как: ${input.questionUnderstood}`);
|
|
lines.push("Декомпозиция:");
|
|
lines.push(...input.decomposition.map((item) => `- ${item}`));
|
|
lines.push(`Ответ: ${input.answer}`);
|
|
return lines.join("\n");
|
|
}
|
|
function buildTurns(items) {
|
|
const turns = [];
|
|
const pendingUsers = [];
|
|
for (const item of items) {
|
|
if (item.role === "user") {
|
|
pendingUsers.push(item);
|
|
continue;
|
|
}
|
|
const pairedUser = pendingUsers.shift();
|
|
if (!pairedUser) {
|
|
continue;
|
|
}
|
|
const questionRaw = pairedUser.text;
|
|
const questionUnderstood = extractNormalizedQuestion(questionRaw, item);
|
|
const decomposition = buildDecompositionLines(item);
|
|
const answer = item.text;
|
|
turns.push({
|
|
turn_id: `turn-${turns.length + 1}`,
|
|
started_at: pairedUser.created_at ?? null,
|
|
completed_at: item.created_at ?? null,
|
|
human_block: toHumanBlock({
|
|
questionRaw,
|
|
questionUnderstood,
|
|
decomposition,
|
|
answer
|
|
}),
|
|
human_readable: {
|
|
question_raw: questionRaw,
|
|
question_understood: questionUnderstood,
|
|
decomposition,
|
|
answer,
|
|
reply_type: item.reply_type
|
|
},
|
|
technical_json: {
|
|
trace_id: item.trace_id,
|
|
user_message: pairedUser,
|
|
assistant_message: item,
|
|
debug: item.debug
|
|
}
|
|
});
|
|
}
|
|
return turns;
|
|
}
|
|
class AssistantSessionLogger {
|
|
rootDir;
|
|
constructor(rootDir = config_1.ASSISTANT_SESSIONS_DIR) {
|
|
this.rootDir = rootDir;
|
|
}
|
|
persistSession(session) {
|
|
try {
|
|
(0, files_1.ensureDir)(this.rootDir);
|
|
const filePath = path_1.default.resolve(this.rootDir, `${session.session_id}.json`);
|
|
const startedAt = session.items[0]?.created_at ?? session.updated_at;
|
|
const userMessages = session.items.filter((item) => item.role === "user").length;
|
|
const assistantMessages = session.items.filter((item) => item.role === "assistant").length;
|
|
const assistantItems = session.items.filter((item) => item.role === "assistant");
|
|
const lastAssistant = assistantItems.length > 0 ? assistantItems[assistantItems.length - 1] : null;
|
|
const traceIds = unique(session.items.map((item) => item.trace_id));
|
|
const replyTypes = Array.from(new Set(session.items
|
|
.map((item) => item.reply_type)
|
|
.filter((item) => typeof item === "string" && item.length > 0)));
|
|
const turns = buildTurns(session.items);
|
|
const record = {
|
|
schema_version: "assistant_session_log_v1",
|
|
session_id: session.session_id,
|
|
started_at: startedAt,
|
|
updated_at: session.updated_at,
|
|
counters: {
|
|
total_messages: session.items.length,
|
|
user_messages: userMessages,
|
|
assistant_messages: assistantMessages
|
|
},
|
|
trace_ids: traceIds,
|
|
reply_types: replyTypes,
|
|
investigation_state: session.investigation_state,
|
|
address_navigation_state: session.address_navigation_state,
|
|
turns,
|
|
conversation: session.items,
|
|
last_assistant: {
|
|
message_id: lastAssistant?.message_id ?? null,
|
|
reply_type: lastAssistant?.reply_type ?? null,
|
|
trace_id: lastAssistant?.trace_id ?? null,
|
|
created_at: lastAssistant?.created_at ?? null
|
|
}
|
|
};
|
|
(0, files_1.writeJsonFile)(filePath, record);
|
|
}
|
|
catch (error) {
|
|
const code = error?.code;
|
|
if (code === "ENOSPC") {
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
exports.AssistantSessionLogger = AssistantSessionLogger;
|