-
Notifications
You must be signed in to change notification settings - Fork 0
feat: per-purpose inference config (LLM / embedder / graph) #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c2ceb90
c6b3832
987d3c9
bd45c22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,19 +23,41 @@ const BUILD_COMMIT = process.env.GIT_SHA || process.env.BUILD_COMMIT || "unknown | |
| const BUILD_IMAGE_DIGEST = process.env.IMAGE_DIGEST || "unknown"; | ||
| const BUILD_TIME = process.env.BUILD_TIME || "unknown"; | ||
|
|
||
| // --- Shared defaults (used as fallback for per-purpose config) --- | ||
| const OPENAI_BASE_URL = process.env.OPENAI_BASE_URL; // e.g. http://foxmemory-infer:8081/v1 | ||
| const OPENAI_API_KEY = process.env.OPENAI_API_KEY || "local-infer-no-key"; | ||
| const HAS_OPENAI_API_KEY = Boolean(process.env.OPENAI_API_KEY); | ||
|
|
||
| // --- Per-purpose inference config --- | ||
| // Each purpose (main LLM, embedder, graph LLM) can have its own provider, base URL, | ||
| // API key, and model. Falls back to the shared OPENAI_* values when not set. | ||
| // This allows e.g. local inference for the main LLM with cloud for embeddings. | ||
|
|
||
| // Main LLM (fact extraction, memory decisions) | ||
| const LLM_BASE_URL = process.env.MEM0_LLM_BASE_URL || OPENAI_BASE_URL; | ||
| const LLM_API_KEY = process.env.MEM0_LLM_API_KEY || OPENAI_API_KEY; | ||
| const LLM_MODEL = process.env.MEM0_LLM_MODEL || "gpt-4.1-nano"; | ||
|
|
||
| // Embedder | ||
| const EMBED_BASE_URL = process.env.MEM0_EMBED_BASE_URL || OPENAI_BASE_URL; | ||
| const EMBED_API_KEY = process.env.MEM0_EMBED_API_KEY || OPENAI_API_KEY; | ||
| const EMBED_MODEL = process.env.MEM0_EMBED_MODEL || "text-embedding-3-small"; | ||
|
|
||
| // Graph memory (Neo4j). Enabled when NEO4J_URL + NEO4J_PASSWORD are set. | ||
| // MEM0_GRAPH_LLM_MODEL lets you use a separate, more capable model for entity/relation extraction. | ||
| // MEM0_GRAPH_LLM_* lets you use a separate, more capable model/endpoint for entity/relation extraction. | ||
| // Recommended hosted: gpt-4o-mini. Recommended local: Qwen2.5-14B-Instruct or Mistral-Small-3.1-24B. | ||
| const NEO4J_URL = process.env.NEO4J_URL || null; | ||
| const NEO4J_USERNAME = process.env.NEO4J_USERNAME || "neo4j"; | ||
| const NEO4J_PASSWORD = process.env.NEO4J_PASSWORD || null; | ||
| const GRAPH_LLM_BASE_URL = process.env.MEM0_GRAPH_LLM_BASE_URL || OPENAI_BASE_URL; | ||
| const GRAPH_LLM_API_KEY = process.env.MEM0_GRAPH_LLM_API_KEY || OPENAI_API_KEY; | ||
| const GRAPH_LLM_MODEL = process.env.MEM0_GRAPH_LLM_MODEL || LLM_MODEL; | ||
| const GRAPH_EXTRACTION_STRATEGY = (() => { | ||
| const raw = process.env.MEM0_GRAPH_EXTRACTION_STRATEGY; | ||
| if (!raw) return undefined; | ||
| if (raw === "tool_calling" || raw === "json_prompting") return raw; | ||
| console.warn(`[config] invalid MEM0_GRAPH_EXTRACTION_STRATEGY "${raw}" — must be "tool_calling" or "json_prompting", falling back to "tool_calling"`); | ||
| return "tool_calling" as const; | ||
| })(); | ||
|
|
||
| // Mutable effective models — start from env vars, overridden by DB on startup. | ||
| // Use these everywhere instead of the const env vars so hot-reload works. | ||
|
|
@@ -61,8 +83,10 @@ function sanitizeBaseUrl(url?: string) { | |
| } | ||
| } | ||
|
|
||
| const AUTH_MODE = HAS_OPENAI_API_KEY ? "api_key" : "local-default"; | ||
| const OPENAI_BASE_URL_SANITIZED = sanitizeBaseUrl(OPENAI_BASE_URL); | ||
| const HAS_LLM_API_KEY = Boolean(process.env.MEM0_LLM_API_KEY || process.env.OPENAI_API_KEY); | ||
| const HAS_EMBED_API_KEY = Boolean(process.env.MEM0_EMBED_API_KEY || process.env.OPENAI_API_KEY); | ||
| const HAS_GRAPH_LLM_API_KEY = Boolean(process.env.MEM0_GRAPH_LLM_API_KEY || process.env.OPENAI_API_KEY); | ||
| const AUTH_MODE = (HAS_LLM_API_KEY || HAS_EMBED_API_KEY || (GRAPH_ENABLED && HAS_GRAPH_LLM_API_KEY)) ? "api_key" : "local-default"; | ||
|
|
||
| // mem0 default prompts (copied from @foxlight-foundation/mem0ai prompts/index.ts). | ||
| // Used to show the effective prompt even when no custom prompt is set. | ||
|
|
@@ -217,17 +241,17 @@ function createMemory(customPrompt?: string | null, customUpdatePrompt?: string | |
| llm: { | ||
| provider: "openai", | ||
| config: { | ||
| apiKey: OPENAI_API_KEY, | ||
| apiKey: LLM_API_KEY, | ||
| model: effectiveLlmModel, | ||
| ...(OPENAI_BASE_URL ? { baseURL: OPENAI_BASE_URL } : {}) | ||
| ...(LLM_BASE_URL ? { baseURL: LLM_BASE_URL } : {}) | ||
| } | ||
| }, | ||
| embedder: { | ||
| provider: "openai", | ||
| config: { | ||
| apiKey: OPENAI_API_KEY, | ||
| apiKey: EMBED_API_KEY, | ||
| model: EMBED_MODEL, | ||
| ...(OPENAI_BASE_URL ? { baseURL: OPENAI_BASE_URL } : {}) | ||
| ...(EMBED_BASE_URL ? { baseURL: EMBED_BASE_URL } : {}) | ||
| } | ||
| }, | ||
| ...(process.env.QDRANT_HOST | ||
|
|
@@ -265,11 +289,12 @@ function createMemory(customPrompt?: string | null, customUpdatePrompt?: string | |
| llm: { | ||
| provider: "openai", | ||
| config: { | ||
| apiKey: OPENAI_API_KEY, | ||
| apiKey: GRAPH_LLM_API_KEY, | ||
| model: effectiveGraphLlmModel, | ||
| ...(OPENAI_BASE_URL ? { baseURL: OPENAI_BASE_URL } : {}), | ||
| ...(GRAPH_LLM_BASE_URL ? { baseURL: GRAPH_LLM_BASE_URL } : {}), | ||
| }, | ||
| }, | ||
| ...(GRAPH_EXTRACTION_STRATEGY ? { extractionStrategy: GRAPH_EXTRACTION_STRATEGY } : {}), | ||
| }, | ||
| } | ||
| : {}), | ||
|
|
@@ -497,8 +522,12 @@ app.get("/health", (_req, res) => { | |
| embedModel: EMBED_MODEL, | ||
| diagnostics: { | ||
| authMode: AUTH_MODE, | ||
| openaiApiKeyConfigured: HAS_OPENAI_API_KEY, | ||
| openaiBaseUrl: OPENAI_BASE_URL_SANITIZED, | ||
| // Legacy fields (kept for backward compat with existing monitors) | ||
| openaiApiKeyConfigured: HAS_LLM_API_KEY, | ||
| openaiBaseUrl: sanitizeBaseUrl(LLM_BASE_URL), | ||
| // Per-purpose fields | ||
| apiKeyConfigured: { llm: HAS_LLM_API_KEY, embed: HAS_EMBED_API_KEY, graphLlm: HAS_GRAPH_LLM_API_KEY }, | ||
| baseUrl: { llm: sanitizeBaseUrl(LLM_BASE_URL), embed: sanitizeBaseUrl(EMBED_BASE_URL), graphLlm: sanitizeBaseUrl(GRAPH_LLM_BASE_URL) }, | ||
|
Comment on lines
+529
to
+530
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This change swaps Useful? React with 👍 / 👎. |
||
| graphEnabled: GRAPH_ENABLED, | ||
| neo4jUrl: NEO4J_URL, | ||
| graphLlmModel: GRAPH_ENABLED ? effectiveGraphLlmModel : null, | ||
|
|
@@ -1536,8 +1565,12 @@ app.get("/v2/health", async (_req, res) => { | |
| embedModel: EMBED_MODEL, | ||
| diagnostics: { | ||
| authMode: AUTH_MODE, | ||
| openaiApiKeyConfigured: HAS_OPENAI_API_KEY, | ||
| openaiBaseUrl: OPENAI_BASE_URL_SANITIZED, | ||
| // Legacy fields (kept for backward compat with existing monitors) | ||
| openaiApiKeyConfigured: HAS_LLM_API_KEY, | ||
| openaiBaseUrl: sanitizeBaseUrl(LLM_BASE_URL), | ||
| // Per-purpose fields | ||
| apiKeyConfigured: { llm: HAS_LLM_API_KEY, embed: HAS_EMBED_API_KEY, graphLlm: HAS_GRAPH_LLM_API_KEY }, | ||
| baseUrl: { llm: sanitizeBaseUrl(LLM_BASE_URL), embed: sanitizeBaseUrl(EMBED_BASE_URL), graphLlm: sanitizeBaseUrl(GRAPH_LLM_BASE_URL) }, | ||
| graphEnabled: GRAPH_ENABLED, | ||
| neo4jUrl: NEO4J_URL, | ||
| graphLlmModel: GRAPH_ENABLED ? effectiveGraphLlmModel : null, | ||
|
|
@@ -2897,8 +2930,10 @@ const V2_OPENAPI_SPEC = { | |
| type: "object", | ||
| properties: { | ||
| authMode: { type: "string" }, | ||
| openaiApiKeyConfigured: { type: "boolean" }, | ||
| openaiBaseUrl: { type: "string", nullable: true }, | ||
| openaiApiKeyConfigured: { type: "boolean", description: "Legacy alias — reflects main LLM API key status." }, | ||
| openaiBaseUrl: { type: "string", nullable: true, description: "Legacy alias — reflects main LLM base URL." }, | ||
| apiKeyConfigured: { type: "object", properties: { llm: { type: "boolean" }, embed: { type: "boolean" }, graphLlm: { type: "boolean" } } }, | ||
| baseUrl: { type: "object", properties: { llm: { type: "string", nullable: true }, embed: { type: "string", nullable: true }, graphLlm: { type: "string", nullable: true } } }, | ||
| graphEnabled: { type: "boolean", description: "True when NEO4J_URL + NEO4J_PASSWORD are set." }, | ||
| neo4jUrl: { type: "string", nullable: true }, | ||
| graphLlmModel: { type: "string", nullable: true }, | ||
|
|
@@ -3363,8 +3398,8 @@ app.listen(PORT, () => { | |
| JSON.stringify( | ||
| { | ||
| authMode: AUTH_MODE, | ||
| openaiApiKeyConfigured: HAS_OPENAI_API_KEY, | ||
| openaiBaseUrl: OPENAI_BASE_URL_SANITIZED, | ||
| apiKeyConfigured: { llm: HAS_LLM_API_KEY, embed: HAS_EMBED_API_KEY, graphLlm: HAS_GRAPH_LLM_API_KEY }, | ||
| baseUrl: { llm: sanitizeBaseUrl(LLM_BASE_URL), embed: sanitizeBaseUrl(EMBED_BASE_URL), graphLlm: sanitizeBaseUrl(GRAPH_LLM_BASE_URL) }, | ||
| llmModel: effectiveLlmModel, | ||
| embedModel: EMBED_MODEL, | ||
| qdrantHost: process.env.QDRANT_HOST || null, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By introducing
MEM0_LLM_API_KEY/MEM0_EMBED_API_KEY/MEM0_GRAPH_LLM_API_KEY, deployments can now authenticate without settingOPENAI_API_KEY, butauthModeandopenaiApiKeyConfiguredare still computed fromprocess.env.OPENAI_API_KEYonly. In that valid configuration,/healthand startup diagnostics will incorrectly reportlocal-defaultandfalse, which can mislead operators and monitoring when triaging production incidents. Please compute diagnostics from the effective keys (or expose per-purpose auth diagnostics) instead of the legacy shared variable.Useful? React with 👍 / 👎.