75 lines
3.2 KiB
PHP
75 lines
3.2 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
use App\Services\Autopodbor\Agent\Similarity\Embedder;
|
|||
|
|
use App\Services\Autopodbor\Agent\Similarity\EmbeddingRelevance;
|
|||
|
|
|
|||
|
|
/** Детерминированный эмбеддер для офлайн-теста: текст → заранее заданный вектор (по подстроке). */
|
|||
|
|
function fakeEmbedder(array $byNeedle): Embedder
|
|||
|
|
{
|
|||
|
|
return new class($byNeedle) implements Embedder
|
|||
|
|
{
|
|||
|
|
/** @param array<string,array<int,float>> $map */
|
|||
|
|
public function __construct(private array $map) {}
|
|||
|
|
|
|||
|
|
public function embed(array $texts): array
|
|||
|
|
{
|
|||
|
|
return array_map(function (string $t): array {
|
|||
|
|
foreach ($this->map as $needle => $vec) {
|
|||
|
|
if (str_contains($t, $needle)) {
|
|||
|
|
return $vec;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return [0.0, 0.0, 0.0]; // нет совпадения — нулевая похожесть
|
|||
|
|
}, $texts);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
it('ранжирует кандидатов по близости к профилю клиента (косинус)', function () {
|
|||
|
|
// профиль клиента — «автоломбард / займ под залог авто»
|
|||
|
|
$embedder = fakeEmbedder([
|
|||
|
|
'залог авто' => [1.0, 0.0, 0.0], // клиентский профиль и близкие к нему
|
|||
|
|
'автозайм' => [0.9, 0.1, 0.0],
|
|||
|
|
'пластиковые окна' => [0.0, 1.0, 0.0], // совсем другое
|
|||
|
|
]);
|
|||
|
|
$rel = new EmbeddingRelevance($embedder);
|
|||
|
|
|
|||
|
|
$ranked = $rel->rank(
|
|||
|
|
clientExamples: ['Автоломбард Клиента залог авто'],
|
|||
|
|
candidates: [
|
|||
|
|
['name' => 'Окна Комфорт', 'description' => 'пластиковые окна и балконы'],
|
|||
|
|
['name' => 'АвтоДеньги', 'description' => 'автозайм под ПТС'],
|
|||
|
|
['name' => 'ЗалогЦентр', 'description' => 'займ под залог авто'],
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ближайший — «займ под залог авто» (точное совпадение профиля), затем «автозайм», окна — последними
|
|||
|
|
expect(array_column($ranked, 'name'))->toBe(['ЗалогЦентр', 'АвтоДеньги', 'Окна Комфорт']);
|
|||
|
|
expect($ranked[0]['relevance_pct'])->toBe(100);
|
|||
|
|
expect($ranked[2]['relevance_pct'])->toBe(0);
|
|||
|
|
foreach ($ranked as $c) {
|
|||
|
|
expect($c['relevance_pct'])->toBeGreaterThanOrEqual(0)->toBeLessThanOrEqual(100);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('без профиля клиента похожесть 0, порядок сохраняется', function () {
|
|||
|
|
$rel = new EmbeddingRelevance(fakeEmbedder([]));
|
|||
|
|
|
|||
|
|
$ranked = $rel->rank(clientExamples: [], candidates: [
|
|||
|
|
['name' => 'А', 'description' => 'x'],
|
|||
|
|
['name' => 'Б', 'description' => 'y'],
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
expect($ranked)->toHaveCount(2);
|
|||
|
|
expect($ranked[0]['relevance_pct'])->toBe(0);
|
|||
|
|
expect($ranked[1]['relevance_pct'])->toBe(0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('пустой список кандидатов → пустой результат', function () {
|
|||
|
|
$rel = new EmbeddingRelevance(fakeEmbedder([]));
|
|||
|
|
expect($rel->rank(['что-то'], []))->toBe([]);
|
|||
|
|
});
|