АРЧ - UI и domain loop: синхронизировать локальную LLM-модель через shared config + КВИН 3
This commit is contained in:
@@ -188,6 +188,7 @@ export const AUTORUN_ANNOTATIONS_DIR = path.resolve(DATA_DIR, "autorun_annotatio
|
||||
export const AUTORUN_ANNOTATIONS_FILE = path.resolve(AUTORUN_ANNOTATIONS_DIR, "annotations.json");
|
||||
export const AUTORUN_GENERATOR_DIR = path.resolve(DATA_DIR, "autorun_generators");
|
||||
export const AUTORUN_GENERATOR_HISTORY_FILE = path.resolve(AUTORUN_GENERATOR_DIR, "history.json");
|
||||
export const SHARED_LLM_CONNECTION_FILE = path.resolve(DATA_DIR, "shared_llm_connection.json");
|
||||
|
||||
export const PROMPTS_DIR = path.resolve(MODULE_ROOT, "prompts");
|
||||
export const REPORTS_DIR = path.resolve(MODULE_ROOT, "reports");
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
import { SHARED_LLM_CONNECTION_FILE } from "../config";
|
||||
import { ok } from "../utils/http";
|
||||
import { ensureDir, readJsonFile, writeJsonFile } from "../utils/files";
|
||||
import path from "path";
|
||||
|
||||
type LlmProvider = "openai" | "local";
|
||||
|
||||
interface SharedLlmConnectionRecord {
|
||||
schema_version: "shared_llm_connection_v1";
|
||||
updated_at: string;
|
||||
connection: {
|
||||
llmProvider: LlmProvider;
|
||||
model: string;
|
||||
baseUrl: string;
|
||||
temperature: number;
|
||||
maxOutputTokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizeString(value: unknown, fallback: string): string {
|
||||
if (typeof value !== "string") {
|
||||
return fallback;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : fallback;
|
||||
}
|
||||
|
||||
function sanitizeNumber(value: unknown, fallback: number): number {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
if (Number.isFinite(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function buildFallbackRecord(): SharedLlmConnectionRecord {
|
||||
return {
|
||||
schema_version: "shared_llm_connection_v1",
|
||||
updated_at: "",
|
||||
connection: {
|
||||
llmProvider: "local",
|
||||
model: "qwen2.5-14b-instruct-1m",
|
||||
baseUrl: "http://127.0.0.1:1234/v1",
|
||||
temperature: 0,
|
||||
maxOutputTokens: 900
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function loadSharedRecord(): SharedLlmConnectionRecord | null {
|
||||
const fallback = buildFallbackRecord();
|
||||
const parsed = readJsonFile<SharedLlmConnectionRecord | null>(SHARED_LLM_CONNECTION_FILE, null);
|
||||
if (!parsed || typeof parsed !== "object" || !("connection" in parsed) || !parsed.connection) {
|
||||
return null;
|
||||
}
|
||||
const connection = parsed.connection;
|
||||
return {
|
||||
schema_version: "shared_llm_connection_v1",
|
||||
updated_at: sanitizeString(parsed.updated_at, ""),
|
||||
connection: {
|
||||
llmProvider: connection.llmProvider === "local" ? "local" : "openai",
|
||||
model: sanitizeString(connection.model, fallback.connection.model),
|
||||
baseUrl: sanitizeString(connection.baseUrl, fallback.connection.baseUrl),
|
||||
temperature: sanitizeNumber(connection.temperature, fallback.connection.temperature),
|
||||
maxOutputTokens: Math.max(1, Math.trunc(sanitizeNumber(connection.maxOutputTokens, fallback.connection.maxOutputTokens)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSharedLlmConfigRouter(): Router {
|
||||
const router = Router();
|
||||
|
||||
router.get("/api/llm/shared-connection", (_req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const record = loadSharedRecord();
|
||||
ok(res, {
|
||||
ok: true,
|
||||
connection: record?.connection ?? null,
|
||||
updated_at: record?.updated_at ?? null,
|
||||
exists: Boolean(record)
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/api/llm/shared-connection", (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const body = (req.body ?? {}) as Record<string, unknown>;
|
||||
const fallback = buildFallbackRecord();
|
||||
const record: SharedLlmConnectionRecord = {
|
||||
schema_version: "shared_llm_connection_v1",
|
||||
updated_at: new Date().toISOString(),
|
||||
connection: {
|
||||
llmProvider: body.llmProvider === "local" ? "local" : "openai",
|
||||
model: sanitizeString(body.model, fallback.connection.model),
|
||||
baseUrl: sanitizeString(body.baseUrl, fallback.connection.baseUrl),
|
||||
temperature: sanitizeNumber(body.temperature, fallback.connection.temperature),
|
||||
maxOutputTokens: Math.max(
|
||||
1,
|
||||
Math.trunc(sanitizeNumber(body.maxOutputTokens, fallback.connection.maxOutputTokens))
|
||||
)
|
||||
}
|
||||
};
|
||||
ensureDir(path.dirname(SHARED_LLM_CONNECTION_FILE));
|
||||
writeJsonFile(SHARED_LLM_CONNECTION_FILE, record);
|
||||
ok(res, {
|
||||
ok: true,
|
||||
connection: record.connection,
|
||||
updated_at: record.updated_at
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { buildEvalRouter } from "./routes/eval";
|
||||
import { buildHistoryRouter } from "./routes/history";
|
||||
import { buildNormalizeRouter } from "./routes/normalize";
|
||||
import { buildPresetsRouter } from "./routes/presets";
|
||||
import { buildSharedLlmConfigRouter } from "./routes/sharedLlmConfig";
|
||||
import { buildTestConnectionRouter } from "./routes/testConnection";
|
||||
import { InMemoryRuntimeAdapter } from "./runtime/inMemoryRuntimeAdapter";
|
||||
import { AssistantService } from "./services/assistantService";
|
||||
@@ -71,6 +72,7 @@ export function createApp(): express.Express {
|
||||
});
|
||||
|
||||
app.use(buildTestConnectionRouter(openaiClient));
|
||||
app.use(buildSharedLlmConfigRouter());
|
||||
app.use(buildNormalizeRouter(services));
|
||||
app.use(buildEvalRouter(services));
|
||||
app.use(buildAssistantRouter(services));
|
||||
|
||||
@@ -1612,7 +1612,7 @@ function hasSelectedObjectInventoryCue(text: string): boolean {
|
||||
function hasSelectedObjectInventoryProvenanceSignal(text: string): boolean {
|
||||
return (
|
||||
hasSelectedObjectInventoryCue(text) &&
|
||||
/(?:кто\s+(?:(?:это|этот\s+товар|эту\s+позицию)\s+)?(?:нам\s+)?поставил|кто\s+(?:нам\s+)?поставил\s+(?:это|этот\s+товар|эту\s+позицию)|от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+куплено|supplier|vendor|поставщик)/iu.test(
|
||||
/(?:кто\s+(?:(?:это|этот\s+товар|эту\s+позицию)\s+)?(?:нам\s+)?поставил|кто\s+(?:нам\s+)?поставил\s+(?:это|этот\s+товар|эту\s+позицию)|от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+куплено|supplier|vendor|поставщик)/iu.test(
|
||||
text
|
||||
)
|
||||
);
|
||||
@@ -1630,7 +1630,7 @@ function hasSelectedObjectInventoryPurchaseDocumentsSignal(text: string): boolea
|
||||
function hasInventoryProvenanceSignalV2(text: string): boolean {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue =
|
||||
/(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставщик|supplier|vendor)/iu.test(
|
||||
/(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставщик|supplier|vendor)/iu.test(
|
||||
text
|
||||
);
|
||||
const hasPurchaseCue =
|
||||
|
||||
@@ -278,7 +278,7 @@ function hasSelectedObjectInventoryFollowupSignal(text: string): boolean {
|
||||
if (!/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции)/iu.test(text)) {
|
||||
return false;
|
||||
}
|
||||
return /(?:у\s+кого\s+купили|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:поставил|продал)|кому\s+(?:продали|реализовали)|когда\s+(?:примерно\s+)?купили|по\s+каким\s+документам\s+.*купили)/iu.test(
|
||||
return /(?:у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:поставил|продал)|кому\s+(?:продали|реализовали)|когда\s+(?:примерно\s+)?купили|по\s+каким\s+документам\s+.*купили)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ function hasSameDateHint(text: string): boolean {
|
||||
}
|
||||
|
||||
function hasExplicitPeriodLiteral(text: string): boolean {
|
||||
return /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(String(text ?? ""));
|
||||
return /(?:^|[^\d*×xх])((?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?)(?=$|[^\d*×xх])/iu.test(String(text ?? ""));
|
||||
}
|
||||
|
||||
function hasExplicitCurrentDateHint(text: string): boolean {
|
||||
@@ -323,7 +323,7 @@ function hasSelectedObjectInventorySignal(text: string): boolean {
|
||||
}
|
||||
|
||||
function hasInventorySupplierFollowupCue(text: string): boolean {
|
||||
return /(?:кто\s+(?:(?:это|этот\s+товар|эту\s+позицию)\s+)?(?:нам\s+)?поставил|кто\s+(?:нам\s+)?поставил\s+(?:это|этот\s+товар|эту\s+позицию)|от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+куплено|supplier|vendor|поставщик)/iu.test(
|
||||
return /(?:кто\s+(?:(?:это|этот\s+товар|эту\s+позицию)\s+)?(?:нам\s+)?поставил|кто\s+(?:нам\s+)?поставил\s+(?:это|этот\s+товар|эту\s+позицию)|от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+куплено|supplier|vendor|поставщик)/iu.test(
|
||||
String(text ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user