Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# - gemini:gemini-embedding-exp-03-07 (Google Generative AI)
# - aws:amazon.titan-embed-text-v1
# - microsoft:text-embedding-ada-002
# - orcarouter:openai/text-embedding-3-small (OrcaRouter)
DOCS_MCP_EMBEDDING_MODEL=

# PostHog Analytics Configuration (Optional)
Expand Down Expand Up @@ -44,6 +45,12 @@ AZURE_OPENAI_API_INSTANCE_NAME=your-instance
AZURE_OPENAI_API_DEPLOYMENT_NAME=your-deployment
AZURE_OPENAI_API_VERSION=2024-02-01

# OrcaRouter Configuration
# Required for orcarouter provider
ORCAROUTER_API_KEY=your-orcarouter-api-key
# Optional: Override the default OrcaRouter endpoint (https://api.orcarouter.ai/v1)
# ORCAROUTER_API_BASE=

# Optional: Specify a custom directory to store the SQLite database file (documents.db).
# If set, this path takes precedence over the default locations.
# Default behavior (if unset):
Expand Down
15 changes: 15 additions & 0 deletions docs/guides/embedding-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ If you leave the model empty but provide `OPENAI_API_KEY`, the server defaults t
- `gemini:embedding-001` (Google Gemini)
- `aws:amazon.titan-embed-text-v1` (AWS Bedrock)
- `microsoft:text-embedding-ada-002` (Azure OpenAI)
- `orcarouter:openai/text-embedding-3-small` (OrcaRouter)
- Or any OpenAI-compatible model name

## Provider Configuration
Expand All @@ -34,6 +35,8 @@ Provider credentials use the provider-specific environment variables listed belo
| `AZURE_OPENAI_API_INSTANCE_NAME` | Azure OpenAI instance name. |
| `AZURE_OPENAI_API_DEPLOYMENT_NAME` | Azure OpenAI deployment name. |
| `AZURE_OPENAI_API_VERSION` | Azure OpenAI API version. |
| `ORCAROUTER_API_KEY` | OrcaRouter API key for embeddings. |
| `ORCAROUTER_API_BASE` | Optional OrcaRouter endpoint override (defaults to `https://api.orcarouter.ai/v1`). |

### Examples

Expand Down Expand Up @@ -114,6 +117,18 @@ DOCS_MCP_EMBEDDING_MODEL="microsoft:text-embedding-ada-002" \
npx @arabold/docs-mcp-server@latest
```

#### OrcaRouter

Use embeddings through the OrcaRouter OpenAI-compatible gateway. Model names follow the OrcaRouter model catalog (e.g., `openai/text-embedding-3-small`).

```bash
ORCAROUTER_API_KEY="your-orcarouter-api-key" \
DOCS_MCP_EMBEDDING_MODEL="orcarouter:openai/text-embedding-3-small" \
npx @arabold/docs-mcp-server@latest
```

Set `ORCAROUTER_API_BASE` to override the default endpoint (`https://api.orcarouter.ai/v1`).

## Changing the Embedding Model

When you change the embedding model or vector dimension after initial setup, existing embedding vectors become semantically incompatible with the new configuration. The server detects this automatically by tracking the active model identity in a metadata table.
Expand Down
1 change: 1 addition & 0 deletions src/store/embeddings/EmbeddingConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ describe("EmbeddingConfig", () => {
"aws",
"microsoft",
"sagemaker",
"orcarouter",
];

it("should accept all valid providers", () => {
Expand Down
4 changes: 3 additions & 1 deletion src/store/embeddings/EmbeddingConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type EmbeddingProvider =
| "gemini"
| "aws"
| "microsoft"
| "sagemaker";
| "sagemaker"
| "orcarouter";

/**
* Embedding model configuration parsed from environment variables.
Expand Down Expand Up @@ -333,6 +334,7 @@ export class EmbeddingConfig {
* - aws: AWS Bedrock models
* - microsoft: Azure OpenAI
* - sagemaker: AWS SageMaker hosted models
* - orcarouter: OrcaRouter (OpenAI-compatible AI gateway)
*
* @param modelSpec Model specification (e.g., "openai:text-embedding-3-small"), defaults to "text-embedding-3-small"
* @returns Parsed embedding model configuration
Expand Down
42 changes: 42 additions & 0 deletions src/store/embeddings/EmbeddingFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ beforeEach(() => {
AZURE_OPENAI_API_INSTANCE_NAME: "test-instance",
AZURE_OPENAI_API_DEPLOYMENT_NAME: "test-deployment",
AZURE_OPENAI_API_VERSION: "2024-02-01",
ORCAROUTER_API_KEY: "test-orcarouter-key",
},
});
});
Expand Down Expand Up @@ -150,6 +151,47 @@ describe("createEmbeddingModel", () => {
);
});

test("should create OrcaRouter embeddings via OpenAI-compatible endpoint", () => {
const model = createEmbeddingModel(
"orcarouter:openai/text-embedding-3-small",
runtimeConfig,
);
expect(model).toBeInstanceOf(OpenAIEmbeddings);
expect(model).toMatchObject({
modelName: "openai/text-embedding-3-small",
encodingFormat: "float",
});
const clientConfig = (model as unknown as { clientConfig?: { baseURL?: string } })
.clientConfig;
expect(clientConfig?.baseURL).toBe("https://api.orcarouter.ai/v1");
});

test("should honor ORCAROUTER_API_BASE for OrcaRouter embeddings", () => {
vi.stubGlobal("process", {
env: {
ORCAROUTER_API_KEY: "test-orcarouter-key",
ORCAROUTER_API_BASE: "https://orcarouter.example.com/v1",
},
});
const model = createEmbeddingModel("orcarouter:test-model", runtimeConfig);
expect(model).toBeInstanceOf(OpenAIEmbeddings);
const clientConfig = (model as unknown as { clientConfig?: { baseURL?: string } })
.clientConfig;
expect(clientConfig?.baseURL).toBe("https://orcarouter.example.com/v1");
});

test("should throw MissingCredentialsError for OrcaRouter without ORCAROUTER_API_KEY", () => {
vi.stubGlobal("process", {
env: {
// Missing ORCAROUTER_API_KEY
},
});

expect(() =>
createEmbeddingModel("orcarouter:openai/text-embedding-3-small", runtimeConfig),
).toThrow(MissingCredentialsError);
});

test("should throw MissingCredentialsError for Azure OpenAI without required env vars", () => {
// Override env to simulate missing Azure variables
vi.stubGlobal("process", {
Expand Down
37 changes: 35 additions & 2 deletions src/store/embeddings/EmbeddingFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export type EmbeddingProvider =
| "gemini"
| "aws"
| "microsoft"
| "sagemaker";
| "sagemaker"
| "orcarouter";

/**
* Error thrown when an invalid or unsupported embedding provider is specified.
Expand All @@ -31,7 +32,7 @@ export class UnsupportedProviderError extends Error {
constructor(provider: string) {
super(
`❌ Unsupported embedding provider: ${provider}\n` +
" Supported providers: openai, vertex, gemini, aws, microsoft, sagemaker\n" +
" Supported providers: openai, vertex, gemini, aws, microsoft, sagemaker, orcarouter\n" +
" See README.md for configuration options or run with --help for more details.",
);
this.name = "UnsupportedProviderError";
Expand Down Expand Up @@ -93,6 +94,9 @@ export function areCredentialsAvailable(provider: EmbeddingProvider): boolean {
);
}

case "orcarouter":
return !!process.env.ORCAROUTER_API_KEY;

default:
return false;
}
Expand All @@ -110,6 +114,7 @@ export function areCredentialsAvailable(provider: EmbeddingProvider): boolean {
* - Google GenAI (Gemini): GOOGLE_API_KEY
* - AWS: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (or BEDROCK_AWS_REGION)
* - Microsoft: AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_INSTANCE_NAME, AZURE_OPENAI_API_DEPLOYMENT_NAME, AZURE_OPENAI_API_VERSION
* - OrcaRouter: ORCAROUTER_API_KEY (and optionally ORCAROUTER_API_BASE)
*
* @param providerAndModel - The provider and model name in the format "provider:model_name"
* or just "model_name" for OpenAI models.
Expand Down Expand Up @@ -271,6 +276,34 @@ export function createEmbeddingModel(
});
}

case "orcarouter": {
if (!process.env.ORCAROUTER_API_KEY) {
throw new MissingCredentialsError("orcarouter", ["ORCAROUTER_API_KEY"]);
}
// OrcaRouter exposes an OpenAI-compatible embeddings API on the same
// endpoint as its chat/agent gateway, so the OpenAI SDK path applies.
// The "float" encoding format keeps OpenAI-compatible providers that
// ignore the parameter from silently corrupting the returned vectors.
const config: Partial<OpenAIEmbeddingsParams> & { configuration?: ClientOptions } =
{
...baseConfig,
modelName: model,
batchSize: 512,
timeout: requestTimeoutMs,
encodingFormat: "float",
};
// Custom base URL if specified, otherwise the default OrcaRouter endpoint
const baseURL = process.env.ORCAROUTER_API_BASE || "https://api.orcarouter.ai/v1";
config.configuration = {
baseURL,
timeout: requestTimeoutMs,
};
return new OpenAIEmbeddings({
...config,
apiKey: process.env.ORCAROUTER_API_KEY,
});
}

default:
throw new UnsupportedProviderError(provider);
}
Expand Down