Skip to content

Commit 3159d0a

Browse files
authored
Merge pull request #3 from utopia-php/feat-openai
feat: add openai, xai, perplexity and fix tests
2 parents 387fb33 + 437d0f2 commit 3159d0a

22 files changed

+643
-105
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
LLM_KEY_ANTHROPIC=sk-ant-1234567890
2+
LLM_KEY_OPENAI=sk-proj-1234567890
3+
LLM_KEY_DEEPSEEK=sk-1234567890
4+
LLM_KEY_XAI=xai-1234567890
5+
LLM_KEY_PERPLEXITY=pplx-1234567890

.github/workflows/tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ jobs:
1515
- run: git checkout HEAD^2
1616

1717
- name: Build
18+
env:
19+
LLM_KEY_ANTHROPIC: ${{ secrets.LLM_KEY_ANTHROPIC }}
20+
LLM_KEY_OPENAI: ${{ secrets.LLM_KEY_OPENAI }}
21+
LLM_KEY_DEEPSEEK: ${{ secrets.LLM_KEY_DEEPSEEK }}
22+
LLM_KEY_XAI: ${{ secrets.LLM_KEY_XAI }}
23+
LLM_KEY_PERPLEXITY: ${{ secrets.LLM_KEY_PERPLEXITY }}
1824
run: |
1925
docker compose build
2026
docker compose up -d

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM composer:2.0 as composer
1+
FROM composer:2.0 AS composer
22

33
ARG TESTING=false
44
ENV TESTING=$TESTING
@@ -14,7 +14,7 @@ RUN composer update \
1414
--no-scripts \
1515
--prefer-dist
1616

17-
FROM php:8.0-cli-alpine as final
17+
FROM php:8.3-cli-alpine AS final
1818

1919
LABEL maintainer="team@appwrite.io"
2020

README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP ve
2121

2222
## Features
2323

24-
- **Multiple AI Providers** - Support for OpenAI, Anthropic, and Deepseek APIs
24+
- **Multiple AI Providers** - Support for OpenAI, Anthropic, Deepseek, Perplexity, and XAI APIs
2525
- **Flexible Message Types** - Support for text and structured content in messages
2626
- **Conversation Management** - Easy-to-use conversation handling between agents and users
27-
- **Model Selection** - Choose from various AI models (GPT-4, Claude 3, Deepseek Chat, etc.)
27+
- **Model Selection** - Choose from various AI models (GPT-4, Claude 3, Deepseek Chat, Sonar, Grok, etc.)
2828
- **Parameter Control** - Fine-tune model behavior with temperature and token controls
2929

3030
## Usage
@@ -110,6 +110,43 @@ Available Deepseek Models:
110110
- `MODEL_DEEPSEEK_CHAT`: General-purpose chat model
111111
- `MODEL_DEEPSEEK_CODER`: Specialized for code-related tasks
112112

113+
#### Perplexity
114+
115+
```php
116+
use Utopia\Agents\Adapters\Perplexity;
117+
118+
$perplexity = new Perplexity(
119+
apiKey: 'your-api-key',
120+
model: Perplexity::MODEL_SONAR,
121+
maxTokens: 2048,
122+
temperature: 0.7
123+
);
124+
```
125+
126+
Available Perplexity Models:
127+
- `MODEL_SONAR`: General-purpose search model
128+
- `MODEL_SONAR_PRO`: Enhanced search model
129+
- `MODEL_SONAR_DEEP_RESEARCH`: Advanced search model
130+
- `MODEL_SONAR_REASONING`: Reasoning model
131+
- `MODEL_SONAR_REASONING_PRO`: Enhanced reasoning model
132+
133+
#### XAI
134+
135+
```php
136+
use Utopia\Agents\Adapters\XAI;
137+
138+
$xai = new XAI(
139+
apiKey: 'your-api-key',
140+
model: XAI::MODEL_GROK_2_LATEST,
141+
maxTokens: 2048,
142+
temperature: 0.7
143+
);
144+
```
145+
146+
Available XAI Models:
147+
- `MODEL_GROK_2_LATEST`: Latest Grok model
148+
- `MODEL_GROK_2_IMAGE`: Latest Grok model with image support
149+
113150
### Managing Conversations
114151

115152
```php

docker-compose.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ services:
1010
- ./phpunit.xml:/usr/src/code/phpunit.xml
1111
environment:
1212
- TESTING=true
13+
- LLM_KEY_ANTHROPIC=${LLM_KEY_ANTHROPIC}
14+
- LLM_KEY_OPENAI=${LLM_KEY_OPENAI}
15+
- LLM_KEY_DEEPSEEK=${LLM_KEY_DEEPSEEK}
16+
- LLM_KEY_XAI=${LLM_KEY_XAI}
17+
- LLM_KEY_PERPLEXITY=${LLM_KEY_PERPLEXITY}
1318
networks:
1419
- utopia
1520

1621
networks:
17-
utopia:
22+
utopia:

src/Agents/Adapter.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ public function setAgent(Agent $agent): self
9090
return $this;
9191
}
9292

93-
9493
/**
9594
* Get input tokens count
9695
*

src/Agents/Adapters/Anthropic.php

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Utopia\Agents\Adapters;
44

55
use Utopia\Agents\Adapter;
6-
use Utopia\Agents\Conversation;
76
use Utopia\Agents\Message;
87
use Utopia\Agents\Messages\Text;
98
use Utopia\Fetch\Chunk;
@@ -88,38 +87,41 @@ public function send(array $messages, ?callable $listener = null): Message
8887
throw new \Exception('Agent not set');
8988
}
9089

91-
$client = new \Utopia\Fetch\Client();
90+
$client = new Client();
9291
$client
9392
->setTimeout(90)
9493
->addHeader('x-api-key', $this->apiKey)
9594
->addHeader('anthropic-version', '2023-06-01')
96-
->addHeader('content-type', 'application/json');
95+
->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);
9796

97+
$formattedMessages = [];
9898
foreach ($messages as $message) {
99-
$messages[] = [
100-
'role' => $message['role'],
101-
'content' => $message['content'],
99+
$formattedMessages[] = [
100+
'role' => $message->getRole(),
101+
'content' => $message->getContent(),
102102
];
103103
}
104104

105105
$instructions = [];
106106
foreach ($this->getAgent()->getInstructions() as $name => $content) {
107-
$instructions[] = "# " . $name . "\n\n" . $content;
107+
$instructions[] = '# '.$name."\n\n".$content;
108108
}
109109

110+
$payload = [
111+
'model' => $this->model,
112+
'system' => $this->getAgent()->getDescription().
113+
(empty($instructions) ? '' : "\n\n".implode("\n\n", $instructions)),
114+
'messages' => $formattedMessages,
115+
'max_tokens' => $this->maxTokens,
116+
'temperature' => $this->temperature,
117+
'stream' => true,
118+
];
119+
110120
$content = '';
111121
$response = $client->fetch(
112122
'https://api.anthropic.com/v1/messages',
113123
Client::METHOD_POST,
114-
[
115-
'model' => $this->model,
116-
'system' => $this->getAgent()->getDescription() .
117-
(empty($instructions) ? '' : "\n\n" . implode("\n\n", $instructions)),
118-
'messages' => $messages,
119-
'max_tokens' => $this->maxTokens,
120-
'temperature' => $this->temperature,
121-
'stream' => true,
122-
],
124+
$payload,
123125
[],
124126
function ($chunk) use (&$content, $listener) {
125127
$content .= $this->process($chunk, $listener);
@@ -130,16 +132,13 @@ function ($chunk) use (&$content, $listener) {
130132
throw new \Exception('Anthropic API error ('.$response->getStatusCode().'): '.$response->getBody());
131133
}
132134

133-
$message = new Text($content);
134-
135-
return $message;
135+
return new Text($content);
136136
}
137137

138138
/**
139139
* Process a stream chunk from the Anthropic API
140140
*
141141
* @param \Utopia\Fetch\Chunk $chunk
142-
* @param Conversation $conversation
143142
* @param callable|null $listener
144143
* @return string
145144
*
@@ -152,7 +151,6 @@ protected function process(Chunk $chunk, ?callable $listener): string
152151
$lines = explode("\n", $data);
153152

154153
foreach ($lines as $line) {
155-
156154
if (empty(trim($line))) {
157155
continue;
158156
}
@@ -199,7 +197,7 @@ protected function process(Chunk $chunk, ?callable $listener): string
199197
$block = $json['delta']['text'];
200198
}
201199

202-
if (!empty($block)) {
200+
if (! empty($block)) {
203201
if ($listener !== null) {
204202
$listener($block);
205203
}

src/Agents/Adapters/Deepseek.php

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function __construct(
6565
/**
6666
* Send a message to the Deepseek API
6767
*
68-
* @param array<array<string, mixed>> $messages
68+
* @param array<Message> $messages
6969
* @param callable|null $listener
7070
* @return Message
7171
*
@@ -77,35 +77,34 @@ public function send(array $messages, ?callable $listener = null): Message
7777
throw new \Exception('Agent not set');
7878
}
7979

80-
$client = new \Utopia\Fetch\Client();
80+
$client = new Client();
8181
$client
8282
->setTimeout(90)
83-
->addHeader('authorization', 'Bearer ' . $this->apiKey)
83+
->addHeader('authorization', 'Bearer '.$this->apiKey)
8484
->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);
8585

8686
$formattedMessages = [];
8787
foreach ($messages as $message) {
88-
if (!isset($message['role']) || !isset($message['content'])) {
89-
throw new \Exception('Invalid message format');
88+
if (! empty($message->getRole()) && ! empty($message->getContent())) {
89+
$formattedMessages[] = [
90+
'role' => $message->getRole(),
91+
'content' => $message->getContent(),
92+
];
9093
}
91-
$formattedMessages[] = [
92-
'role' => $message['role'],
93-
'content' => $message['content'],
94-
];
9594
}
9695

9796
$instructions = [];
9897
foreach ($this->getAgent()->getInstructions() as $name => $content) {
99-
$instructions[] = "# " . $name . "\n\n" . $content;
98+
$instructions[] = '# '.$name."\n\n".$content;
10099
}
101100

102-
$systemMessage = $this->getAgent()->getDescription() .
103-
(empty($instructions) ? '' : "\n\n" . implode("\n\n", $instructions));
101+
$systemMessage = $this->getAgent()->getDescription().
102+
(empty($instructions) ? '' : "\n\n".implode("\n\n", $instructions));
104103

105-
if (!empty($systemMessage)) {
104+
if (! empty($systemMessage)) {
106105
array_unshift($formattedMessages, [
107106
'role' => 'system',
108-
'content' => $systemMessage
107+
'content' => $systemMessage,
109108
]);
110109
}
111110

@@ -173,7 +172,7 @@ protected function process(Chunk $chunk, ?callable $listener): string
173172

174173
if (isset($json['choices'][0]['delta']['content'])) {
175174
$delta = $json['choices'][0]['delta']['content'];
176-
if (!empty($delta)) {
175+
if (! empty($delta)) {
177176
$block .= $delta;
178177
if ($listener !== null) {
179178
$listener($delta);
@@ -271,4 +270,4 @@ public function getName(): string
271270
{
272271
return 'deepseek';
273272
}
274-
}
273+
}

0 commit comments

Comments
 (0)