Fix EmbeddingService
This commit is contained in:
@@ -279,7 +279,7 @@
|
|||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 26.2;
|
MACOSX_DEPLOYMENT_TARGET = 26.2;
|
||||||
MARKETING_VERSION = 2.3.4;
|
MARKETING_VERSION = 2.3.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.oai.oAI;
|
PRODUCT_BUNDLE_IDENTIFIER = com.oai.oAI;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -323,7 +323,7 @@
|
|||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 26.2;
|
MACOSX_DEPLOYMENT_TARGET = 26.2;
|
||||||
MARKETING_VERSION = 2.3.4;
|
MARKETING_VERSION = 2.3.5;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.oai.oAI;
|
PRODUCT_BUNDLE_IDENTIFIER = com.oai.oAI;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
|||||||
@@ -75,6 +75,16 @@ final class EmbeddingService {
|
|||||||
|
|
||||||
private let settings = SettingsService.shared
|
private let settings = SettingsService.shared
|
||||||
|
|
||||||
|
/// Dedicated session for embedding requests — keeps embedding traffic isolated
|
||||||
|
/// from the chat API sessions and self-limits concurrent connections.
|
||||||
|
private let session: URLSession = {
|
||||||
|
let config = URLSessionConfiguration.default
|
||||||
|
config.httpMaximumConnectionsPerHost = 2 // max 2 concurrent embedding requests
|
||||||
|
config.timeoutIntervalForRequest = 60
|
||||||
|
config.timeoutIntervalForResource = 120
|
||||||
|
return URLSession(configuration: config)
|
||||||
|
}()
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
// MARK: - Provider Detection
|
// MARK: - Provider Detection
|
||||||
@@ -164,7 +174,7 @@ final class EmbeddingService {
|
|||||||
]
|
]
|
||||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||||
|
|
||||||
let (data, response) = try await URLSession.shared.data(for: request)
|
let (data, response) = try await session.data(for: request)
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
throw EmbeddingError.invalidResponse
|
throw EmbeddingError.invalidResponse
|
||||||
@@ -205,7 +215,7 @@ final class EmbeddingService {
|
|||||||
]
|
]
|
||||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||||
|
|
||||||
let (data, response) = try await URLSession.shared.data(for: request)
|
let (data, response) = try await session.data(for: request)
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
throw EmbeddingError.invalidResponse
|
throw EmbeddingError.invalidResponse
|
||||||
@@ -247,7 +257,7 @@ final class EmbeddingService {
|
|||||||
]
|
]
|
||||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||||
|
|
||||||
let (data, response) = try await URLSession.shared.data(for: request)
|
let (data, response) = try await session.data(for: request)
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
throw EmbeddingError.invalidResponse
|
throw EmbeddingError.invalidResponse
|
||||||
|
|||||||
@@ -1793,16 +1793,15 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
|||||||
await checkAndSummarizeOldMessages(conversationId: conversation.id)
|
await checkAndSummarizeOldMessages(conversationId: conversation.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate embeddings for messages that don't have them yet
|
// Generate embeddings for messages that don't have them yet.
|
||||||
|
// Run sequentially at background priority so this never blocks the chat.
|
||||||
if settings.embeddingsEnabled {
|
if settings.embeddingsEnabled {
|
||||||
Task {
|
Task(priority: .background) {
|
||||||
|
guard let provider = EmbeddingService.shared.getSelectedProvider() else { return }
|
||||||
for message in chatMessages {
|
for message in chatMessages {
|
||||||
// Skip if already embedded
|
await embedMessage(message, provider: provider)
|
||||||
if let _ = try? EmbeddingService.shared.getMessageEmbedding(messageId: message.id) {
|
// Yield briefly between requests to avoid bursting the API
|
||||||
continue
|
try? await Task.sleep(for: .milliseconds(150))
|
||||||
}
|
|
||||||
// Generate embedding (this will now succeed since message is in DB)
|
|
||||||
generateEmbeddingForMessage(message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1927,38 +1926,44 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
|||||||
|
|
||||||
// MARK: - Embedding Generation
|
// MARK: - Embedding Generation
|
||||||
|
|
||||||
/// Generate embedding for a message in the background
|
/// Generate embedding for a single message (awaitable, no Task spawned).
|
||||||
|
/// Call this directly when you want to sequence or rate-limit requests.
|
||||||
|
private func embedMessage(_ message: Message, provider: EmbeddingProvider) async {
|
||||||
|
guard message.content.count > 20 else { return }
|
||||||
|
// Skip if already embedded
|
||||||
|
if let _ = try? EmbeddingService.shared.getMessageEmbedding(messageId: message.id) { return }
|
||||||
|
do {
|
||||||
|
let embedding = try await EmbeddingService.shared.generateEmbedding(
|
||||||
|
text: message.content,
|
||||||
|
provider: provider
|
||||||
|
)
|
||||||
|
try EmbeddingService.shared.saveMessageEmbedding(
|
||||||
|
messageId: message.id,
|
||||||
|
embedding: embedding,
|
||||||
|
model: provider.defaultModel
|
||||||
|
)
|
||||||
|
Log.api.info("Generated embedding for message \(message.id) using \(provider.displayName)")
|
||||||
|
} catch {
|
||||||
|
let errorString = String(describing: error)
|
||||||
|
if errorString.contains("FOREIGN KEY constraint failed") {
|
||||||
|
Log.api.debug("Message \(message.id) not in database yet - will embed later during save or batch operation")
|
||||||
|
} else {
|
||||||
|
Log.api.error("Failed to generate embedding for message \(message.id): \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire-and-forget wrapper — runs at background priority so it never blocks the chat.
|
||||||
func generateEmbeddingForMessage(_ message: Message) {
|
func generateEmbeddingForMessage(_ message: Message) {
|
||||||
guard settings.embeddingsEnabled else { return }
|
guard settings.embeddingsEnabled else { return }
|
||||||
guard message.content.count > 20 else { return } // Skip very short messages
|
guard message.content.count > 20 else { return }
|
||||||
|
|
||||||
Task {
|
Task(priority: .background) {
|
||||||
do {
|
guard let provider = EmbeddingService.shared.getSelectedProvider() else {
|
||||||
// Use user's selected provider, or fall back to best available
|
Log.api.warning("No embedding providers available - skipping embedding generation")
|
||||||
guard let provider = EmbeddingService.shared.getSelectedProvider() else {
|
return
|
||||||
Log.api.warning("No embedding providers available - skipping embedding generation")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let embedding = try await EmbeddingService.shared.generateEmbedding(
|
|
||||||
text: message.content,
|
|
||||||
provider: provider
|
|
||||||
)
|
|
||||||
try EmbeddingService.shared.saveMessageEmbedding(
|
|
||||||
messageId: message.id,
|
|
||||||
embedding: embedding,
|
|
||||||
model: provider.defaultModel
|
|
||||||
)
|
|
||||||
Log.api.info("Generated embedding for message \(message.id) using \(provider.displayName)")
|
|
||||||
} catch {
|
|
||||||
// Check if it's a foreign key constraint error (message not saved to DB yet)
|
|
||||||
let errorString = String(describing: error)
|
|
||||||
if errorString.contains("FOREIGN KEY constraint failed") {
|
|
||||||
Log.api.debug("Message \(message.id) not in database yet - will embed later during save or batch operation")
|
|
||||||
} else {
|
|
||||||
Log.api.error("Failed to generate embedding for message \(message.id): \(error)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
await embedMessage(message, provider: provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user