7e1ff09abe
AitunnelEmbedder (text-embedding-3-small) → косинус-похожесть конкурента на профиль клиента (%). AitunnelAggregatorClassifier (chat) → «поставщик или площадка?» — авто-отсев Авито/Zoon/Банки.ру. Оба деградируют сами при пустом ключе (пустые векторы → 0%, null → не выкидываем), поэтому подключены всегда. Ключ — только в .env (services.aitunnel.key), в гит не попадает. Тесты: AITUNNEL клиенты 9/9; Автоподбор unit+feature 182/182 (флаг real_find в тестах принудительно ВЫКЛ через phpunit.xml — иначе findCompetitors ходил бы в сеть); Pint чисто. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
1.6 KiB
PHP
40 lines
1.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Autopodbor\Agent\Similarity\AitunnelEmbedder;
|
|
use Illuminate\Http\Client\ConnectionException;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
it('эмбеддит тексты через AITUNNEL, сохраняя порядок', function () {
|
|
config(['services.aitunnel.key' => 'sk-test', 'services.aitunnel.base_url' => 'https://api.aitunnel.ru/v1']);
|
|
Http::fake(['api.aitunnel.ru/*' => Http::response([
|
|
'data' => [
|
|
['index' => 0, 'embedding' => [0.1, 0.2, 0.3]],
|
|
['index' => 1, 'embedding' => [0.9, 0.8, 0.7]],
|
|
],
|
|
], 200)]);
|
|
|
|
$vecs = app(AitunnelEmbedder::class)->embed(['ломбард', 'окна']);
|
|
|
|
expect($vecs)->toBe([[0.1, 0.2, 0.3], [0.9, 0.8, 0.7]]);
|
|
Http::assertSent(fn ($r) => str_contains($r->url(), '/embeddings')
|
|
&& $r->hasHeader('Authorization', 'Bearer sk-test')
|
|
&& $r['model'] === 'text-embedding-3-small');
|
|
});
|
|
|
|
it('без ключа → пустые векторы (движок отдаст 0% похожести)', function () {
|
|
config(['services.aitunnel.key' => '']);
|
|
expect(app(AitunnelEmbedder::class)->embed(['x', 'y']))->toBe([[], []]);
|
|
});
|
|
|
|
it('ошибка сети → пустые векторы, не падаем', function () {
|
|
config(['services.aitunnel.key' => 'sk']);
|
|
Http::fake(fn () => throw new ConnectionException('timeout'));
|
|
expect(app(AitunnelEmbedder::class)->embed(['x']))->toBe([[]]);
|
|
});
|
|
|
|
it('пустой вход → пустой выход', function () {
|
|
expect(app(AitunnelEmbedder::class)->embed([]))->toBe([]);
|
|
});
|