Skip to content

Commit d583532

Browse files
authored
Merge pull request #5 from upstash/fetch-random-vector
feat: add ability to fetch a random vector
2 parents 1270477 + 2c69e39 commit d583532

File tree

11 files changed

+235
-73
lines changed

11 files changed

+235
-73
lines changed

src/Contracts/IndexNamespaceInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Upstash\Vector\VectorDeleteResult;
1010
use Upstash\Vector\VectorFetch;
1111
use Upstash\Vector\VectorFetchResult;
12+
use Upstash\Vector\VectorMatch;
1213
use Upstash\Vector\VectorQuery;
1314
use Upstash\Vector\VectorQueryManyResult;
1415
use Upstash\Vector\VectorQueryResult;
@@ -51,4 +52,6 @@ public function queryData(DataQuery $query): DataQueryResult;
5152
public function delete(array $ids): VectorDeleteResult;
5253

5354
public function fetch(VectorFetch $vectorFetch): VectorFetchResult;
55+
56+
public function random(): ?VectorMatch;
5457
}

src/Index.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,9 @@ public function resetAll(): void
125125
{
126126
(new ResetAllNamespacesOperation($this->getTransporter()))->resetAll();
127127
}
128+
129+
public function random(): ?VectorMatch
130+
{
131+
return $this->namespace('')->random();
132+
}
128133
}

src/IndexNamespace.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Upstash\Vector\Contracts\TransporterInterface;
77
use Upstash\Vector\Operations\DeleteNamespaceOperation;
88
use Upstash\Vector\Operations\DeleteVectorsOperation;
9+
use Upstash\Vector\Operations\FetchRandomVectorOperation;
910
use Upstash\Vector\Operations\FetchVectorsOperation;
1011
use Upstash\Vector\Operations\GetNamespaceInfoOperation;
1112
use Upstash\Vector\Operations\QueryDataOperation;
@@ -80,4 +81,9 @@ public function fetch(VectorFetch $vectorFetch): VectorFetchResult
8081
return (new FetchVectorsOperation($this->namespace, $this->transporter))
8182
->fetch($vectorFetch);
8283
}
84+
85+
public function random(): ?VectorMatch
86+
{
87+
return (new FetchRandomVectorOperation($this->namespace, $this->transporter))->fetch();
88+
}
8389
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Operations\Concerns;
4+
5+
use Upstash\Vector\SparseVector;
6+
use Upstash\Vector\VectorMatch;
7+
8+
trait MapsVectorMatches
9+
{
10+
private function mapVectorMatch(array $result): VectorMatch
11+
{
12+
$vector = [];
13+
if (isset($result['vector'])) {
14+
$vector = $result['vector'];
15+
}
16+
17+
$sparseVector = new SparseVector;
18+
if (isset($result['sparseVector'])) {
19+
['indices' => $indices, 'values' => $values] = $result['sparseVector'];
20+
$sparseVector = new SparseVector(indices: $indices, values: $values);
21+
}
22+
23+
return new VectorMatch(
24+
id: $result['id'],
25+
score: $result['score'] ?? 0,
26+
vector: $vector,
27+
sparseVector: $sparseVector,
28+
data: $result['data'] ?? '',
29+
metadata: $result['metadata'] ?? [],
30+
);
31+
}
32+
}

src/Operations/FetchRandomVectorOperation.php

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,58 @@
33
namespace Upstash\Vector\Operations;
44

55
use Upstash\Vector\Contracts\TransporterInterface;
6+
use Upstash\Vector\Operations\Concerns\AssertsApiResponseErrors;
7+
use Upstash\Vector\Operations\Concerns\MapsVectorMatches;
8+
use Upstash\Vector\Transporter\ContentType;
9+
use Upstash\Vector\Transporter\Method;
10+
use Upstash\Vector\Transporter\TransporterRequest;
11+
use Upstash\Vector\Transporter\TransporterResponse;
12+
use Upstash\Vector\VectorMatch;
613

714
/**
815
* @internal
916
*/
1017
final readonly class FetchRandomVectorOperation
1118
{
19+
use AssertsApiResponseErrors;
20+
use MapsVectorMatches;
21+
1222
public function __construct(private string $namespace, private TransporterInterface $transporter) {}
1323

14-
public function fetch(): void
24+
public function fetch(): ?VectorMatch
25+
{
26+
$request = new TransporterRequest(
27+
contentType: ContentType::JSON,
28+
method: Method::GET,
29+
path: $this->getPath(),
30+
);
31+
32+
$response = $this->transporter->sendRequest($request);
33+
34+
$this->assertResponse($response);
35+
36+
return $this->transformResponse($response);
37+
}
38+
39+
private function getPath(): string
1540
{
16-
// TODO: Implement
41+
$namespace = trim($this->namespace);
42+
if ($namespace !== '') {
43+
return "/random/$namespace";
44+
}
45+
46+
return '/random';
47+
}
48+
49+
private function transformResponse(TransporterResponse $response): ?VectorMatch
50+
{
51+
$data = json_decode($response->data, true);
52+
53+
$result = $data['result'] ?? null;
54+
if ($result === null) {
55+
return null;
56+
}
57+
58+
return $this->mapVectorMatch($result);
1759
}
1860
}

src/Operations/FetchVectorsOperation.php

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@
44

55
use Upstash\Vector\Contracts\TransporterInterface;
66
use Upstash\Vector\Operations\Concerns\AssertsApiResponseErrors;
7-
use Upstash\Vector\SparseVector;
7+
use Upstash\Vector\Operations\Concerns\MapsVectorMatches;
88
use Upstash\Vector\Transporter\ContentType;
99
use Upstash\Vector\Transporter\Method;
1010
use Upstash\Vector\Transporter\TransporterRequest;
1111
use Upstash\Vector\Transporter\TransporterResponse;
1212
use Upstash\Vector\VectorFetch;
1313
use Upstash\Vector\VectorFetchResult;
14-
use Upstash\Vector\VectorMatch;
1514

1615
/**
1716
* @internal
1817
*/
1918
final readonly class FetchVectorsOperation
2019
{
2120
use AssertsApiResponseErrors;
21+
use MapsVectorMatches;
2222

2323
public function __construct(private string $namespace, private TransporterInterface $transporter) {}
2424

@@ -52,27 +52,7 @@ private function getPath(): string
5252
private function transformResponse(TransporterResponse $response): VectorFetchResult
5353
{
5454
$data = json_decode($response->data, true)['result'] ?? [];
55-
$results = array_map(function (array $result) {
56-
$vector = [];
57-
if (isset($result['vector'])) {
58-
$vector = $result['vector'];
59-
}
60-
61-
$sparseVector = new SparseVector;
62-
if (isset($result['sparseVector'])) {
63-
['indices' => $indices, 'values' => $values] = $result['sparseVector'];
64-
$sparseVector = new SparseVector(indices: $indices, values: $values);
65-
}
66-
67-
return new VectorMatch(
68-
id: $result['id'],
69-
score: 1.0,
70-
vector: $vector,
71-
sparseVector: $sparseVector,
72-
data: $result['data'] ?? '',
73-
metadata: $result['metadata'] ?? [],
74-
);
75-
}, $data);
55+
$results = array_map(fn (array $result) => $this->mapVectorMatch($result), $data);
7656

7757
return new VectorFetchResult($results);
7858
}

src/Operations/QueryVectorsManyOperation.php

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55
use Upstash\Vector\Contracts\TransporterInterface;
66
use Upstash\Vector\Operations\Concerns\AssertsApiResponseErrors;
7-
use Upstash\Vector\SparseVector;
7+
use Upstash\Vector\Operations\Concerns\MapsVectorMatches;
88
use Upstash\Vector\Transporter\ContentType;
99
use Upstash\Vector\Transporter\Method;
1010
use Upstash\Vector\Transporter\TransporterRequest;
1111
use Upstash\Vector\Transporter\TransporterResponse;
12-
use Upstash\Vector\VectorMatch;
1312
use Upstash\Vector\VectorQuery;
1413
use Upstash\Vector\VectorQueryManyResult;
1514
use Upstash\Vector\VectorQueryResult;
@@ -20,6 +19,7 @@
2019
final readonly class QueryVectorsManyOperation
2120
{
2221
use AssertsApiResponseErrors;
22+
use MapsVectorMatches;
2323

2424
public function __construct(private string $namespace, private TransporterInterface $transporter) {}
2525

@@ -70,27 +70,4 @@ private function transformResponse(TransporterResponse $response, array $queryKe
7070

7171
return new VectorQueryManyResult(results: $result);
7272
}
73-
74-
private function mapVectorMatch(array $result): VectorMatch
75-
{
76-
$vector = [];
77-
if (isset($result['vector'])) {
78-
$vector = $result['vector'];
79-
}
80-
81-
$sparseVector = new SparseVector;
82-
if (isset($result['sparseVector'])) {
83-
['indices' => $indices, 'values' => $values] = $result['sparseVector'];
84-
$sparseVector = new SparseVector(indices: $indices, values: $values);
85-
}
86-
87-
return new VectorMatch(
88-
id: $result['id'],
89-
score: $result['score'],
90-
vector: $vector,
91-
sparseVector: $sparseVector,
92-
data: $result['data'] ?? '',
93-
metadata: $result['metadata'] ?? [],
94-
);
95-
}
9673
}

src/Operations/QueryVectorsOperation.php

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55
use Upstash\Vector\Contracts\TransporterInterface;
66
use Upstash\Vector\Operations\Concerns\AssertsApiResponseErrors;
7-
use Upstash\Vector\SparseVector;
7+
use Upstash\Vector\Operations\Concerns\MapsVectorMatches;
88
use Upstash\Vector\Transporter\ContentType;
99
use Upstash\Vector\Transporter\Method;
1010
use Upstash\Vector\Transporter\TransporterRequest;
1111
use Upstash\Vector\Transporter\TransporterResponse;
12-
use Upstash\Vector\VectorMatch;
1312
use Upstash\Vector\VectorQuery;
1413
use Upstash\Vector\VectorQueryResult;
1514

@@ -19,6 +18,7 @@
1918
final readonly class QueryVectorsOperation
2019
{
2120
use AssertsApiResponseErrors;
21+
use MapsVectorMatches;
2222

2323
public function __construct(
2424
private string $namespace,
@@ -51,27 +51,7 @@ private function transformResponse(TransporterResponse $response): VectorQueryRe
5151
{
5252
$data = json_decode($response->data, true);
5353

54-
$results = array_map(function (array $result) {
55-
$vector = [];
56-
if (isset($result['vector'])) {
57-
$vector = $result['vector'];
58-
}
59-
60-
$sparseVector = new SparseVector;
61-
if (isset($result['sparseVector'])) {
62-
['indices' => $indices, 'values' => $values] = $result['sparseVector'];
63-
$sparseVector = new SparseVector(indices: $indices, values: $values);
64-
}
65-
66-
return new VectorMatch(
67-
id: $result['id'],
68-
score: $result['score'],
69-
vector: $vector,
70-
sparseVector: $sparseVector,
71-
data: $result['data'] ?? '',
72-
metadata: $result['metadata'] ?? [],
73-
);
74-
}, $data['result'] ?? []);
54+
$results = array_map(fn (array $result) => $this->mapVectorMatch($result), $data['result'] ?? []);
7555

7656
return new VectorQueryResult($results);
7757
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Tests\Dense\Operations;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Upstash\Vector\Tests\Concerns\UsesDenseIndex;
7+
use Upstash\Vector\Tests\Concerns\WaitsForIndex;
8+
use Upstash\Vector\VectorUpsert;
9+
10+
class FetchRandomVectorTest extends TestCase
11+
{
12+
use UsesDenseIndex;
13+
use WaitsForIndex;
14+
15+
public function test_can_fetch_random_vector(): void
16+
{
17+
// Arrange
18+
$this->namespace->upsert(new VectorUpsert(
19+
id: '1',
20+
vector: [0.1, 0.1],
21+
));
22+
$this->waitForIndex($this->namespace);
23+
24+
// Act
25+
$result = $this->namespace->random();
26+
27+
// Assert
28+
$this->assertNotNull($result);
29+
$this->assertSame('1', $result->id);
30+
$this->assertCount(2, $result->vector);
31+
}
32+
33+
public function test_can_fetch_random_vector_when_index_is_empty(): void
34+
{
35+
// Act
36+
$result = $this->namespace->random();
37+
38+
// Assert
39+
$this->assertNull($result);
40+
}
41+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Tests\Hybrid\Operations;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Upstash\Vector\SparseVector;
7+
use Upstash\Vector\Tests\Concerns\UsesHybridIndex;
8+
use Upstash\Vector\Tests\Concerns\WaitsForIndex;
9+
use Upstash\Vector\VectorUpsert;
10+
11+
use function Upstash\Vector\createRandomVector;
12+
13+
class FetchRandomVectorTest extends TestCase
14+
{
15+
use UsesHybridIndex;
16+
use WaitsForIndex;
17+
18+
public function test_can_fetch_random_vector(): void
19+
{
20+
// Arrange
21+
$this->namespace->upsert(new VectorUpsert(
22+
id: '1',
23+
vector: createRandomVector(dimensions: 384),
24+
sparseVector: new SparseVector(
25+
indices: [1, 2, 3],
26+
values: [5.0, 6.0, 7.0],
27+
),
28+
));
29+
$this->waitForIndex($this->namespace);
30+
31+
// Act
32+
$result = $this->namespace->random();
33+
34+
// Assert
35+
$this->assertNotNull($result);
36+
$this->assertSame('1', $result->id);
37+
$this->assertCount(384, $result->vector);
38+
$this->assertSame([1, 2, 3], $result->sparseVector->indices);
39+
$this->assertSame([5.0, 6.0, 7.0], $result->sparseVector->values);
40+
}
41+
42+
public function test_can_fetch_random_vector_when_index_is_empty(): void
43+
{
44+
// Act
45+
$result = $this->namespace->random();
46+
47+
// Assert
48+
$this->assertNull($result);
49+
}
50+
}

0 commit comments

Comments
 (0)