mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies, multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers, semantic caching, combo fallback chains, real-time health monitoring, and a full dashboard with provider management, analytics, and CLI tool integration. Key highlights: - 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.) - 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized) - Export/Import database backup with full archive support - Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor) - 100% TypeScript across src/ and open-sse/ - Docker support with multi-stage builds - Comprehensive documentation and 9 dashboard screenshots
172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
/**
|
|
* Embedding Handler
|
|
*
|
|
* Handles POST /v1/embeddings requests.
|
|
* Proxies to upstream embedding providers using OpenAI-compatible format.
|
|
*
|
|
* Request format (OpenAI-compatible):
|
|
* {
|
|
* "model": "nebius/Qwen/Qwen3-Embedding-8B",
|
|
* "input": "text" | ["text1", "text2"],
|
|
* "dimensions": 4096, // optional
|
|
* "encoding_format": "float" // optional
|
|
* }
|
|
*/
|
|
|
|
import { getEmbeddingProvider, parseEmbeddingModel } from "../config/embeddingRegistry.ts";
|
|
import { saveCallLog } from "@/lib/usageDb";
|
|
|
|
/**
|
|
* Handle embedding request
|
|
* @param {object} options
|
|
* @param {object} options.body - Request body
|
|
* @param {object} options.credentials - Provider credentials { apiKey, accessToken }
|
|
* @param {object} options.log - Logger
|
|
*/
|
|
export async function handleEmbedding({ body, credentials, log }) {
|
|
const { provider, model } = parseEmbeddingModel(body.model);
|
|
const startTime = Date.now();
|
|
|
|
// Summarized request body for call log (avoid storing large embedding input arrays)
|
|
const logRequestBody = {
|
|
model: body.model,
|
|
input_count: Array.isArray(body.input) ? body.input.length : 1,
|
|
dimensions: body.dimensions || undefined,
|
|
};
|
|
|
|
if (!provider) {
|
|
return {
|
|
success: false,
|
|
status: 400,
|
|
error: `Invalid embedding model: ${body.model}. Use format: provider/model`,
|
|
};
|
|
}
|
|
|
|
const providerConfig = getEmbeddingProvider(provider);
|
|
if (!providerConfig) {
|
|
return {
|
|
success: false,
|
|
status: 400,
|
|
error: `Unknown embedding provider: ${provider}`,
|
|
};
|
|
}
|
|
|
|
// Build upstream request
|
|
const upstreamBody: Record<string, any> = {
|
|
model: model,
|
|
input: body.input,
|
|
};
|
|
|
|
// Pass optional parameters
|
|
if (body.dimensions !== undefined) upstreamBody.dimensions = body.dimensions;
|
|
if (body.encoding_format !== undefined) upstreamBody.encoding_format = body.encoding_format;
|
|
|
|
// Build headers
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
const token = credentials.apiKey || credentials.accessToken;
|
|
if (providerConfig.authHeader === "bearer") {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
} else if (providerConfig.authHeader === "x-api-key") {
|
|
headers["x-api-key"] = token;
|
|
}
|
|
|
|
if (log) {
|
|
log.info(
|
|
"EMBED",
|
|
`${provider}/${model} | input: ${Array.isArray(body.input) ? body.input.length + " items" : "1 item"}`
|
|
);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(providerConfig.baseUrl, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(upstreamBody),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
if (log) {
|
|
log.error("EMBED", `${provider} error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
}
|
|
|
|
// Save error call log for Logger panel
|
|
saveCallLog({
|
|
method: "POST",
|
|
path: "/v1/embeddings",
|
|
status: response.status,
|
|
model: `${provider}/${model}`,
|
|
provider,
|
|
duration: Date.now() - startTime,
|
|
error: errorText.slice(0, 500),
|
|
requestBody: logRequestBody,
|
|
}).catch(() => {});
|
|
|
|
return {
|
|
success: false,
|
|
status: response.status,
|
|
error: errorText,
|
|
};
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Save success call log for Logger panel
|
|
// Embeddings only have input tokens (prompt_tokens + total_tokens), no output/completion tokens
|
|
saveCallLog({
|
|
method: "POST",
|
|
path: "/v1/embeddings",
|
|
status: 200,
|
|
model: `${provider}/${model}`,
|
|
provider,
|
|
duration: Date.now() - startTime,
|
|
tokens: {
|
|
prompt_tokens: data.usage?.prompt_tokens || data.usage?.total_tokens || 0,
|
|
completion_tokens: 0,
|
|
},
|
|
requestBody: logRequestBody,
|
|
responseBody: {
|
|
usage: data.usage || null,
|
|
object: "list",
|
|
data_count: data.data?.length || 0,
|
|
},
|
|
}).catch(() => {});
|
|
|
|
// Normalize response to OpenAI format
|
|
return {
|
|
success: true,
|
|
data: {
|
|
object: "list",
|
|
data: data.data || data,
|
|
model: `${provider}/${model}`,
|
|
usage: data.usage || { prompt_tokens: 0, total_tokens: 0 },
|
|
},
|
|
};
|
|
} catch (err) {
|
|
if (log) {
|
|
log.error("EMBED", `${provider} fetch error: ${err.message}`);
|
|
}
|
|
|
|
// Save exception call log for Logger panel
|
|
saveCallLog({
|
|
method: "POST",
|
|
path: "/v1/embeddings",
|
|
status: 502,
|
|
model: `${provider}/${model}`,
|
|
provider,
|
|
duration: Date.now() - startTime,
|
|
error: err.message,
|
|
requestBody: logRequestBody,
|
|
}).catch(() => {});
|
|
|
|
return {
|
|
success: false,
|
|
status: 502,
|
|
error: `Embedding provider error: ${err.message}`,
|
|
};
|
|
}
|
|
}
|