From b9907d6c6388068b9bb744ef11708434527734da Mon Sep 17 00:00:00 2001 From: Simon Kuehn Date: Mon, 18 May 2026 07:18:49 +0000 Subject: [PATCH] feat: replace SerpApi web search with Mistral native web_search tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MistralClient gains generateWithWebSearch() which uses Mistral's built-in web_search tool, handling the multi-turn tool-call loop server-side. SerpApiWebSearch and WebSearchInterface are removed — no longer needed. Co-Authored-By: Claude Sonnet 4.6 --- src/Infrastructure/AI/MistralClient.php | 53 +++++++++++++++++++ .../Search/SerpApiWebSearch.php | 48 ----------------- .../Search/WebSearchInterface.php | 10 ---- 3 files changed, 53 insertions(+), 58 deletions(-) delete mode 100644 src/Infrastructure/Search/SerpApiWebSearch.php delete mode 100644 src/Infrastructure/Search/WebSearchInterface.php diff --git a/src/Infrastructure/AI/MistralClient.php b/src/Infrastructure/AI/MistralClient.php index ca0701c..fca0a0a 100644 --- a/src/Infrastructure/AI/MistralClient.php +++ b/src/Infrastructure/AI/MistralClient.php @@ -66,6 +66,59 @@ final class MistralClient implements OllamaClientInterface return $data['choices'][0]['message']['content']; } + /** + * Chat completion with Mistral's built-in web_search tool enabled. + * Mistral executes searches server-side; we handle the multi-turn loop if needed. + */ + public function generateWithWebSearch(string $model, string $prompt): string + { + $messages = [['role' => 'user', 'content' => $prompt]]; + + $response = $this->httpClient->request('POST', $this->mistralBaseUrl.'/v1/chat/completions', [ + 'headers' => ['Authorization' => 'Bearer '.$this->mistralApiKey], + 'json' => [ + 'model' => $model, + 'tools' => [['type' => 'web_search']], + 'messages' => $messages, + ], + 'timeout' => 120, + ]); + + /** @var array{choices: array{0: array{finish_reason: string, message: array{content: ?string, tool_calls?: list}}}} $data */ + $data = $response->toArray(); + $choice = $data['choices'][0]; + + if ('tool_calls' !== $choice['finish_reason']) { + return (string) $choice['message']['content']; + } + + // Append assistant's tool call turn, then return empty tool results so + // Mistral's servers can complete the search and produce the final answer. + $messages[] = $choice['message']; + foreach ($choice['message']['tool_calls'] ?? [] as $toolCall) { + $messages[] = [ + 'role' => 'tool', + 'tool_call_id' => $toolCall['id'], + 'content' => '', + ]; + } + + $final = $this->httpClient->request('POST', $this->mistralBaseUrl.'/v1/chat/completions', [ + 'headers' => ['Authorization' => 'Bearer '.$this->mistralApiKey], + 'json' => [ + 'model' => $model, + 'tools' => [['type' => 'web_search']], + 'messages' => $messages, + ], + 'timeout' => 120, + ]); + + /** @var array{choices: array{0: array{message: array{content: string}}}} $finalData */ + $finalData = $final->toArray(); + + return $finalData['choices'][0]['message']['content']; + } + private function guessMimeType(string $path): string { return match (strtolower(pathinfo($path, PATHINFO_EXTENSION))) { diff --git a/src/Infrastructure/Search/SerpApiWebSearch.php b/src/Infrastructure/Search/SerpApiWebSearch.php deleted file mode 100644 index a9b6724..0000000 --- a/src/Infrastructure/Search/SerpApiWebSearch.php +++ /dev/null @@ -1,48 +0,0 @@ -httpClient->request('GET', 'https://serpapi.com/search', [ - 'query' => [ - 'q' => $query, - 'api_key' => $this->serpApiKey, - 'num' => 5, - 'hl' => 'de', - ], - 'timeout' => 15, - ]); - - /** @var array{organic_results?: list} $data */ - $data = $response->toArray(); - - $results = $data['organic_results'] ?? []; - if ([] === $results) { - return ''; - } - - $texts = []; - foreach ($results as $result) { - $title = $result['title'] ?? ''; - $snippet = $result['snippet'] ?? ''; - if ('' !== $title || '' !== $snippet) { - $texts[] = $title."\n".$snippet; - } - } - - return implode("\n\n", $texts); - } -} diff --git a/src/Infrastructure/Search/WebSearchInterface.php b/src/Infrastructure/Search/WebSearchInterface.php deleted file mode 100644 index 32e62b3..0000000 --- a/src/Infrastructure/Search/WebSearchInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -