From 3196c33ebb37cff423b50f6a3f262d3a91088b0d Mon Sep 17 00:00:00 2001 From: davegilligan Date: Fri, 15 May 2026 11:30:25 +0200 Subject: [PATCH] fix: replace AiGateway.embedBatch with direct LiteLLM cURL for upload indexing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AiGateway uses getenv(LITELLM_MASTER_KEY) + stream_context HTTP which was failing on the chloe virtualhost process. New dbnToolsLiteLLMEmbedBatch() helper mirrors dbnToolsCallGpuLlm — hardcoded URL + key, cURL-first, same pattern already proven for LLM calls. Removes AiGateway dependency from DeepResearchAgent entirely. Co-Authored-By: Claude Opus 4.7 --- includes/DeepResearchAgent.php | 7 +--- includes/bootstrap.php | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/includes/DeepResearchAgent.php b/includes/DeepResearchAgent.php index 2a1ed99..44022f9 100644 --- a/includes/DeepResearchAgent.php +++ b/includes/DeepResearchAgent.php @@ -14,7 +14,6 @@ final class DbnDeepResearchAgent private const POOL_CAP = 30; private DbnAzureOpenAiGateway $azure; - private ?AiGateway $ai = null; private array $uploadVecs = []; private array $stepTimings = []; @@ -50,9 +49,7 @@ final class DbnDeepResearchAgent dbnToolsBootCaveau(); $aiPortalRoot = dbnToolsAiPortalRoot(); require_once $aiPortalRoot . '/platform/includes/dbn_v6.php'; - require_once $aiPortalRoot . '/lib/ai/AiGateway.php'; - $this->ai = new AiGateway(); $this->uploadVecs = []; $this->stepTimings = []; @@ -141,7 +138,7 @@ final class DbnDeepResearchAgent if ($uploadChunks) { try { $texts = array_map(fn(array $c) => $c['text'], $uploadChunks); - $vecs = $this->ai->embedBatch($texts, 'nomic-embed-text'); + $vecs = dbnToolsLiteLLMEmbedBatch($texts); if (count($vecs) === count($uploadChunks)) { foreach ($uploadChunks as $i => $chunk) { $this->uploadVecs[] = [ @@ -531,7 +528,7 @@ PROMPT; return []; } try { - $qVec = $this->ai->embed($question, 'nomic-embed-text'); + $qVec = dbnToolsLiteLLMEmbedBatch([$question])[0] ?? []; } catch (Throwable $e) { error_log('DBN deep research sub-Q embed failed: ' . $e->getMessage()); return []; diff --git a/includes/bootstrap.php b/includes/bootstrap.php index 6df79aa..030900a 100644 --- a/includes/bootstrap.php +++ b/includes/bootstrap.php @@ -676,3 +676,72 @@ function dbnToolsCallGpuLlm(array $messages, array $options = []): array } return $decoded; } + +/** + * Batch-embed texts via LiteLLM /v1/embeddings using cURL. + * Returns an array of float[] indexed by input position. + */ +function dbnToolsLiteLLMEmbedBatch(array $texts, string $model = 'nomic-embed-text', int $timeout = 60): array +{ + if (empty($texts)) { + return []; + } + + $url = 'http://10.0.1.10:4000/v1/embeddings'; + $apiKey = (string)(dbnToolsEnv('LITELLM_MASTER_KEY') ?: 'sk-bnl-litellm-26xR9mK4qvN3wL8sTj7pB2d'); + + $payload = json_encode(['model' => $model, 'input' => array_values($texts)], JSON_UNESCAPED_UNICODE); + $headers = [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $apiKey, + ]; + + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => $timeout, + ]); + $response = curl_exec($ch); + $code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + $err = curl_error($ch); + curl_close($ch); + + if ($response === false) { + throw new RuntimeException('LiteLLM embed request failed: ' . $err); + } + } else { + $ctx = stream_context_create(['http' => [ + 'method' => 'POST', + 'header' => implode("\r\n", $headers), + 'content' => $payload, + 'timeout' => $timeout, + 'ignore_errors' => true, + ]]); + $response = @file_get_contents($url, false, $ctx); + $code = 0; + if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) { + $code = (int)$m[1]; + } + if ($response === false) { + throw new RuntimeException('LiteLLM embed request failed.'); + } + } + + $decoded = json_decode($response, true); + if (!is_array($decoded)) { + throw new RuntimeException('LiteLLM embed returned non-JSON.'); + } + if ($code < 200 || $code >= 300) { + $msg = $decoded['error']['message'] ?? ('HTTP ' . $code); + throw new RuntimeException('LiteLLM embed error: ' . $msg); + } + + // OpenAI /v1/embeddings returns data[].embedding ordered by index + $data = $decoded['data'] ?? []; + usort($data, fn($a, $b) => ($a['index'] ?? 0) <=> ($b['index'] ?? 0)); + return array_map(fn($d) => $d['embedding'], $data); +}