feat: add LM Studio error messages and validation handling in embedder

This commit is contained in:
Daniel Riccio 2025-07-10 14:51:04 -05:00
parent cd240325ce
commit 2e24c8e7dc
No known key found for this signature in database
GPG key ID: FFD5FD825F8E8209
20 changed files with 389 additions and 17 deletions

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "El model d'Ollama no és capaç de fer incrustacions: {{modelId}}",
"hostNotFound": "No s'ha trobat l'amfitrió d'Ollama: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "El servidor de LM Studio no s'està executant a {{baseUrl}}. Assegureu-vos que LM Studio s'estigui executant amb el servidor local activat.",
"modelNotFound": "No s'ha trobat el model \"{{modelId}}\" a LM Studio. Assegureu-vos que el model estigui carregat a LM Studio.",
"hostNotFound": "No es pot connectar a LM Studio a {{baseUrl}}. Comproveu la configuració de l'URL base."
},
"scanner": {
"unknownErrorProcessingFile": "Error desconegut en processar el fitxer {{filePath}}",
"unknownErrorDeletingPoints": "Error desconegut en eliminar els punts per a {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama-Modell ist nicht für Einbettungen geeignet: {{modelId}}",
"hostNotFound": "Ollama-Host nicht gefunden: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Der LM Studio-Server wird unter {{baseUrl}} nicht ausgeführt. Bitte stelle sicher, dass LM Studio mit aktiviertem lokalen Server läuft.",
"modelNotFound": "Modell \"{{modelId}}\" in LM Studio nicht gefunden. Bitte stelle sicher, dass das Modell in LM Studio geladen ist.",
"hostNotFound": "Verbindung zu LM Studio unter {{baseUrl}} nicht möglich. Bitte überprüfe die Konfiguration der Basis-URL."
},
"scanner": {
"unknownErrorProcessingFile": "Unbekannter Fehler beim Verarbeiten der Datei {{filePath}}",
"unknownErrorDeletingPoints": "Unbekannter Fehler beim Löschen der Punkte für {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama model is not embedding capable: {{modelId}}",
"hostNotFound": "Ollama host not found: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studio server is not running at {{baseUrl}}. Please ensure LM Studio is running with the local server enabled.",
"modelNotFound": "Model \"{{modelId}}\" not found in LM Studio. Please ensure the model is loaded in LM Studio.",
"hostNotFound": "Cannot connect to LM Studio at {{baseUrl}}. Please check the base URL configuration."
},
"scanner": {
"unknownErrorProcessingFile": "Unknown error processing file {{filePath}}",
"unknownErrorDeletingPoints": "Unknown error deleting points for {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "El modelo Ollama no es capaz de realizar incrustaciones: {{modelId}}",
"hostNotFound": "No se encuentra el host de Ollama: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "El servidor de LM Studio no se está ejecutando en {{baseUrl}}. Asegúrate de que LM Studio se esté ejecutando con el servidor local habilitado.",
"modelNotFound": "No se encontró el modelo \"{{modelId}}\" en LM Studio. Asegúrate de que el modelo esté cargado en LM Studio.",
"hostNotFound": "No se puede conectar a LM Studio en {{baseUrl}}. Comprueba la configuración de la URL base."
},
"scanner": {
"unknownErrorProcessingFile": "Error desconocido procesando archivo {{filePath}}",
"unknownErrorDeletingPoints": "Error desconocido eliminando puntos para {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Le modèle Ollama n'est pas capable d'intégrer : {{modelId}}",
"hostNotFound": "Hôte Ollama introuvable : {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Le serveur LM Studio n'est pas en cours d'exécution à {{baseUrl}}. Veuillez vous assurer que LM Studio est en cours d'exécution avec le serveur local activé.",
"modelNotFound": "Le modèle \"{{modelId}}\" n'a pas été trouvé dans LM Studio. Veuillez vous assurer que le modèle est chargé dans LM Studio.",
"hostNotFound": "Impossible de se connecter à LM Studio à {{baseUrl}}. Veuillez vérifier la configuration de l'URL de base."
},
"scanner": {
"unknownErrorProcessingFile": "Erreur inconnue lors du traitement du fichier {{filePath}}",
"unknownErrorDeletingPoints": "Erreur inconnue lors de la suppression des points pour {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "ओलामा मॉडल एम्बेडिंग में सक्षम नहीं है: {{modelId}}",
"hostNotFound": "ओलामा होस्ट नहीं मिला: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "एलएम स्टूडियो सर्वर {{baseUrl}} पर नहीं चल रहा है। कृपया सुनिश्चित करें कि एलएम स्टूडियो स्थानीय सर्वर सक्षम के साथ चल रहा है।",
"modelNotFound": "एलएम स्टूडियो में मॉडल \"{{modelId}}\" नहीं मिला। कृपया सुनिश्चित करें कि मॉडल एलएम स्टूडियो में लोड किया गया है।",
"hostNotFound": "{{baseUrl}} पर एलएम स्टूडियो से कनेक्ट नहीं हो सकता। कृपया आधार यूआरएल कॉन्फ़िगरेशन की जांच करें।"
},
"scanner": {
"unknownErrorProcessingFile": "फ़ाइल {{filePath}} प्रसंस्करण में अज्ञात त्रुटि",
"unknownErrorDeletingPoints": "{{filePath}} के लिए बिंदु हटाने में अज्ञात त्रुटि",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Model Ollama tidak mampu melakukan embedding: {{modelId}}",
"hostNotFound": "Host Ollama tidak ditemukan: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Server LM Studio tidak berjalan di {{baseUrl}}. Pastikan LM Studio berjalan dengan server lokal diaktifkan.",
"modelNotFound": "Model \"{{modelId}}\" tidak ditemukan di LM Studio. Pastikan model dimuat di LM Studio.",
"hostNotFound": "Tidak dapat terhubung ke LM Studio di {{baseUrl}}. Silakan periksa konfigurasi URL dasar."
},
"scanner": {
"unknownErrorProcessingFile": "Error tidak dikenal saat memproses file {{filePath}}",
"unknownErrorDeletingPoints": "Error tidak dikenal saat menghapus points untuk {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Il modello Ollama non è in grado di eseguire l'embedding: {{modelId}}",
"hostNotFound": "Host Ollama non trovato: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Il server di LM Studio non è in esecuzione su {{baseUrl}}. Assicurati che LM Studio sia in esecuzione con il server locale abilitato.",
"modelNotFound": "Modello \"{{modelId}}\" non trovato in LM Studio. Assicurati che il modello sia caricato in LM Studio.",
"hostNotFound": "Impossibile connettersi a LM Studio su {{baseUrl}}. Controlla la configurazione dell'URL di base."
},
"scanner": {
"unknownErrorProcessingFile": "Errore sconosciuto nell'elaborazione del file {{filePath}}",
"unknownErrorDeletingPoints": "Errore sconosciuto nell'eliminazione dei punti per {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollamaモデルは埋め込みに対応していません{{modelId}}",
"hostNotFound": "Ollamaホストが見つかりません{{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studioサーバーが{{baseUrl}}で実行されていません。LM Studioがローカルサーバーを有効にして実行されていることを確認してください。",
"modelNotFound": "モデル「{{modelId}}」がLM Studioで見つかりません。モデルがLM Studioにロードされていることを確認してください。",
"hostNotFound": "{{baseUrl}}のLM Studioに接続できません。ベースURLの構成を確認してください。"
},
"scanner": {
"unknownErrorProcessingFile": "ファイル{{filePath}}の処理中に不明なエラーが発生しました",
"unknownErrorDeletingPoints": "{{filePath}}のポイント削除中に不明なエラーが発生しました",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama 모델은 임베딩이 불가능합니다: {{modelId}}",
"hostNotFound": "Ollama 호스트를 찾을 수 없습니다: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studio 서버가 {{baseUrl}}에서 실행되고 있지 않습니다. 로컬 서버가 활성화된 상태로 LM Studio가 실행 중인지 확인하세요.",
"modelNotFound": "LM Studio에서 \"{{modelId}}\" 모델을 찾을 수 없습니다. 모델이 LM Studio에 로드되었는지 확인하세요.",
"hostNotFound": "{{baseUrl}}에서 LM Studio에 연결할 수 없습니다. 기본 URL 구성을 확인하세요."
},
"scanner": {
"unknownErrorProcessingFile": "파일 {{filePath}} 처리 중 알 수 없는 오류",
"unknownErrorDeletingPoints": "{{filePath}}의 포인트 삭제 중 알 수 없는 오류",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama-model is niet in staat tot insluiten: {{modelId}}",
"hostNotFound": "Ollama-host niet gevonden: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studio-server draait niet op {{baseUrl}}. Zorg ervoor dat LM Studio draait met de lokale server ingeschakeld.",
"modelNotFound": "Model \"{{modelId}}\" niet gevonden in LM Studio. Zorg ervoor dat het model in LM Studio is geladen.",
"hostNotFound": "Kan geen verbinding maken met LM Studio op {{baseUrl}}. Controleer de basis-URL-configuratie."
},
"scanner": {
"unknownErrorProcessingFile": "Onbekende fout bij verwerken van bestand {{filePath}}",
"unknownErrorDeletingPoints": "Onbekende fout bij verwijderen van punten voor {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Model Ollama nie jest zdolny do osadzania: {{modelId}}",
"hostNotFound": "Nie znaleziono hosta Ollama: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Serwer LM Studio nie działa pod adresem {{baseUrl}}. Upewnij się, że LM Studio jest uruchomione z włączonym serwerem lokalnym.",
"modelNotFound": "Nie znaleziono modelu \"{{modelId}}\" w LM Studio. Upewnij się, że model jest załadowany w LM Studio.",
"hostNotFound": "Nie można połączyć się z LM Studio pod adresem {{baseUrl}}. Sprawdź konfigurację podstawowego adresu URL."
},
"scanner": {
"unknownErrorProcessingFile": "Nieznany błąd podczas przetwarzania pliku {{filePath}}",
"unknownErrorDeletingPoints": "Nieznany błąd podczas usuwania punktów dla {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "O modelo Ollama não é capaz de embedding: {{modelId}}",
"hostNotFound": "Host Ollama não encontrado: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "O servidor do LM Studio não está em execução em {{baseUrl}}. Certifique-se de que o LM Studio esteja em execução com o servidor local ativado.",
"modelNotFound": "Modelo \"{{modelId}}\" não encontrado no LM Studio. Certifique-se de que o modelo esteja carregado no LM Studio.",
"hostNotFound": "Não é possível conectar-se ao LM Studio em {{baseUrl}}. Verifique a configuração do URL base."
},
"scanner": {
"unknownErrorProcessingFile": "Erro desconhecido ao processar arquivo {{filePath}}",
"unknownErrorDeletingPoints": "Erro desconhecido ao deletar pontos para {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Модель Ollama не способна к вложению: {{modelId}}",
"hostNotFound": "Хост Ollama не найден: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Сервер LM Studio не запущен по адресу {{baseUrl}}. Убедитесь, что LM Studio запущен с включенным локальным сервером.",
"modelNotFound": "Модель \"{{modelId}}\" не найдена в LM Studio. Убедитесь, что модель загружена в LM Studio.",
"hostNotFound": "Не удается подключиться к LM Studio по адресу {{baseUrl}}. Проверьте конфигурацию базового URL-адреса."
},
"scanner": {
"unknownErrorProcessingFile": "Неизвестная ошибка при обработке файла {{filePath}}",
"unknownErrorDeletingPoints": "Неизвестная ошибка при удалении точек для {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama modeli gömme yeteneğine sahip değil: {{modelId}}",
"hostNotFound": "Ollama ana bilgisayarı bulunamadı: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studio sunucusu {{baseUrl}} adresinde çalışmıyor. Lütfen LM Studio'nun yerel sunucu etkinken çalıştığından emin olun.",
"modelNotFound": "LM Studio'da \"{{modelId}}\" modeli bulunamadı. Lütfen modelin LM Studio'da yüklü olduğundan emin olun.",
"hostNotFound": "LM Studio'ya {{baseUrl}} adresinden bağlanılamıyor. Lütfen temel URL yapılandırmasını kontrol edin."
},
"scanner": {
"unknownErrorProcessingFile": "{{filePath}} dosyası işlenirken bilinmeyen hata",
"unknownErrorDeletingPoints": "{{filePath}} için noktalar silinirken bilinmeyen hata",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Mô hình Ollama không có khả năng nhúng: {{modelId}}",
"hostNotFound": "Không tìm thấy máy chủ Ollama: {{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "Máy chủ LM Studio không chạy tại {{baseUrl}}. Vui lòng đảm bảo LM Studio đang chạy với máy chủ cục bộ được bật.",
"modelNotFound": "Không tìm thấy mô hình \"{{modelId}}\" trong LM Studio. Vui lòng đảm bảo mô hình đã được tải trong LM Studio.",
"hostNotFound": "Không thể kết nối với LM Studio tại {{baseUrl}}. Vui lòng kiểm tra cấu hình URL cơ sở."
},
"scanner": {
"unknownErrorProcessingFile": "Lỗi không xác định khi xử lý tệp {{filePath}}",
"unknownErrorDeletingPoints": "Lỗi không xác định khi xóa điểm cho {{filePath}}",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama 模型不具备嵌入能力:{{modelId}}",
"hostNotFound": "未找到 Ollama 主机:{{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studio 服务器未在 {{baseUrl}} 运行。请确保 LM Studio 已启用本地服务器并正在运行。",
"modelNotFound": "在 LM Studio 中未找到模型“{{modelId}}”。请确保该模型已在 LM Studio 中加载。",
"hostNotFound": "无法连接到 {{baseUrl}} 上的 LM Studio。请检查基本 URL 配置。"
},
"scanner": {
"unknownErrorProcessingFile": "处理文件 {{filePath}} 时出现未知错误",
"unknownErrorDeletingPoints": "删除 {{filePath}} 的数据点时出现未知错误",

View file

@ -17,6 +17,11 @@
"modelNotEmbeddingCapable": "Ollama 模型不具備內嵌能力:{{modelId}}",
"hostNotFound": "找不到 Ollama 主機:{{baseUrl}}"
},
"lmstudio": {
"serviceNotRunning": "LM Studio 伺服器未在 {{baseUrl}} 執行。請確保 LM Studio 已啟用本機伺服器並正在執行。",
"modelNotFound": "在 LM Studio 中找不到模型「{{modelId}}」。請確保該模型已在 LM Studio 中載入。",
"hostNotFound": "無法連線至 {{baseUrl}} 上的 LM Studio。請檢查基礎 URL 設定。"
},
"scanner": {
"unknownErrorProcessingFile": "處理檔案 {{filePath}} 時發生未知錯誤",
"unknownErrorDeletingPoints": "刪除 {{filePath}} 的資料點時發生未知錯誤",

View file

@ -0,0 +1,210 @@
import { vitest, describe, it, expect, beforeEach, afterEach } from "vitest"
import type { MockedFunction } from "vitest"
import { CodeIndexLmStudioEmbedder } from "../lmstudio"
import { OpenAI } from "openai"
vitest.mock("openai", () => {
const mockEmbeddingsCreate = vitest.fn()
return {
OpenAI: vitest.fn().mockImplementation(() => ({
embeddings: {
create: mockEmbeddingsCreate,
},
})),
}
})
const consoleMocks = {
error: vitest.spyOn(console, "error").mockImplementation(() => {}),
warn: vitest.spyOn(console, "warn").mockImplementation(() => {}),
}
describe("CodeIndexLmStudioEmbedder", () => {
let embedder: CodeIndexLmStudioEmbedder
let mockEmbeddingsCreate: MockedFunction<any>
beforeEach(() => {
vitest.clearAllMocks()
consoleMocks.error.mockClear()
consoleMocks.warn.mockClear()
const MockedOpenAI = OpenAI as any
mockEmbeddingsCreate = vitest.fn()
MockedOpenAI.mockImplementation(() => ({
embeddings: {
create: mockEmbeddingsCreate,
},
}))
embedder = new CodeIndexLmStudioEmbedder({
lmStudioBaseUrl: "http://localhost:1234",
embeddingModelId: "text-embedding-nomic-embed-text-v1.5@f16",
})
})
afterEach(() => {
vitest.clearAllMocks()
})
describe("constructor", () => {
it("should initialize with provided options", () => {
expect(embedder.embedderInfo.name).toBe("lmstudio")
})
it("should use default values when not provided", () => {
const embedderWithDefaults = new CodeIndexLmStudioEmbedder({})
expect(embedderWithDefaults.embedderInfo.name).toBe("lmstudio")
})
it("should normalize base URL to include /v1", () => {
const embedderWithoutV1 = new CodeIndexLmStudioEmbedder({
lmStudioBaseUrl: "http://localhost:1234",
})
expect(embedderWithoutV1.embedderInfo.name).toBe("lmstudio")
const embedderWithV1 = new CodeIndexLmStudioEmbedder({
lmStudioBaseUrl: "http://localhost:1234/v1",
})
expect(embedderWithV1.embedderInfo.name).toBe("lmstudio")
})
})
describe("validateConfiguration", () => {
it("should validate successfully with valid configuration", async () => {
const mockResponse = {
data: [{ embedding: [0.1, 0.2, 0.3] }],
usage: { prompt_tokens: 2, total_tokens: 2 },
}
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(true)
expect(result.error).toBeUndefined()
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["test"],
model: "text-embedding-nomic-embed-text-v1.5@f16",
encoding_format: "float",
})
})
it("should fail validation when response has no data", async () => {
const mockResponse = {
data: [],
usage: { prompt_tokens: 0, total_tokens: 0 },
}
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("embeddings:validation.invalidResponse")
})
it("should fail validation when LM Studio is not running (ECONNREFUSED)", async () => {
const error = new Error("ECONNREFUSED")
;(error as any).code = "ECONNREFUSED"
mockEmbeddingsCreate.mockRejectedValue(error)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("lmstudio.serviceNotRunning")
})
it("should fail validation when model is not found (404)", async () => {
const error = new Error("HTTP 404: Not Found")
;(error as any).status = 404
mockEmbeddingsCreate.mockRejectedValue(error)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("lmstudio.modelNotFound")
})
it("should fail validation when host is not found (ENOTFOUND)", async () => {
const error = new Error("ENOTFOUND")
;(error as any).code = "ENOTFOUND"
mockEmbeddingsCreate.mockRejectedValue(error)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("lmstudio.hostNotFound")
})
it("should handle generic errors with standard error handling", async () => {
const error = new Error("Unknown error")
mockEmbeddingsCreate.mockRejectedValue(error)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("embeddings:validation.configurationError")
})
it("should handle fetch failed errors", async () => {
const error = new Error("fetch failed")
mockEmbeddingsCreate.mockRejectedValue(error)
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("lmstudio.serviceNotRunning")
})
})
describe("createEmbeddings", () => {
it("should create embeddings successfully", async () => {
const mockResponse = {
data: [{ embedding: [0.1, 0.2, 0.3] }, { embedding: [0.4, 0.5, 0.6] }],
usage: { prompt_tokens: 10, total_tokens: 10 },
}
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
const result = await embedder.createEmbeddings(["test1", "test2"])
expect(result.embeddings).toEqual([
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
])
expect(result.usage).toEqual({ promptTokens: 10, totalTokens: 10 })
})
it("should handle rate limit errors with retry", async () => {
const error = new Error("Rate limit exceeded")
;(error as any).status = 429
mockEmbeddingsCreate.mockRejectedValueOnce(error)
const mockResponse = {
data: [{ embedding: [0.1, 0.2, 0.3] }],
usage: { prompt_tokens: 5, total_tokens: 5 },
}
mockEmbeddingsCreate.mockResolvedValueOnce(mockResponse)
const result = await embedder.createEmbeddings(["test"])
expect(result.embeddings).toEqual([[0.1, 0.2, 0.3]])
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(2)
})
it("should skip texts that exceed token limit", async () => {
const longText = "a".repeat(100000)
const shortText = "short text"
const mockResponse = {
data: [{ embedding: [0.1, 0.2, 0.3] }],
usage: { prompt_tokens: 3, total_tokens: 3 },
}
mockEmbeddingsCreate.mockResolvedValue(mockResponse)
const result = await embedder.createEmbeddings([longText, shortText])
expect(result.embeddings).toEqual([[0.1, 0.2, 0.3]])
expect(consoleMocks.warn).toHaveBeenCalledWith(expect.stringContaining("exceeds maximum token limit"))
})
})
})

View file

@ -7,6 +7,8 @@ import {
MAX_BATCH_RETRIES as MAX_RETRIES,
INITIAL_RETRY_DELAY_MS as INITIAL_DELAY_MS,
} from "../constants"
import { withValidationErrorHandling, formatEmbeddingError, HttpError } from "../shared/validation-helpers"
import { t } from "../../../i18n"
/**
* LM Studio implementation of the embedder interface with batching and rate limiting.
@ -81,19 +83,10 @@ export class CodeIndexLmStudioEmbedder implements IEmbedder {
}
if (currentBatch.length > 0) {
try {
const batchResult = await this._embedBatchWithRetries(currentBatch, modelToUse)
allEmbeddings.push(...batchResult.embeddings)
usage.promptTokens += batchResult.usage.promptTokens
usage.totalTokens += batchResult.usage.totalTokens
} catch (error) {
const batchInfo = `batch of ${currentBatch.length} documents (indices: ${processedIndices.join(", ")})`
console.error(`Failed to process ${batchInfo}:`, error)
throw new Error(
`Failed to create embeddings for ${batchInfo}: ${error instanceof Error ? error.message : "batch processing error"}`,
)
}
const batchResult = await this._embedBatchWithRetries(currentBatch, modelToUse)
allEmbeddings.push(...batchResult.embeddings)
usage.promptTokens += batchResult.usage.promptTokens
usage.totalTokens += batchResult.usage.totalTokens
}
}
@ -115,7 +108,6 @@ export class CodeIndexLmStudioEmbedder implements IEmbedder {
const response = await this.embeddingsClient.embeddings.create({
input: batchTexts,
model: model,
encoding_format: "float",
})
return {
@ -126,10 +118,11 @@ export class CodeIndexLmStudioEmbedder implements IEmbedder {
},
}
} catch (error: any) {
const isRateLimitError = error?.status === 429
const hasMoreAttempts = attempts < MAX_RETRIES - 1
if (isRateLimitError && hasMoreAttempts) {
// Check if it's a rate limit error
const httpError = error as HttpError
if (httpError?.status === 429 && hasMoreAttempts) {
const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts)
await new Promise((resolve) => setTimeout(resolve, delayMs))
continue
@ -139,7 +132,86 @@ export class CodeIndexLmStudioEmbedder implements IEmbedder {
}
}
throw new Error(`Failed to create embeddings after ${MAX_RETRIES} attempts`)
throw new Error(t("embeddings:failedMaxAttempts", { attempts: MAX_RETRIES }))
}
/**
* Validates the LM Studio embedder configuration by testing connectivity and model availability
* @returns Promise resolving to validation result with success status and optional error message
*/
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
return withValidationErrorHandling(
async () => {
// Test with a minimal embedding request
const testTexts = ["test"]
const modelToUse = this.defaultModelId
try {
const response = await this.embeddingsClient.embeddings.create({
input: testTexts,
model: modelToUse,
})
// Check if we got a valid response
if (!response.data || response.data.length === 0) {
return {
valid: false,
error: t("embeddings:validation.invalidResponse"),
}
}
return { valid: true }
} catch (error: any) {
// Handle LM Studio specific errors
if (error?.message?.includes("ECONNREFUSED") || error?.code === "ECONNREFUSED") {
return {
valid: false,
error: t("embeddings:lmstudio.serviceNotRunning", {
baseUrl: this.options.lmStudioBaseUrl,
}),
}
}
if (error?.status === 404 || error?.message?.includes("404")) {
return {
valid: false,
error: t("embeddings:lmstudio.modelNotFound", { modelId: modelToUse }),
}
}
// Re-throw to let standard error handling take over
throw error
}
},
"lmstudio",
{
beforeStandardHandling: (error: any) => {
// Handle LM Studio-specific connection errors
if (
error?.message?.includes("fetch failed") ||
error?.code === "ECONNREFUSED" ||
error?.message?.includes("ECONNREFUSED")
) {
return {
valid: false,
error: t("embeddings:lmstudio.serviceNotRunning", {
baseUrl: this.options.lmStudioBaseUrl,
}),
}
}
if (error?.code === "ENOTFOUND" || error?.message?.includes("ENOTFOUND")) {
return {
valid: false,
error: t("embeddings:lmstudio.hostNotFound", { baseUrl: this.options.lmStudioBaseUrl }),
}
}
// Let standard handling take over
return undefined
},
},
)
}
get embedderInfo(): EmbedderInfo {