fix: switch embedding dimension from 1536 to 2560 for qwen-embedding-4b
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 5s

This commit is contained in:
Antigravity
2026-05-12 09:07:55 +00:00
parent feaeb075ce
commit e09ea3a145
10 changed files with 105 additions and 27 deletions

View File

@@ -1,13 +1,13 @@
import { createOpenAI } from '@ai-sdk/openai';
import { generateObject, generateText as aiGenerateText, embed, stepCountIs } from 'ai';
import { generateObject, generateText as aiGenerateText, stepCountIs } from 'ai';
import { z } from 'zod';
import { AIProvider, TagSuggestion, TitleSuggestion, ToolUseOptions, ToolCallResult } from '../types';
export class CustomOpenAIProvider implements AIProvider {
private model: any;
private embeddingModel: any;
private apiKey: string;
private baseUrl: string;
private embeddingModelName: string;
constructor(
apiKey: string,
@@ -17,6 +17,7 @@ export class CustomOpenAIProvider implements AIProvider {
) {
this.apiKey = apiKey;
this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
this.embeddingModelName = embeddingModelName;
// Create OpenAI-compatible client with custom base URL
// Use .chat() to force /chat/completions endpoint (avoids Responses API)
const customClient = createOpenAI({
@@ -44,7 +45,16 @@ export class CustomOpenAIProvider implements AIProvider {
});
this.model = customClient.chat(modelName);
this.embeddingModel = customClient.embedding(embeddingModelName);
}
private async fetchWithTimeout(url: string, options: RequestInit, timeoutMs: number = 60_000): Promise<Response> {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), timeoutMs)
try {
return await fetch(url, { ...options, signal: controller.signal })
} finally {
clearTimeout(timer)
}
}
async generateTags(content: string): Promise<TagSuggestion[]> {
@@ -70,13 +80,40 @@ export class CustomOpenAIProvider implements AIProvider {
async getEmbeddings(text: string): Promise<number[]> {
try {
const { embedding } = await embed({
model: this.embeddingModel,
value: text,
const response = await this.fetchWithTimeout(`${this.baseUrl}/embeddings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'HTTP-Referer': 'https://localhost:3000',
'X-Title': 'Memento AI',
},
body: JSON.stringify({
model: this.embeddingModelName,
input: text,
}),
});
return embedding;
if (!response.ok) {
const errText = await response.text();
throw new Error(`${this.baseUrl}/embeddings error ${response.status}: ${errText}`);
}
const data = await response.json();
// Standard OpenAI-compatible response: { data: [{ embedding: number[] }] }
if (data.data && Array.isArray(data.data) && data.data[0]?.embedding) {
return data.data[0].embedding;
}
// Fallback: some providers return { embedding: number[] }
if (data.embedding && Array.isArray(data.embedding)) {
return data.embedding;
}
throw new Error(`Unexpected embeddings response shape: ${JSON.stringify(data)}`);
} catch (e) {
console.error('Error generating embeddings (Custom OpenAI):', e);
console.error('Error generating embeddings (CustomOpenAI):', e);
throw e;
}
}

View File

@@ -1,21 +1,35 @@
import { createOpenAI } from '@ai-sdk/openai';
import { generateObject, generateText as aiGenerateText, embed, stepCountIs } from 'ai';
import { generateObject, generateText as aiGenerateText, stepCountIs } from 'ai';
import { z } from 'zod';
import { AIProvider, TagSuggestion, TitleSuggestion, ToolUseOptions, ToolCallResult } from '../types';
export class OpenRouterProvider implements AIProvider {
private model: any;
private embeddingModel: any;
private apiKey: string;
private baseUrl: string;
private embeddingModelName: string;
constructor(apiKey: string, modelName: string = 'anthropic/claude-3-haiku', embeddingModelName: string = 'openai/text-embedding-3-small') {
this.apiKey = apiKey;
this.baseUrl = 'https://openrouter.ai/api/v1';
this.embeddingModelName = embeddingModelName;
// Create OpenAI-compatible client for OpenRouter
const openrouter = createOpenAI({
baseURL: 'https://openrouter.ai/api/v1',
baseURL: this.baseUrl,
apiKey: apiKey,
});
this.model = openrouter.chat(modelName);
this.embeddingModel = openrouter.embedding(embeddingModelName);
}
private async fetchWithTimeout(url: string, options: RequestInit, timeoutMs: number = 60_000): Promise<Response> {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), timeoutMs)
try {
return await fetch(url, { ...options, signal: controller.signal })
} finally {
clearTimeout(timer)
}
}
async generateTags(content: string): Promise<TagSuggestion[]> {
@@ -41,11 +55,38 @@ export class OpenRouterProvider implements AIProvider {
async getEmbeddings(text: string): Promise<number[]> {
try {
const { embedding } = await embed({
model: this.embeddingModel,
value: text,
const response = await this.fetchWithTimeout(`${this.baseUrl}/embeddings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'HTTP-Referer': 'https://localhost:3000',
'X-Title': 'Memento AI',
},
body: JSON.stringify({
model: this.embeddingModelName,
input: text,
}),
});
return embedding;
if (!response.ok) {
const errText = await response.text();
throw new Error(`OpenRouter embeddings error ${response.status}: ${errText}`);
}
const data = await response.json();
// OpenRouter returns { data: [{ embedding: number[] }] }
if (data.data && Array.isArray(data.data) && data.data[0]?.embedding) {
return data.data[0].embedding;
}
// Fallback: some OpenAI-compatible providers return { embedding: number[] }
if (data.embedding && Array.isArray(data.embedding)) {
return data.embedding;
}
throw new Error(`Unexpected OpenRouter embeddings response shape: ${JSON.stringify(data)}`);
} catch (e) {
console.error('Error generating embeddings (OpenRouter):', e);
throw e;

View File

@@ -1,7 +1,7 @@
/**
* Embedding Service
* Generates vector embeddings for semantic search and similarity analysis.
* Stores embeddings as native pgvector(1536) in PostgreSQL.
* Stores embeddings as native pgvector(2560) in PostgreSQL.
*/
import { getAIProvider } from '../factory'
@@ -14,7 +14,7 @@ export interface EmbeddingResult {
}
export class EmbeddingService {
private readonly EMBEDDING_DIMENSION = 1536
private readonly EMBEDDING_DIMENSION = 2560
async generateEmbedding(text: string): Promise<EmbeddingResult> {
if (!text || text.trim().length === 0) {