feat: replace SerpApi web search with Mistral native web_search tool
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 <noreply@anthropic.com>
This commit is contained in:
parent
f3b018e048
commit
b9907d6c63
3 changed files with 53 additions and 58 deletions
|
|
@ -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<array{id: string, function: array{name: string, arguments: string}}>}}}} $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))) {
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Search;
|
||||
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
final class SerpApiWebSearch implements WebSearchInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly string $serpApiKey,
|
||||
) {
|
||||
}
|
||||
|
||||
public function search(string $query): string
|
||||
{
|
||||
$response = $this->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<array{title?: string, snippet?: string}>} $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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Search;
|
||||
|
||||
interface WebSearchInterface
|
||||
{
|
||||
public function search(string $query): string;
|
||||
}
|
||||
Loading…
Reference in a new issue