Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ OM_WEAVIATE_CLASS=OpenMemory
# --------------------------------------------
# Embeddings Configuration
# --------------------------------------------
# Available providers: openai, gemini, aws, ollama, local, synthetic, orcarouter
# Available providers: openai, openrouter, gemini, aws, ollama, local, synthetic, orcarouter
# Embedding models per sector can be configured in models.yaml
#
# NOTE: Your selected TIER (fast/smart/deep) affects how embeddings work:
Expand Down Expand Up @@ -105,6 +105,11 @@ OM_EMBED_DELAY_MS=200
# Model override for all sector embeddings (leave empty to use defaults)
# OM_OPENAI_MODEL=text-embedding-qwen3-embedding-4b

# OpenRouter Embeddings (OpenAI-compatible gateway)
# OM_OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
# Model override for all sector embeddings (leave empty to use defaults)
# OM_OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small

# Alibaba Cloud Model Studio (Bailian / DashScope) uses the same
# OpenAI-compatible provider. Set OPENAI_API_KEY and OM_OPENAI_API_KEY empty
# when using DASHSCOPE_API_KEY so the explicit OpenAI keys keep precedence.
Expand All @@ -127,6 +132,9 @@ OM_MAX_PAYLOAD_SIZE=1000000
# OpenAI Embeddings
OPENAI_API_KEY=your-openai-api-key-here

# OpenRouter Embeddings
OPENROUTER_API_KEY=sk-or-v1-your-key-here

# Google Gemini Embeddings
GEMINI_API_KEY=your-gemini-api-key-here

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ results = mem.search("allergies", user_id="user123")
“Waypoint” traces that show exactly which nodes were used in context.

- **Embeddings**
OpenAI, Gemini, Ollama, AWS, [OrcaRouter](https://www.orcarouter.ai), synthetic fallback.
OpenAI, [OpenRouter](https://openrouter.ai), Gemini, Ollama, AWS, [OrcaRouter](https://www.orcarouter.ai), synthetic fallback.

- **Integrations**
LangChain, CrewAI, AutoGen, Streamlit, MCP, VS Code, IDEs.
Expand Down
2 changes: 2 additions & 0 deletions dashboard/app/api/settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export async function GET() {

const masked = { ...settings }
if (masked.OPENAI_API_KEY) masked.OPENAI_API_KEY = '***'
if (masked.OPENROUTER_API_KEY) masked.OPENROUTER_API_KEY = '***'
if (masked.OM_OPENROUTER_API_KEY) masked.OM_OPENROUTER_API_KEY = '***'
if (masked.DASHSCOPE_API_KEY) masked.DASHSCOPE_API_KEY = '***'
if (masked.GEMINI_API_KEY) masked.GEMINI_API_KEY = '***'
if (masked.AWS_SECRET_ACCESS_KEY) masked.AWS_SECRET_ACCESS_KEY = "***"
Expand Down
23 changes: 22 additions & 1 deletion dashboard/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const SETTING_METADATA: Record<string, SettingInfo> = {
label: 'Embedding Provider',
description: 'AI provider for generating embeddings (used in SMART/DEEP tiers)',
type: 'select',
options: ['openai', 'gemini', 'aws', 'ollama', 'local', 'synthetic', 'orcarouter']
options: ['openai', 'openrouter', 'gemini', 'aws', 'ollama', 'local', 'synthetic', 'orcarouter']
},
OM_VEC_DIM: {
category: 'Embeddings',
Expand Down Expand Up @@ -231,6 +231,20 @@ const SETTING_METADATA: Record<string, SettingInfo> = {
type: 'text',
placeholder: 'text-embedding-3-small'
},
OM_OPENROUTER_BASE_URL: {
category: 'Embeddings',
label: 'OpenRouter Base URL',
description: 'OpenRouter OpenAI-compatible API endpoint',
type: 'text',
placeholder: 'https://openrouter.ai/api/v1'
},
OM_OPENROUTER_EMBEDDING_MODEL: {
category: 'Embeddings',
label: 'OpenRouter Model Override',
description: 'Override the default OpenRouter embedding model',
type: 'text',
placeholder: 'openai/text-embedding-3-small'
},
OM_ORCAROUTER_BASE_URL: {
category: 'Embeddings',
label: 'OrcaRouter Base URL',
Expand Down Expand Up @@ -259,6 +273,13 @@ const SETTING_METADATA: Record<string, SettingInfo> = {
type: 'password',
placeholder: 'sk-...'
},
OPENROUTER_API_KEY: {
category: 'API Keys',
label: 'OpenRouter API Key',
description: 'API key for OpenRouter embeddings',
type: 'password',
placeholder: 'sk-or-v1-...'
},
DASHSCOPE_API_KEY: {
category: 'API Keys',
label: 'DashScope API Key',
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ services:
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:-}
- OM_OPENAI_BASE_URL=${OM_OPENAI_BASE_URL:-https://api.openai.com/v1}
- OM_OPENAI_MODEL=${OM_OPENAI_MODEL:-}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
- OM_OPENROUTER_API_KEY=${OM_OPENROUTER_API_KEY:-}
- OM_OPENROUTER_BASE_URL=${OM_OPENROUTER_BASE_URL:-https://openrouter.ai/api/v1}
- OM_OPENROUTER_MODEL=${OM_OPENROUTER_MODEL:-}
- OM_OPENROUTER_EMBEDDING_MODEL=${OM_OPENROUTER_EMBEDDING_MODEL:-}

# Gemini Provider
- GEMINI_API_KEY=${GEMINI_API_KEY:-}
Expand Down
4 changes: 2 additions & 2 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Configure your MCP client (`.mcp.json` for Cursor/Windsurf, or Claude settings):
#### Environment Variables

- `OM_METADATA_BACKEND`: Database type (`sqlite` or `postgres`)
- `OM_EMBEDDINGS`: Embedding provider (`openai`, `aws`, `ollama`, `synthetic`, `orcarouter`)
- `OM_EMBEDDINGS`: Embedding provider (`openai`, `openrouter`, `aws`, `ollama`, `synthetic`, `orcarouter`)
- Database credentials (if using Postgres): `OM_PG_HOST`, `OM_PG_DB`, `OM_PG_USER`, `OM_PG_PASSWORD`

## Memory Systems
Expand Down Expand Up @@ -612,7 +612,7 @@ If `type="factual"` returns no results:

Ensure environment variables are set:
- Postgres: `OM_PG_HOST`, `OM_PG_DB`, `OM_PG_USER`, `OM_PG_PASSWORD`
- Embeddings: `OPENAI_API_KEY` (or a compatible provider key such as `DASHSCOPE_API_KEY` or `ORCAROUTER_API_KEY`)
- Embeddings: `OPENAI_API_KEY` (or a compatible provider key such as `OPENROUTER_API_KEY`, `DASHSCOPE_API_KEY`, or `ORCAROUTER_API_KEY`)

## Performance

Expand Down
6 changes: 5 additions & 1 deletion models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
episodic:
ollama: nomic-embed-text
openai: text-embedding-3-small
openrouter: openai/text-embedding-3-small
gemini: models/gemini-embedding-001
aws: amazon.titan-embed-text-v2:0
local: all-MiniLM-L6-v2
Expand All @@ -16,6 +17,7 @@ episodic:
semantic:
ollama: nomic-embed-text
openai: text-embedding-3-small
openrouter: openai/text-embedding-3-small
gemini: models/gemini-embedding-001
aws: amazon.titan-embed-text-v2:0
local: all-MiniLM-L6-v2
Expand All @@ -24,6 +26,7 @@ semantic:
procedural:
ollama: nomic-embed-text
openai: text-embedding-3-small
openrouter: openai/text-embedding-3-small
gemini: models/gemini-embedding-001
aws: amazon.titan-embed-text-v2:0
local: all-MiniLM-L6-v2
Expand All @@ -32,6 +35,7 @@ procedural:
emotional:
ollama: nomic-embed-text
openai: text-embedding-3-small
openrouter: openai/text-embedding-3-small
gemini: models/gemini-embedding-001
aws: amazon.titan-embed-text-v2:0
local: all-MiniLM-L6-v2
Expand All @@ -40,6 +44,7 @@ emotional:
reflective:
ollama: nomic-embed-text
openai: text-embedding-3-large
openrouter: openai/text-embedding-3-small
gemini: models/gemini-embedding-001
aws: amazon.titan-embed-text-v2:0
local: all-mpnet-base-v2
Expand All @@ -61,4 +66,3 @@ reflective:

#AWS models:
# - amazon.titan-embed-text-v2:0 (1024d, 512d, 256)

7 changes: 6 additions & 1 deletion packages/openmemory-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ OM_DB_PATH=./data/om.db # sqlite file path (default: ./data/openmem
OM_DB_URL=sqlite://:memory: # or use in-memory db

# embeddings
OM_EMBEDDINGS=ollama # synthetic | openai | gemini | ollama
OM_EMBEDDINGS=ollama # synthetic | openai | openrouter | gemini | ollama
OM_OLLAMA_URL=http://localhost:11434
OM_OLLAMA_MODEL=embeddinggemma # or nomic-embed-text, mxbai-embed-large

Expand All @@ -102,6 +102,11 @@ OM_OLLAMA_MODEL=embeddinggemma # or nomic-embed-text, mxbai-embed-large
# OM_OPENAI_BASE_URL=https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
# OM_OPENAI_MODEL=text-embedding-v4

# OpenRouter through its OpenAI-compatible endpoint
# OPENROUTER_API_KEY=sk-or-v1-...
# OM_OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
# OM_OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small

# openai
OPENAI_API_KEY=sk-...
OM_OPENAI_MODEL=text-embedding-3-small
Expand Down
10 changes: 10 additions & 0 deletions packages/openmemory-js/src/core/cfg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export const env = {
"https://api.openai.com/v1",
),
openai_model: process.env.OM_OPENAI_MODEL,
openrouter_key:
process.env.OPENROUTER_API_KEY ||
process.env.OM_OPENROUTER_API_KEY ||
"",
openrouter_base_url: str(
process.env.OM_OPENROUTER_BASE_URL,
"https://openrouter.ai/api/v1",
),
openrouter_model: process.env.OM_OPENROUTER_MODEL,
openrouter_embedding_model: process.env.OM_OPENROUTER_EMBEDDING_MODEL,
gemini_key:
process.env.GEMINI_API_KEY || process.env.OM_GEMINI_API_KEY || "",
AWS_REGION: process.env.AWS_REGION || "",
Expand Down
11 changes: 11 additions & 0 deletions packages/openmemory-js/src/core/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const get_defaults = (): model_cfg => ({
episodic: {
ollama: "nomic-embed-text",
openai: "text-embedding-3-small",
openrouter: "openai/text-embedding-3-small",
gemini: "models/gemini-embedding-001",
aws: "amazon.titan-embed-text-v2:0",
siray: "text-embedding-3-small",
Expand All @@ -60,6 +61,7 @@ const get_defaults = (): model_cfg => ({
semantic: {
ollama: "nomic-embed-text",
openai: "text-embedding-3-small",
openrouter: "openai/text-embedding-3-small",
gemini: "models/gemini-embedding-001",
aws: "amazon.titan-embed-text-v2:0",
siray: "text-embedding-3-small",
Expand All @@ -71,6 +73,7 @@ const get_defaults = (): model_cfg => ({
openai: "text-embedding-3-small",
gemini: "models/gemini-embedding-001",
aws: "amazon.titan-embed-text-v2:0",
openrouter: "openai/text-embedding-3-small",
orcarouter: "orcarouter/auto",
local: "all-MiniLM-L6-v2",
},
Expand All @@ -79,6 +82,7 @@ const get_defaults = (): model_cfg => ({
openai: "text-embedding-3-small",
gemini: "models/gemini-embedding-001",
aws: "amazon.titan-embed-text-v2:0",
openrouter: "openai/text-embedding-3-small",
orcarouter: "orcarouter/auto",
local: "all-MiniLM-L6-v2",
},
Expand All @@ -87,6 +91,7 @@ const get_defaults = (): model_cfg => ({
openai: "text-embedding-3-large",
gemini: "models/gemini-embedding-001",
aws: "amazon.titan-embed-text-v2:0",
openrouter: "openai/text-embedding-3-small",
orcarouter: "orcarouter/auto",
local: "all-mpnet-base-v2",
},
Expand All @@ -99,6 +104,12 @@ export const get_model = (sector: string, provider: string): string => {
if (provider === "openai" && process.env.OM_OPENAI_MODEL) {
return process.env.OM_OPENAI_MODEL;
}
if (
provider === "openrouter" &&
process.env.OM_OPENROUTER_EMBEDDING_MODEL
) {
return process.env.OM_OPENROUTER_EMBEDDING_MODEL;
}
if (provider === "orcarouter" && process.env.OM_ORCAROUTER_EMBEDDING_MODEL) {
return process.env.OM_ORCAROUTER_EMBEDDING_MODEL;
}
Expand Down
83 changes: 80 additions & 3 deletions packages/openmemory-js/src/memory/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,20 @@ export async function embedQueryForAllSectors(
return result;
}

if (env.emb_kind === "gemini" && env.gemini_key) {
if (
env.embed_mode === "simple" &&
(env.emb_kind === "gemini" ||
env.emb_kind === "openai" ||
env.emb_kind === "openrouter" ||
env.emb_kind === "orcarouter")
) {
try {
const txts: Record<string, string> = {};
for (const s of sectors) txts[s] = query;
return await emb_gemini(txts);
return await emb_batch_with_fallback(txts);
} catch (e) {
console.error(
`[EMBED] Gemini batch failed, falling back to sequential: ${e}`,
`[EMBED] Batch query embedding failed, falling back to sequential: ${e}`,
);
}
}
Expand All @@ -134,6 +140,8 @@ async function embed_with_provider(
switch (provider) {
case "openai":
return await emb_openai(t, s);
case "openrouter":
return await emb_openrouter(t, s);
case "gemini":
return (await emb_gemini({ [s]: t }))[s];
case "ollama":
Expand Down Expand Up @@ -205,6 +213,9 @@ async function emb_batch_with_fallback(
case "openai":
result = await emb_batch_openai(txts);
break;
case "openrouter":
result = await emb_batch_openrouter(txts);
break;
case "orcarouter":
result = await emb_batch_orcarouter(txts);
break;
Expand Down Expand Up @@ -291,6 +302,58 @@ async function emb_batch_openai(
return out;
}

async function emb_openrouter(t: string, s: string): Promise<number[]> {
if (!env.openrouter_key) throw new Error("OpenRouter key missing");
const m = get_model(s, "openrouter");
const r = await fetchWithTimeout(
`${env.openrouter_base_url.replace(/\/$/, "")}/embeddings`,
{
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${env.openrouter_key}`,
},
body: JSON.stringify({
input: t,
model: env.openrouter_embedding_model || m,
...(env.vec_dim ? { dimensions: env.vec_dim } : {}),
}),
},
);
if (!r.ok) throw new Error(`OpenRouter: ${r.status}`);
return ((await r.json()) as any).data[0].embedding;
}

async function emb_batch_openrouter(
txts: Record<string, string>,
): Promise<Record<string, number[]>> {
if (!env.openrouter_key) throw new Error("OpenRouter key missing");
const secs = Object.keys(txts),
m = get_model("semantic", "openrouter");
const r = await fetchWithTimeout(
`${env.openrouter_base_url.replace(/\/$/, "")}/embeddings`,
{
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${env.openrouter_key}`,
},
body: JSON.stringify({
input: Object.values(txts),
model: env.openrouter_embedding_model || m,
...(env.vec_dim ? { dimensions: env.vec_dim } : {}),
}),
},
);
if (!r.ok) throw new Error(`OpenRouter batch: ${r.status}`);
const d = (await r.json()) as any,
out: Record<string, number[]> = {};
secs.forEach((sector, index) => {
out[sector] = d.data[index].embedding;
});
return out;
}

const task_map: Record<string, string> = {
episodic: "RETRIEVAL_DOCUMENT",
semantic: "SEMANTIC_SIMILARITY",
Expand Down Expand Up @@ -631,6 +694,7 @@ export async function embedMultiSector(
simp &&
(env.emb_kind === "gemini" ||
env.emb_kind === "openai" ||
env.emb_kind === "openrouter" ||
env.emb_kind === "orcarouter")
) {
console.error(
Expand Down Expand Up @@ -738,6 +802,7 @@ export const getEmbeddingInfo = () => {
env.embed_mode === "simple" &&
(env.emb_kind === "gemini" ||
env.emb_kind === "openai" ||
env.emb_kind === "openrouter" ||
env.emb_kind === "orcarouter"),
advanced_parallel: env.adv_embed_parallel,
embed_delay_ms: env.embed_delay_ms,
Expand All @@ -754,6 +819,18 @@ export const getEmbeddingInfo = () => {
emotional: get_model("emotional", "openai"),
reflective: get_model("reflective", "openai"),
};
} else if (env.emb_kind === "openrouter") {
i.configured = !!env.openrouter_key;
i.base_url = env.openrouter_base_url;
i.model_override = env.openrouter_embedding_model || null;
i.batch_api = env.embed_mode === "simple";
i.models = {
episodic: get_model("episodic", "openrouter"),
semantic: get_model("semantic", "openrouter"),
procedural: get_model("procedural", "openrouter"),
emotional: get_model("emotional", "openrouter"),
reflective: get_model("reflective", "openrouter"),
};
} else if (env.emb_kind === "gemini") {
i.configured = !!env.gemini_key;
i.batch_api = env.embed_mode === "simple";
Expand Down
Loading
Loading