Skip to content

Commit 94e9808

Browse files
Setup some embedding functions and fix error handling
- Setup the OpenAIEmbeddingFunction, JinaEmbeddingFunction and HuggingFaceEmbeddingServerFunction - Fix the way exceptions are being handled in the ApiCLient - Added more tests to expect exceptions - Added an examples folder for examples on usage
1 parent e1212b6 commit 94e9808

23 files changed

Lines changed: 360 additions & 473 deletions

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
uses: actions/checkout@v3
2626

2727
- name: Cache dependencies
28-
uses: actions/cache@v2
28+
uses: actions/cache@v3
2929
with:
3030
path: ~/.composer/cache/files
3131
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@
66
*.swp
77
*.swo
88
playground/*
9-
.idea
10-
index.php
9+
.idea

examples/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
vendor
2+
composer.lock

examples/composer.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "kyrian/examples",
3+
"type": "project",
4+
"autoload": {
5+
"psr-4": {
6+
}
7+
},
8+
"authors": [
9+
{
10+
"name": "Kyrian Obikwelu",
11+
"email": "koshnawaza@gmail.com"
12+
}
13+
],
14+
"repositories": [
15+
{
16+
"type": "path",
17+
"url": "../",
18+
"options": {
19+
"symlink": true
20+
}
21+
}
22+
],
23+
"require": {
24+
"codewithkyrian/chromadb-php": "@dev",
25+
"symfony/var-dumper": "^6.3"
26+
}
27+
}

examples/index.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require './vendor/autoload.php';
6+
7+
use Codewithkyrian\ChromaDB\ChromaDB;
8+
use Codewithkyrian\ChromaDB\Embeddings\JinaEmbeddingFunction;
9+
10+
$chroma = ChromaDB::factory()
11+
->withDatabase('test_database')
12+
->withTenant('test_tenant')
13+
->connect();
14+
15+
$chroma->deleteAllCollections();
16+
17+
$embeddingFunction = new JinaEmbeddingFunction(
18+
'jina_8cbaafb9543e42f1a2fc7430d456c3faKKPc93W8Eur5T2XjAkryfwQ9TOv8'
19+
);
20+
21+
$collection = $chroma->createCollection(
22+
name: 'test_collection',
23+
embeddingFunction: $embeddingFunction
24+
);
25+
26+
$collection->add(
27+
ids: ['hello', 'world'],
28+
documents: ['This is a test document', 'The man is happy']
29+
);
30+
31+
$queryResponse = $collection->query(
32+
queryTexts: ['The man is excited'],
33+
include: ['documents', 'distances']
34+
);
35+
36+
dd($queryResponse->documents[0], $queryResponse->distances[0]);
37+
38+

src/Client.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ public function __construct(
2121
}
2222

2323

24-
/**
25-
* @throws Generated\Exceptions\ChromaApiExceptionInterface
26-
*/
2724
public function initDatabaseAndTenant(): void
2825
{
2926

@@ -118,21 +115,18 @@ public function getOrCreateCollection(string $name, ?array $metadata = null, ?Em
118115
}
119116

120117
/**
121-
* Gets a collection with the specified name.
118+
* Gets a collection with the specified name. Will raise an exception if the
119+
* collection does not exist.
122120
*
123121
* @param string $name The name of the collection.
124122
* @param ?EmbeddingFunction $embeddingFunction Optional custom embedding function for the collection.
125123
*
126-
* @return ?CollectionResource
124+
* @return CollectionResource
127125
*/
128-
public function getCollection(string $name, ?EmbeddingFunction $embeddingFunction = null): ?CollectionResource
126+
public function getCollection(string $name, ?EmbeddingFunction $embeddingFunction = null): CollectionResource
129127
{
130128
$collection = $this->apiClient->getCollection($name, $this->database, $this->tenant);
131129

132-
if ($collection === null) {
133-
return null;
134-
}
135-
136130
return CollectionResource::make(
137131
$collection,
138132
$this->database,

src/Embeddings/HuggingFaceEmbeddingFunction.php renamed to src/Embeddings/HuggingFaceEmbeddingServerFunction.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,26 @@
88
use GuzzleHttp\Client;
99
use GuzzleHttp\Exception\GuzzleException;
1010

11-
class HuggingFaceEmbeddingFunction implements EmbeddingFunction
11+
class HuggingFaceEmbeddingServerFunction implements EmbeddingFunction
1212
{
1313

1414
public function __construct(
15-
public readonly string $url,
15+
public readonly string $baseUrl = 'http://localhost:8080',
1616
)
1717
{
1818
}
1919

2020
public function generate(array $texts): array
2121
{
2222
$client = new Client([
23+
'base_uri' => $this->baseUrl,
2324
'headers' => [
2425
'Content-Type' => 'application/json',
2526
]
2627
]);
2728

2829
try {
29-
$response = $client->post($this->url, [
30+
$response = $client->post('embed', [
3031
'body' => json_encode([
3132
'inputs' => $texts,
3233
])

src/Embeddings/JinaEmbeddingFunction.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,47 @@
88
use Codewithkyrian\ChromaDB\Embeddings\EmbeddingFunction;
99
use GuzzleHttp\Client;
1010
use GuzzleHttp\Exception\GuzzleException;
11+
use Psr\Http\Client\ClientExceptionInterface;
1112

1213
class JinaEmbeddingFunction implements EmbeddingFunction
1314
{
14-
15-
private const API_URL = 'https://api.jina.ai/v1/embeddings';
15+
private Client $client;
1616

1717
public function __construct(
1818
public readonly string $apiKey,
1919
public readonly string $model = 'jina-embeddings-v2-base-en',
2020
)
2121
{
22-
}
23-
24-
/**
25-
* @inheritDoc
26-
*/
27-
public function generate(array $texts): array
28-
{
29-
$client = new Client([
22+
$this->client = new Client([
23+
'base_uri' => 'https://api.jina.ai/v1/',
3024
'headers' => [
3125
'Authorization' => "Bearer $this->apiKey",
3226
'Content-Type' => 'application/json',
3327
'Accept-Encoding' => 'identity',
3428
]
3529
]);
30+
}
3631

32+
/**
33+
* @inheritDoc
34+
*/
35+
public function generate(array $texts): array
36+
{
3737
try {
38-
$response = $client->post(self::API_URL, [
39-
'body' => json_encode([
38+
$response = $this->client->post('embeddings', [
39+
'json' => [
4040
'model' => $this->model,
4141
'input' => $texts,
42-
])
42+
]
4343
]);
4444

4545
$result = json_decode($response->getBody()->getContents(), true);
4646
$embeddings = $result['data'];
4747
usort($embeddings, fn($a, $b) => $a['index'] <=> $b['index']);
4848

4949
return array_map(fn($embedding) => $embedding['embedding'], $embeddings);
50-
} catch (GuzzleException $e) {
51-
throw new \RuntimeException('Failed to generate embeddings', 0, $e);
50+
} catch (ClientExceptionInterface $e) {
51+
throw new \RuntimeException("Error calling Jina AI API: {$e->getMessage()}", 0, $e);
5252
}
5353
}
5454
}

src/Embeddings/OpenAIEmbeddingFunction.php

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,54 @@
55

66
namespace Codewithkyrian\ChromaDB\Embeddings;
77

8-
use Codewithkyrian\ChromaDB\Embeddings\EmbeddingFunction;
9-
10-
interface OpenAIAPIInterface {
11-
/**
12-
* Creates a new embedding function
13-
* @param string $model
14-
* @param string[] $input
15-
* @param string $user
16-
*
17-
* @return int[][]
18-
*/
19-
public function createEmbedding(string $model, array $input, string $user ): array;
20-
}
8+
use GuzzleHttp\Client;
9+
use Psr\Http\Client\ClientExceptionInterface;
2110

2211
class OpenAIEmbeddingFunction implements EmbeddingFunction
2312
{
13+
private Client $client;
2414

2515
public function __construct(
2616
public readonly string $apiKey,
27-
public readonly string $organizationId,
28-
public readonly string $model,
29-
17+
public readonly string $organization = '',
18+
public readonly string $model = 'text-embedding-ada-002',
3019
)
3120
{
21+
$headers = [
22+
'Authorization' => "Bearer $this->apiKey",
23+
'Content-Type' => 'application/json',
24+
];
25+
26+
if (!empty($this->organization)) {
27+
$headers['OpenAI-Organization'] = $this->organization;
28+
}
29+
30+
$this->client = new Client([
31+
'base_uri' => 'https://api.openai.com/v1/',
32+
'headers' => $headers
33+
]);
3234
}
3335

3436
/**
3537
* @inheritDoc
3638
*/
3739
public function generate(array $texts): array
3840
{
39-
return [[1,2,3,4,5,6,7,8,9,10], [1,2,3,4,5,6,7,8,9,10]];
41+
try {
42+
$response = $this->client->post('embeddings', [
43+
'json' => [
44+
'model' => $this->model,
45+
'input' => $texts,
46+
]
47+
]);
48+
49+
$result = json_decode($response->getBody()->getContents(), true);
50+
$embeddings = $result['data'];
51+
usort($embeddings, fn($a, $b) => $a['index'] <=> $b['index']);
52+
53+
return array_map(fn($embedding) => $embedding['embedding'], $embeddings);
54+
} catch (ClientExceptionInterface $e) {
55+
throw new \RuntimeException("Error calling OpenAI API: {$e->getMessage()}", 0, $e);
56+
}
4057
}
4158
}

src/Enums/CollectionInclude.php

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)