Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LLM_KEY_ANTHROPIC=sk-ant-1234567890
LLM_KEY_OPENAI=sk-proj-1234567890
LLM_KEY_DEEPSEEK=sk-1234567890
LLM_KEY_XAI=xai-1234567890
LLM_KEY_PERPLEXITY=pplx-1234567890
6 changes: 6 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ jobs:
- run: git checkout HEAD^2

- name: Build
env:
LLM_KEY_ANTHROPIC: ${{ secrets.LLM_KEY_ANTHROPIC }}
LLM_KEY_OPENAI: ${{ secrets.LLM_KEY_OPENAI }}
LLM_KEY_DEEPSEEK: ${{ secrets.LLM_KEY_DEEPSEEK }}
LLM_KEY_XAI: ${{ secrets.LLM_KEY_XAI }}
LLM_KEY_PERPLEXITY: ${{ secrets.LLM_KEY_PERPLEXITY }}
run: |
docker compose build
docker compose up -d
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM composer:2.0 as composer
FROM composer:2.0 AS composer

ARG TESTING=false
ENV TESTING=$TESTING
Expand All @@ -14,7 +14,7 @@ RUN composer update \
--no-scripts \
--prefer-dist

FROM php:8.0-cli-alpine as final
FROM php:8.3-cli-alpine AS final

LABEL maintainer="team@appwrite.io"

Expand Down
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP ve

## Features

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

## Usage
Expand Down Expand Up @@ -110,6 +110,43 @@ Available Deepseek Models:
- `MODEL_DEEPSEEK_CHAT`: General-purpose chat model
- `MODEL_DEEPSEEK_CODER`: Specialized for code-related tasks

#### Perplexity

```php
use Utopia\Agents\Adapters\Perplexity;

$perplexity = new Perplexity(
apiKey: 'your-api-key',
model: Perplexity::MODEL_SONAR,
maxTokens: 2048,
temperature: 0.7
);
```

Available Perplexity Models:
- `MODEL_SONAR`: General-purpose search model
- `MODEL_SONAR_PRO`: Enhanced search model
- `MODEL_SONAR_DEEP_RESEARCH`: Advanced search model
- `MODEL_SONAR_REASONING`: Reasoning model
- `MODEL_SONAR_REASONING_PRO`: Enhanced reasoning model

#### XAI

```php
use Utopia\Agents\Adapters\XAI;

$xai = new XAI(
apiKey: 'your-api-key',
model: XAI::MODEL_GROK_2_LATEST,
maxTokens: 2048,
temperature: 0.7
);
```

Available XAI Models:
- `MODEL_GROK_2_LATEST`: Latest Grok model
- `MODEL_GROK_2_IMAGE`: Latest Grok model with image support

### Managing Conversations

```php
Expand Down
7 changes: 6 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ services:
- ./phpunit.xml:/usr/src/code/phpunit.xml
environment:
- TESTING=true
- LLM_KEY_ANTHROPIC=${LLM_KEY_ANTHROPIC}
- LLM_KEY_OPENAI=${LLM_KEY_OPENAI}
- LLM_KEY_DEEPSEEK=${LLM_KEY_DEEPSEEK}
- LLM_KEY_XAI=${LLM_KEY_XAI}
- LLM_KEY_PERPLEXITY=${LLM_KEY_PERPLEXITY}
networks:
- utopia

networks:
utopia:
utopia:
1 change: 0 additions & 1 deletion src/Agents/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ public function setAgent(Agent $agent): self
return $this;
}


/**
* Get input tokens count
*
Expand Down
42 changes: 20 additions & 22 deletions src/Agents/Adapters/Anthropic.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Utopia\Agents\Adapters;

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

$client = new \Utopia\Fetch\Client();
$client = new Client();
$client
->setTimeout(90)
->addHeader('x-api-key', $this->apiKey)
->addHeader('anthropic-version', '2023-06-01')
->addHeader('content-type', 'application/json');
->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);

$formattedMessages = [];
foreach ($messages as $message) {
$messages[] = [
'role' => $message['role'],
'content' => $message['content'],
$formattedMessages[] = [
'role' => $message->getRole(),
'content' => $message->getContent(),
];
}

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

$payload = [
'model' => $this->model,
'system' => $this->getAgent()->getDescription().
(empty($instructions) ? '' : "\n\n".implode("\n\n", $instructions)),
'messages' => $formattedMessages,
'max_tokens' => $this->maxTokens,
'temperature' => $this->temperature,
'stream' => true,
];

$content = '';
$response = $client->fetch(
'https://api.anthropic.com/v1/messages',
Client::METHOD_POST,
[
'model' => $this->model,
'system' => $this->getAgent()->getDescription() .
(empty($instructions) ? '' : "\n\n" . implode("\n\n", $instructions)),
'messages' => $messages,
'max_tokens' => $this->maxTokens,
'temperature' => $this->temperature,
'stream' => true,
],
$payload,
[],
function ($chunk) use (&$content, $listener) {
$content .= $this->process($chunk, $listener);
Expand All @@ -130,16 +132,13 @@ function ($chunk) use (&$content, $listener) {
throw new \Exception('Anthropic API error ('.$response->getStatusCode().'): '.$response->getBody());
}

$message = new Text($content);

return $message;
return new Text($content);
}

/**
* Process a stream chunk from the Anthropic API
*
* @param \Utopia\Fetch\Chunk $chunk
* @param Conversation $conversation
* @param callable|null $listener
* @return string
*
Expand All @@ -152,7 +151,6 @@ protected function process(Chunk $chunk, ?callable $listener): string
$lines = explode("\n", $data);

foreach ($lines as $line) {

if (empty(trim($line))) {
continue;
}
Expand Down Expand Up @@ -199,7 +197,7 @@ protected function process(Chunk $chunk, ?callable $listener): string
$block = $json['delta']['text'];
}

if (!empty($block)) {
if (! empty($block)) {
if ($listener !== null) {
$listener($block);
}
Expand Down
31 changes: 15 additions & 16 deletions src/Agents/Adapters/Deepseek.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function __construct(
/**
* Send a message to the Deepseek API
*
* @param array<array<string, mixed>> $messages
* @param array<Message> $messages
* @param callable|null $listener
* @return Message
*
Expand All @@ -77,35 +77,34 @@ public function send(array $messages, ?callable $listener = null): Message
throw new \Exception('Agent not set');
}

$client = new \Utopia\Fetch\Client();
$client = new Client();
$client
->setTimeout(90)
->addHeader('authorization', 'Bearer ' . $this->apiKey)
->addHeader('authorization', 'Bearer '.$this->apiKey)
->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);

$formattedMessages = [];
foreach ($messages as $message) {
if (!isset($message['role']) || !isset($message['content'])) {
throw new \Exception('Invalid message format');
if (! empty($message->getRole()) && ! empty($message->getContent())) {
$formattedMessages[] = [
'role' => $message->getRole(),
'content' => $message->getContent(),
];
}
$formattedMessages[] = [
'role' => $message['role'],
'content' => $message['content'],
];
}

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

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

if (!empty($systemMessage)) {
if (! empty($systemMessage)) {
array_unshift($formattedMessages, [
'role' => 'system',
'content' => $systemMessage
'content' => $systemMessage,
]);
}

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

if (isset($json['choices'][0]['delta']['content'])) {
$delta = $json['choices'][0]['delta']['content'];
if (!empty($delta)) {
if (! empty($delta)) {
$block .= $delta;
if ($listener !== null) {
$listener($delta);
Expand Down Expand Up @@ -271,4 +270,4 @@ public function getName(): string
{
return 'deepseek';
}
}
}
Loading