Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
AssistantClient
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
6 / 6
16
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 chat
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 uploadFile
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
5
 listFiles
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 describeFile
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 deleteFile
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Mbvb1223\Pinecone\Assistant;
6
7use GuzzleHttp\Client;
8use GuzzleHttp\Exception\GuzzleException;
9use Mbvb1223\Pinecone\Errors\PineconeException;
10use Mbvb1223\Pinecone\Errors\PineconeValidationException;
11use Mbvb1223\Pinecone\Utils\Configuration;
12use Mbvb1223\Pinecone\Utils\HandlesApiResponse;
13
14class AssistantClient
15{
16    use HandlesApiResponse;
17
18    private Client $httpClient;
19    private string $assistantName;
20
21    /** @param array<string, mixed> $assistantInfo */
22    public function __construct(Configuration $config, string $assistantName, array $assistantInfo = [])
23    {
24        $host = $assistantInfo['host'] ?? null;
25        $baseUri = $host ? "https://{$host}" : $config->getControllerHost();
26
27        $this->assistantName = $assistantName;
28        $this->httpClient = new Client([
29            'base_uri' => $baseUri,
30            'timeout' => $config->getTimeout(),
31            'headers' => $config->getDefaultHeaders(),
32        ]);
33    }
34
35    /**
36     * Chat with the assistant.
37     *
38     * @param array<int, array{role: string, content: string}> $messages Array of message objects.
39     * @param array<string, mixed> $options Additional options for the chat request.
40     * @return array<string, mixed> The chat response.
41     */
42    public function chat(array $messages, array $options = []): array
43    {
44        if (empty($messages)) {
45            throw new PineconeValidationException('At least one message is required for chat.');
46        }
47
48        try {
49            $payload = array_merge([
50                'messages' => $messages,
51            ], $options);
52
53            $encodedName = urlencode($this->assistantName);
54            $response = $this->httpClient->post("/assistant/chat/{$encodedName}/completions", [
55                'json' => $payload,
56            ]);
57
58            return $this->handleResponse($response);
59        } catch (GuzzleException $e) {
60            throw new PineconeException('Failed to chat with assistant: ' . $e->getMessage(), 0, $e);
61        }
62    }
63
64    /**
65     * Upload a file to the assistant's knowledge base.
66     *
67     * @param string $filePath Path to the file to upload.
68     * @return array<string, mixed> The upload response.
69     */
70    public function uploadFile(string $filePath): array
71    {
72        if (!file_exists($filePath)) {
73            throw new PineconeValidationException("File not found: {$filePath}");
74        }
75
76        $fileHandle = @fopen($filePath, 'r');
77        if ($fileHandle === false) {
78            throw new PineconeValidationException("Cannot open file: {$filePath}");
79        }
80
81        try {
82            $encodedName = urlencode($this->assistantName);
83            $response = $this->httpClient->post("/assistant/files/{$encodedName}", [
84                'multipart' => [
85                    [
86                        'name' => 'file',
87                        'contents' => $fileHandle,
88                        'filename' => basename($filePath),
89                    ],
90                ],
91            ]);
92
93            return $this->handleResponse($response);
94        } catch (GuzzleException $e) {
95            throw new PineconeException('Failed to upload file to assistant: ' . $e->getMessage(), 0, $e);
96        } finally {
97            if (is_resource($fileHandle)) {
98                fclose($fileHandle);
99            }
100        }
101    }
102
103    /**
104     * List files uploaded to the assistant.
105     *
106     * @return array<string, mixed> The list of files.
107     */
108    public function listFiles(): array
109    {
110        try {
111            $encodedName = urlencode($this->assistantName);
112            $response = $this->httpClient->get("/assistant/files/{$encodedName}");
113
114            return $this->handleResponse($response);
115        } catch (GuzzleException $e) {
116            throw new PineconeException('Failed to list assistant files: ' . $e->getMessage(), 0, $e);
117        }
118    }
119
120    /**
121     * Describe a specific file uploaded to the assistant.
122     *
123     * @param string $fileId The file ID.
124     * @return array<string, mixed> The file details.
125     */
126    public function describeFile(string $fileId): array
127    {
128        try {
129            $encodedName = urlencode($this->assistantName);
130            $encodedFileId = urlencode($fileId);
131            $response = $this->httpClient->get("/assistant/files/{$encodedName}/{$encodedFileId}");
132
133            return $this->handleResponse($response);
134        } catch (GuzzleException $e) {
135            throw new PineconeException('Failed to describe assistant file: ' . $e->getMessage(), 0, $e);
136        }
137    }
138
139    /**
140     * Delete a file from the assistant.
141     *
142     * @param string $fileId The file ID.
143     */
144    public function deleteFile(string $fileId): void
145    {
146        try {
147            $encodedName = urlencode($this->assistantName);
148            $encodedFileId = urlencode($fileId);
149            $response = $this->httpClient->delete("/assistant/files/{$encodedName}/{$encodedFileId}");
150            $this->handleResponse($response);
151        } catch (GuzzleException $e) {
152            throw new PineconeException('Failed to delete assistant file: ' . $e->getMessage(), 0, $e);
153        }
154    }
155}