/
OskarSR
/
Factor
Обзор
Документация
Войти
/
OskarSR
/
Factor
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
factor/server.js
129 строк
5 KB
Oskar Stud
v2
13 мар 2026, 18:41
13 мар 2026, 18:41
e114d8d
Код
Авторство
О чём код?
import "./loadEnv.js"; import express from "express"; import rateLimit from "express-rate-limit"; import { HumanMessage } from "@langchain/core/messages"; import { createGraph } from "./graph/index.js"; import { validateFact } from "./lib/security.js"; const app = express(); // Лимит размера тела (защита от больших payload) app.use(express.json({ limit: "32kb" })); // Базовые заголовки безопасности app.use((req, res, next) => { res.setHeader("X-Content-Type-Options", "nosniff"); res.setHeader("X-Frame-Options", "DENY"); res.setHeader("X-XSS-Protection", "1; mode=block"); next(); }); app.use(express.static("public")); // Ограничение частоты запросов к API проверки фактов const checkLimiter = rateLimit({ windowMs: 60 * 1000, // 1 минута max: 15, // не более 15 запросов в минуту с одного IP message: { error: "Слишком много запросов. Попробуйте позже." }, standardHeaders: true, legacyHeaders: false, }); const graph = createGraph(); const ROLE_BY_AGENT = { ConfirmAgent: "подтверждение", ConfirmGeminiAgent: "подтверждение", RefuteAgent: "опровержение", RefuteDeepSeekAgent: "опровержение", }; function sendSSE(res, event, data) { res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); } /** Сообщение об ошибке без утечки деталей (стек, ввод пользователя) */ function safeErrorMessage(err) { const msg = (err?.message ?? String(err)).toLowerCase(); if (msg.includes("credentials") || msg.includes("api") || msg.includes("401") || msg.includes("429")) { return "Ошибка сервиса. Проверьте настройки (.env) или попробуйте позже."; } if (msg.includes("model") || msg.includes("404") || msg.includes("not found") || msg.includes("invalid") || msg.includes("не найден")) { return "Ошибка модели или API. Проверьте имена моделей в .env (BOTHUB_GEMINI_MODEL, BOTHUB_DEEPSEEK_MODEL). Подробности — в консоли сервера."; } return "Произошла ошибка при проверке. Подробности смотрите в консоли сервера (терминал)."; } app.post("/api/check", checkLimiter, async (req, res) => { const validated = validateFact(req.body?.fact); if (!validated.ok) { return res.status(400).json({ error: validated.error }); } const fact = validated.value; res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.flushHeaders(); try { const stream = await graph.stream( { messages: [new HumanMessage(fact)], next: null, completedReflectors: [], summarizerDone: false, summaryTable: "", }, { streamMode: "updates" } ); const REFLECTOR_AGENTS = ["ConfirmAgent", "RefuteAgent", "ConfirmGeminiAgent", "RefuteDeepSeekAgent"]; outer: for await (const chunk of stream) { for (const [nodeName, update] of Object.entries(chunk)) { if (nodeName === "supervisor" && update?.next) { if (update.next === "__end__") { sendSSE(res, "done", {}); break outer; } if (update.next === "__parallel_reflectors__") { for (const agent of REFLECTOR_AGENTS) { sendSSE(res, "agent_start", { agent }); } continue; } sendSSE(res, "agent_start", { agent: update.next }); continue; } if (nodeName === "ConfirmAgent" || nodeName === "ConfirmGeminiAgent" || nodeName === "RefuteAgent" || nodeName === "RefuteDeepSeekAgent") { const messages = update?.messages ?? []; const lastAi = messages[messages.length - 1]; const content = lastAi?.content ?? ""; sendSSE(res, "reflection", { agent: nodeName, role: ROLE_BY_AGENT[nodeName], content: typeof content === "string" ? content : String(content), }); continue; } if (nodeName === "Summarizer" && update?.summaryRows != null) { sendSSE(res, "summary", { summaryRows: update.summaryRows }); } } } sendSSE(res, "done", {}); } catch (err) { console.error("Graph error:", err?.message ?? err); if (err?.cause) console.error("Cause:", err.cause); if (err?.response?.data) console.error("API response:", err.response.data); sendSSE(res, "error", { error: safeErrorMessage(err) }); } finally { res.end(); } }); const PORT = process.env.PORT ?? 4003; app.listen(PORT, () => { console.log(`Factor running at http://localhost:${PORT}`); });