index.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import dotenv from "dotenv";
  2. import fs from "node:fs";
  3. dotenv.config();
  4. function resolveDbSsl() {
  5. const caPath = process.env.DB_SSL_CA;
  6. const rejectUnauthorizedEnv = process.env.DB_SSL_REJECT_UNAUTHORIZED;
  7. if (!caPath && rejectUnauthorizedEnv === undefined) return undefined;
  8. return {
  9. rejectUnauthorized: rejectUnauthorizedEnv !== "false",
  10. ...(caPath ? { ca: fs.readFileSync(caPath, "utf8") } : {})
  11. };
  12. }
  13. export const config = {
  14. port: Number(process.env.PORT ?? 3001),
  15. corsOrigin: process.env.CORS_ORIGIN ?? "http://localhost:5173",
  16. apiKey: process.env.API_KEY ?? "",
  17. auth: {
  18. mode: process.env.AUTH_MODE ?? "none"
  19. },
  20. http: {
  21. defaultTimeoutMs: Number(process.env.HTTP_DEFAULT_TIMEOUT_MS ?? 15_000)
  22. },
  23. db: {
  24. host: process.env.DB_HOST,
  25. user: process.env.DB_USER,
  26. port: process.env.DB_PORT ? Number(process.env.DB_PORT) : undefined,
  27. password: process.env.DB_PASS,
  28. schema: process.env.DB_SCHEMA,
  29. charset: "utf8mb4",
  30. ssl: resolveDbSsl()
  31. },
  32. jwt: {
  33. secret: process.env.JWT_SECRET ?? "",
  34. issuer: process.env.JWT_ISSUER ?? "oraculo-api",
  35. accessTtlSeconds: Number(process.env.JWT_ACCESS_TTL_SECONDS ?? 900),
  36. refreshTtlSeconds: Number(process.env.JWT_REFRESH_TTL_SECONDS ?? 60 * 60 * 24 * 30),
  37. refreshTtlShortSeconds: Number(process.env.JWT_REFRESH_TTL_SHORT_SECONDS ?? 60 * 60 * 24)
  38. },
  39. qdrant: {
  40. url: process.env.QDRANT_URL ?? "http://localhost:6333",
  41. apiKey: process.env.QDRANT_API_KEY ?? "",
  42. collection: process.env.QDRANT_COLLECTION ?? "oraculo_docs"
  43. },
  44. ollama: {
  45. url: process.env.OLLAMA_URL ?? "http://localhost:11434",
  46. embeddingsModel: process.env.OLLAMA_EMBEDDINGS_MODEL ?? "nomic-embed-text",
  47. embedQueryPrefix: process.env.OLLAMA_EMBED_QUERY_PREFIX ?? "",
  48. embedPassagePrefix: process.env.OLLAMA_EMBED_PASSAGE_PREFIX ?? "",
  49. chatModel: process.env.OLLAMA_CHAT_MODEL ?? "llama3.1",
  50. visionModel: process.env.OLLAMA_VISION_MODEL ?? "llava:latest",
  51. timeoutMs: Number(process.env.OLLAMA_TIMEOUT_MS ?? 180_000),
  52. streamIdleTimeoutMs: Number(process.env.OLLAMA_STREAM_IDLE_TIMEOUT_MS ?? 60_000)
  53. },
  54. rag: {
  55. topK: Number(process.env.RAG_TOP_K ?? 8),
  56. minScore: Number(process.env.RAG_MIN_SCORE ?? 0.40),
  57. chunkSize: Number(process.env.RAG_CHUNK_SIZE ?? 900),
  58. chunkOverlap: Number(process.env.RAG_CHUNK_OVERLAP ?? 150),
  59. queryRewrite: process.env.RAG_QUERY_REWRITE === "true",
  60. historyLimit: Number(process.env.RAG_HISTORY_LIMIT ?? 6),
  61. rerank: process.env.RAG_RERANK === "true",
  62. rerankTopN: Number(process.env.RAG_RERANK_TOP_N ?? process.env.RAG_TOP_K ?? 8),
  63. hyde: process.env.RAG_HYDE === "true"
  64. },
  65. llm: {
  66. temperature: Number(process.env.LLM_TEMPERATURE ?? 0.2),
  67. topP: Number(process.env.LLM_TOP_P ?? 0.9),
  68. numPredict: Number(process.env.LLM_NUM_PREDICT ?? 4096),
  69. repeatPenalty: Number(process.env.LLM_REPEAT_PENALTY ?? 1.1),
  70. numCtx: Number(process.env.LLM_NUM_CTX ?? 8192),
  71. atendimentosNumCtx: Number(process.env.LLM_ATENDIMENTOS_NUM_CTX ?? 32768),
  72. auxNumPredict: Number(process.env.LLM_AUX_NUM_PREDICT ?? 400)
  73. },
  74. rateLimit: {
  75. perMinute: Number(process.env.RATE_LIMIT_PER_MINUTE ?? 0)
  76. },
  77. ifbot: {
  78. baseUrl: process.env.IFBOT_BASE_URL ?? "https://star.ifbot.com.br/api",
  79. mediaBaseUrl: (process.env.IFBOT_MEDIA_BASE_URL ?? (process.env.IFBOT_BASE_URL ?? "https://star.ifbot.com.br/api").replace(/\/api\/?$/, "")).replace(/\/+$/, ""),
  80. token: process.env.IFBOT_TOKEN ?? "",
  81. authHeader: process.env.IFBOT_AUTH_HEADER ?? "Authorization",
  82. syncIntervalMinutes: Number(process.env.ATENDIMENTOS_SYNC_INTERVAL_MINUTES ?? 30)
  83. },
  84. avaliacao: {
  85. model: process.env.OLLAMA_JUDGE_MODEL ?? "",
  86. intervalMinutes: Number(process.env.ATENDIMENTOS_AVALIACAO_INTERVAL_MINUTES ?? 60),
  87. batchSize: Number(process.env.ATENDIMENTOS_AVALIACAO_BATCH ?? 10),
  88. maxChars: Number(process.env.ATENDIMENTOS_AVALIACAO_MAX_CHARS ?? 12000)
  89. },
  90. atendimentosRag: {
  91. collection: process.env.ATENDIMENTOS_RAG_COLLECTION ?? "atendimentos_rag",
  92. intervalMinutes: Number(process.env.ATENDIMENTOS_RAG_INTERVAL_MINUTES ?? 60),
  93. batchSize: Number(process.env.ATENDIMENTOS_RAG_BATCH ?? 10),
  94. chunkSize: Number(process.env.ATENDIMENTOS_RAG_CHUNK_SIZE ?? 1800),
  95. chunkOverlap: Number(process.env.ATENDIMENTOS_RAG_CHUNK_OVERLAP ?? 200),
  96. topK: Number(process.env.ATENDIMENTOS_RAG_TOP_K ?? 20),
  97. minScore: Number(process.env.ATENDIMENTOS_RAG_MIN_SCORE ?? 0.5),
  98. searchEnabled: process.env.ATENDIMENTOS_RAG_SEARCH_ENABLED === "true",
  99. hyde: process.env.ATENDIMENTOS_RAG_HYDE === "true",
  100. rerank: process.env.ATENDIMENTOS_RAG_RERANK !== "false",
  101. rerankTopN: Number(process.env.ATENDIMENTOS_RAG_RERANK_TOP_N ?? 10)
  102. },
  103. atendenteSugestao: {
  104. model: process.env.OLLAMA_ATENDENTE_MODEL ?? "star-atendente",
  105. historyLimit: Number(process.env.ATENDENTE_SUGESTAO_HISTORY_LIMIT ?? 20),
  106. maxChars: Number(process.env.ATENDENTE_SUGESTAO_MAX_CHARS ?? 8000),
  107. ragEnabled: process.env.ATENDENTE_SUGESTAO_RAG_ENABLED !== "false",
  108. ragTopK: Number(process.env.ATENDENTE_SUGESTAO_RAG_TOP_K ?? 3),
  109. ragMinScore: Number(process.env.ATENDENTE_SUGESTAO_RAG_MIN_SCORE ?? process.env.ATENDIMENTOS_RAG_MIN_SCORE ?? 0.5),
  110. ragMaxCharsPerHit: Number(process.env.ATENDENTE_SUGESTAO_RAG_MAX_CHARS_POR_HIT ?? 600)
  111. },
  112. whatsapp: {
  113. apiVersion: process.env.WHATSAPP_API_VERSION || "v21.0",
  114. phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID ?? "",
  115. token: process.env.WHATSAPP_TOKEN ?? "",
  116. verifyToken: process.env.WHATSAPP_VERIFY_TOKEN ?? "",
  117. appSecret: process.env.WHATSAPP_APP_SECRET ?? "",
  118. businessAccountId: process.env.WHATSAPP_BUSINESS_ACCOUNT_ID ?? ""
  119. },
  120. ixcIntegradorSeed: {
  121. baseUrl: process.env.IXC_INTEGRADOR_BASE_URL ?? "",
  122. login: process.env.IXC_INTEGRADOR_LOGIN ?? "",
  123. senha: process.env.IXC_INTEGRADOR_SENHA ?? ""
  124. },
  125. whisper: {
  126. enabled: process.env.WHISPER_TRANSCRICAO_ENABLED === "true",
  127. binaryPath: process.env.WHISPER_BINARY_PATH ?? "whisper-cli",
  128. ffmpegPath: process.env.WHISPER_FFMPEG_PATH ?? "ffmpeg",
  129. modelPath: process.env.WHISPER_MODEL_PATH ?? "whisper_models/ggml-small.bin",
  130. vadModelPath: process.env.WHISPER_VAD_MODEL_PATH ?? "whisper_models/ggml-silero-vad.bin",
  131. timeoutMs: Number(process.env.WHISPER_TIMEOUT_MS ?? 60_000)
  132. }
  133. };
  134. function validateConfig(cfg) {
  135. const dbRequired = { host: "DB_HOST", user: "DB_USER", password: "DB_PASS", schema: "DB_SCHEMA" };
  136. for (const [key, envName] of Object.entries(dbRequired)) {
  137. if (!cfg.db[key]) throw new Error(`[config] ${envName} é obrigatório`);
  138. }
  139. if (cfg.auth.mode === "jwt" && !cfg.jwt.secret) {
  140. throw new Error("[config] JWT_SECRET não pode ser vazio quando AUTH_MODE=jwt");
  141. }
  142. if (cfg.rateLimit.perMinute === 0) {
  143. console.warn("[config] RATE_LIMIT_PER_MINUTE=0 — rate limiting desabilitado");
  144. }
  145. if (!cfg.whatsapp.token || !cfg.whatsapp.phoneNumberId) {
  146. console.warn("[config] WHATSAPP_TOKEN/WHATSAPP_PHONE_NUMBER_ID não configurados — envio via WhatsApp ficará desabilitado (whatsapp_nao_conectado)");
  147. }
  148. if (!cfg.whatsapp.verifyToken) {
  149. console.warn("[config] WHATSAPP_VERIFY_TOKEN não configurado — a verificação do webhook (GET /webhooks/whatsapp) sempre retornará 403");
  150. }
  151. if (!cfg.whatsapp.appSecret) {
  152. console.warn("[config] WHATSAPP_APP_SECRET vazio — assinatura X-Hub-Signature-256 do webhook NÃO será validada");
  153. }
  154. }
  155. validateConfig(config);