feat: replace Mistral web_search with Tavily for specs research
- Add WebSearchInterface + TavilyWebSearch (POST /search, max 5 results)
- SpecsResearchAgent now fetches search results first, injects them as
{{searchResults}} context into the prompt, then calls plain generate()
— no dependency on model-specific web_search tool support
- Update specs_research prompt template (PHP default + DB migration) to
use the new {{searchResults}} variable
- Wire TAVILY_API_KEY env var; register TavilyWebSearch in services.yaml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
00dc232426
commit
cfb5cc4ad0
7 changed files with 133 additions and 5 deletions
2
.env
2
.env
|
|
@ -16,6 +16,8 @@ OLLAMA_BASE_URL=http://172.18.0.1:11434
|
|||
OLLAMA_VISION_MODEL=llava
|
||||
OLLAMA_TEXT_MODEL=llama3.2
|
||||
|
||||
TAVILY_API_KEY=
|
||||
|
||||
MISTRAL_BASE_URL=https://api.mistral.ai
|
||||
MISTRAL_API_KEY=
|
||||
# Vision requires a Pixtral model, e.g. pixtral-12b-2409
|
||||
|
|
|
|||
|
|
@ -82,6 +82,13 @@ services:
|
|||
arguments:
|
||||
$model: '%env(AI_TEXT_MODEL)%'
|
||||
|
||||
App\Infrastructure\Search\WebSearchInterface:
|
||||
alias: App\Infrastructure\Search\TavilyWebSearch
|
||||
|
||||
App\Infrastructure\Search\TavilyWebSearch:
|
||||
arguments:
|
||||
$apiKey: '%env(TAVILY_API_KEY)%'
|
||||
|
||||
App\Infrastructure\AI\Agent\JsonCodingAgent:
|
||||
arguments:
|
||||
$model: '%env(AI_TEXT_MODEL)%'
|
||||
|
|
|
|||
51
migrations/Version20260520020000.php
Normal file
51
migrations/Version20260520020000.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260520020000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Update specs_research prompt to include {{searchResults}} from Tavily';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$body = <<<'PROMPT'
|
||||
You are a hardware specifications expert. Extract the technical specifications for the {{articleType}}: "{{subject}}".
|
||||
|
||||
Web search results:
|
||||
{{searchResults}}
|
||||
|
||||
Based on the search results above, list all technical specifications including:
|
||||
processor, RAM, storage variants, display size and resolution, GPU, battery capacity,
|
||||
ports, connectivity, weight, dimensions, OS, and any other relevant specs.
|
||||
Be specific and accurate. If a spec is not found in the search results, omit it rather than guessing.
|
||||
PROMPT;
|
||||
|
||||
$this->addSql(
|
||||
"UPDATE app.prompt_templates SET body = :body WHERE key = 'specs_research'",
|
||||
['body' => $body],
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$body = <<<'PROMPT'
|
||||
List all known technical specifications for the {{articleType}}: "{{subject}}".
|
||||
Include: processor, RAM, storage variants, display size and resolution, GPU, battery capacity,
|
||||
ports, connectivity, weight, dimensions, OS, and any other relevant specs.
|
||||
If you know this device, be specific and complete. If it is unknown, say so explicitly.
|
||||
PROMPT;
|
||||
|
||||
$this->addSql(
|
||||
"UPDATE app.prompt_templates SET body = :body WHERE key = 'specs_research'",
|
||||
['body' => $body],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,14 @@ namespace App\Infrastructure\AI\Agent;
|
|||
|
||||
use App\Infrastructure\AI\OllamaClientInterface;
|
||||
use App\Infrastructure\AI\PromptTemplateService;
|
||||
use App\Infrastructure\Search\WebSearchInterface;
|
||||
|
||||
final class SpecsResearchAgent
|
||||
{
|
||||
public function __construct(
|
||||
private readonly OllamaClientInterface $client,
|
||||
private readonly PromptTemplateService $prompts,
|
||||
private readonly WebSearchInterface $search,
|
||||
private readonly string $model,
|
||||
) {
|
||||
}
|
||||
|
|
@ -20,12 +22,15 @@ final class SpecsResearchAgent
|
|||
{
|
||||
$subject = trim(($manufacturer !== '' ? $manufacturer.' ' : '').$modelName);
|
||||
|
||||
$searchResults = $this->search->search("{$subject} {$articleTypeName} specifications");
|
||||
|
||||
$prompt = $this->prompts->render('specs_research', [
|
||||
'articleType' => $articleTypeName,
|
||||
'subject' => $subject,
|
||||
'searchResults' => $searchResults !== '' ? $searchResults : 'No web results available.',
|
||||
]);
|
||||
|
||||
$result = $this->client->generateWithWebSearch($this->model, $prompt);
|
||||
$result = $this->client->generate($this->model, $prompt);
|
||||
|
||||
if ('' === trim($result)) {
|
||||
throw new \RuntimeException("No specifications found for model: {$modelName}");
|
||||
|
|
|
|||
|
|
@ -11,10 +11,15 @@ final class PromptTemplateService
|
|||
/** @var array<string, string> */
|
||||
private const array DEFAULTS = [
|
||||
'specs_research' => <<<'PROMPT'
|
||||
List all known technical specifications for the {{articleType}}: "{{subject}}".
|
||||
Include: processor, RAM, storage variants, display size and resolution, GPU, battery capacity,
|
||||
You are a hardware specifications expert. Extract the technical specifications for the {{articleType}}: "{{subject}}".
|
||||
|
||||
Web search results:
|
||||
{{searchResults}}
|
||||
|
||||
Based on the search results above, list all technical specifications including:
|
||||
processor, RAM, storage variants, display size and resolution, GPU, battery capacity,
|
||||
ports, connectivity, weight, dimensions, OS, and any other relevant specs.
|
||||
If you know this device, be specific and complete. If it is unknown, say so explicitly.
|
||||
Be specific and accurate. If a spec is not found in the search results, omit it rather than guessing.
|
||||
PROMPT,
|
||||
|
||||
'ebay_title' => <<<'PROMPT'
|
||||
|
|
@ -89,7 +94,7 @@ PROMPT,
|
|||
public static function knownKeys(): array
|
||||
{
|
||||
return [
|
||||
'specs_research' => ['articleType', 'subject'],
|
||||
'specs_research' => ['articleType', 'subject', 'searchResults'],
|
||||
'ebay_title' => ['typeName', 'deviceLabel', 'condition', 'specsSection'],
|
||||
'ebay_description' => ['typeName', 'deviceLabel', 'condition', 'conditionNotes', 'specsSection'],
|
||||
'vision_analyze' => [],
|
||||
|
|
|
|||
45
src/Infrastructure/Search/TavilyWebSearch.php
Normal file
45
src/Infrastructure/Search/TavilyWebSearch.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Search;
|
||||
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
final class TavilyWebSearch implements WebSearchInterface
|
||||
{
|
||||
private const string API_URL = 'https://api.tavily.com/search';
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly string $apiKey,
|
||||
) {
|
||||
}
|
||||
|
||||
public function search(string $query): string
|
||||
{
|
||||
$response = $this->httpClient->request('POST', self::API_URL, [
|
||||
'json' => [
|
||||
'api_key' => $this->apiKey,
|
||||
'query' => $query,
|
||||
'max_results' => 5,
|
||||
'include_raw_content' => false,
|
||||
],
|
||||
'timeout' => 30,
|
||||
]);
|
||||
|
||||
/** @var array{results: list<array{title: string, url: string, content: string}>} $data */
|
||||
$data = $response->toArray();
|
||||
|
||||
if (empty($data['results'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
foreach ($data['results'] as $result) {
|
||||
$parts[] = "### {$result['title']}\n{$result['content']}";
|
||||
}
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
}
|
||||
13
src/Infrastructure/Search/WebSearchInterface.php
Normal file
13
src/Infrastructure/Search/WebSearchInterface.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Search;
|
||||
|
||||
interface WebSearchInterface
|
||||
{
|
||||
/**
|
||||
* Run a web search and return the results as plain text suitable for use as AI context.
|
||||
*/
|
||||
public function search(string $query): string;
|
||||
}
|
||||
Loading…
Reference in a new issue