Skip to content

Commit 34ccb1f

Browse files
authored
Merge pull request #72 from MaestroError/feature/expose-api
Expose any Agent(s) as API endpoint compatible to OpenAI API
2 parents 6597b9f + f96c57c commit 34ccb1f

38 files changed

+3093
-60
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ testbench.yaml
3333

3434
/openai-api-key.php
3535
/chat-history/*
36-
/json_History/*
36+
/json_History/*
37+
/gemini-api-key.php

config/laragent.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@
5151
'default_max_completion_tokens' => 10000,
5252
'default_temperature' => 1,
5353
],
54-
],
5554

56-
'groq' => [
57-
'label' => 'groq',
58-
'api_key' => env('GROQ_API_KEY'),
59-
'api_url' => 'https://api.groq.com/openai/v1',
60-
'model' => 'llama-3.1-8b-instant',
61-
'driver' => \LarAgent\Drivers\Groq\GroqDriver::class,
62-
'default_context_window' => 131072,
63-
'default_max_completion_tokens' => 131072,
64-
'default_temperature' => 1,
55+
'groq' => [
56+
'label' => 'groq',
57+
'api_key' => env('GROQ_API_KEY'),
58+
'api_url' => 'https://api.groq.com/openai/v1',
59+
'model' => 'llama-3.1-8b-instant',
60+
'driver' => \LarAgent\Drivers\Groq\GroqDriver::class,
61+
'default_context_window' => 131072,
62+
'default_max_completion_tokens' => 131072,
63+
'default_temperature' => 1,
64+
],
6565
],
6666

6767
/**
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
namespace LarAgent\API\Completion;
4+
5+
use ArrayAccess;
6+
use JsonSerializable;
7+
8+
class CompletionRequestDTO implements ArrayAccess, JsonSerializable
9+
{
10+
public function __construct(
11+
public readonly array $messages,
12+
public string $model,
13+
public readonly ?array $modalities = null,
14+
public readonly ?array $audio = null,
15+
public readonly ?int $n = null,
16+
public readonly ?float $temperature = null,
17+
public readonly ?float $top_p = null,
18+
public readonly ?float $frequency_penalty = null,
19+
public readonly ?float $presence_penalty = null,
20+
public readonly ?int $max_completion_tokens = null,
21+
public readonly ?array $response_format = null,
22+
public readonly ?array $tools = null,
23+
public readonly mixed $tool_choice = null,
24+
public readonly ?bool $parallel_tool_calls = null,
25+
public readonly bool $stream = false,
26+
) {}
27+
28+
public static function fromArray(array $data): self
29+
{
30+
return new self(
31+
messages: $data['messages'],
32+
model: $data['model'],
33+
modalities: $data['modalities'] ?? null,
34+
audio: $data['audio'] ?? null,
35+
n: $data['n'] ?? null,
36+
temperature: $data['temperature'] ?? null,
37+
top_p: $data['top_p'] ?? null,
38+
frequency_penalty: $data['frequency_penalty'] ?? null,
39+
presence_penalty: $data['presence_penalty'] ?? null,
40+
max_completion_tokens: $data['max_completion_tokens'] ?? null,
41+
response_format: $data['response_format'] ?? null,
42+
tools: $data['tools'] ?? null,
43+
tool_choice: $data['tool_choice'] ?? null,
44+
parallel_tool_calls: $data['parallel_tool_calls'] ?? null,
45+
stream: $data['stream'] ?? false,
46+
);
47+
}
48+
49+
public function toArray(): array
50+
{
51+
return [
52+
'messages' => $this->messages,
53+
'model' => $this->model,
54+
'modalities' => $this->modalities,
55+
'audio' => $this->audio,
56+
'n' => $this->n,
57+
'temperature' => $this->temperature,
58+
'top_p' => $this->top_p,
59+
'frequency_penalty' => $this->frequency_penalty,
60+
'presence_penalty' => $this->presence_penalty,
61+
'max_completion_tokens' => $this->max_completion_tokens,
62+
'response_format' => $this->response_format,
63+
'tools' => $this->tools,
64+
'tool_choice' => $this->tool_choice,
65+
'parallel_tool_calls' => $this->parallel_tool_calls,
66+
'stream' => $this->stream,
67+
];
68+
}
69+
70+
public function jsonSerialize(): array
71+
{
72+
return $this->toArray();
73+
}
74+
75+
public function offsetExists($offset): bool
76+
{
77+
return array_key_exists($offset, $this->toArray());
78+
}
79+
80+
public function offsetGet($offset): mixed
81+
{
82+
return $this->toArray()[$offset] ?? null;
83+
}
84+
85+
public function offsetSet($offset, $value): void
86+
{
87+
throw new \BadMethodCallException('CompletionRequestDTO is immutable.');
88+
}
89+
90+
public function offsetUnset($offset): void
91+
{
92+
throw new \BadMethodCallException('CompletionRequestDTO is immutable.');
93+
}
94+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace LarAgent\API\Completion\Controllers;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\Log;
7+
use InvalidArgumentException;
8+
use LarAgent\API\Completion\Traits\HasSessionId;
9+
use LarAgent\API\Completions;
10+
11+
abstract class MultiAgentController
12+
{
13+
use HasSessionId;
14+
15+
protected ?array $agents = null;
16+
17+
protected ?array $models = null;
18+
19+
public function completion(Request $request)
20+
{
21+
$request->validate([
22+
'model' => ['required', 'string'],
23+
]);
24+
25+
$sessionId = $this->setSessionId();
26+
27+
$model = $request->model;
28+
29+
try {
30+
// Check the agent and model
31+
if (! str_contains($model, '/')) {
32+
$agentClass = $this->getAgent($model);
33+
$model = '';
34+
} else {
35+
// Separate agent from model
36+
$result = explode('/', $model, 2);
37+
$agent = $result[0];
38+
39+
// Get agent class
40+
$agentClass = $this->getAgent($agent);
41+
$model = $result[1];
42+
}
43+
44+
$response = Completions::make($request, $agentClass, $model, $sessionId);
45+
46+
if ($response instanceof \Generator) {
47+
// Return SSE
48+
return response()->stream(function () use ($response) {
49+
foreach ($response as $chunk) {
50+
echo "event: chunk\n";
51+
echo 'data: '.json_encode($chunk)."\n\n";
52+
ob_flush();
53+
flush();
54+
}
55+
}, 200, [
56+
'Content-Type' => 'text/event-stream',
57+
'Cache-Control' => 'no-cache',
58+
'X-Accel-Buffering' => 'no',
59+
]);
60+
} else {
61+
return response()->json($response);
62+
}
63+
} catch (\Throwable $th) {
64+
Log::error($th);
65+
66+
return response()->json([
67+
'error' => $th->getMessage(),
68+
], 500);
69+
}
70+
71+
}
72+
73+
public function models()
74+
{
75+
$appName = config('laragent.app_name');
76+
foreach ($this->models as $model) {
77+
$models[] = [
78+
'id' => $model,
79+
'object' => 'model',
80+
'created' => 1753357877,
81+
'owned_by' => $appName,
82+
];
83+
}
84+
85+
return response()->json([
86+
'object' => 'list',
87+
'data' => $models,
88+
]);
89+
}
90+
91+
private function getAgent(string $agent)
92+
{
93+
foreach ($this->agents as $agentClass) {
94+
if (basename($agentClass) === $agent) {
95+
return $agentClass;
96+
}
97+
}
98+
throw new InvalidArgumentException('Invalid model name, expected format: agentName/model or AgentName');
99+
}
100+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace LarAgent\API\Completion\Controllers;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\Log;
7+
use InvalidArgumentException;
8+
use LarAgent\API\Completion\Traits\HasSessionId;
9+
use LarAgent\API\Completions;
10+
11+
abstract class SingleAgentController
12+
{
13+
use HasSessionId;
14+
15+
protected ?string $agentClass = null;
16+
17+
protected ?array $models = null;
18+
19+
public function completion(Request $request)
20+
{
21+
$request->validate([
22+
'model' => ['required', 'string'],
23+
]);
24+
25+
$sessionId = $this->setSessionId();
26+
27+
try {
28+
29+
// Check the model name
30+
if (! in_array($request->model, $this->models)) {
31+
throw new InvalidArgumentException('Invalid model name');
32+
}
33+
34+
$response = Completions::make($request, $this->agentClass, null, $sessionId);
35+
36+
if ($response instanceof \Generator) {
37+
// Return SSE
38+
return response()->stream(function () use ($response) {
39+
foreach ($response as $chunk) {
40+
echo "event: chunk\n";
41+
echo 'data: '.json_encode($chunk)."\n\n";
42+
ob_flush();
43+
flush();
44+
}
45+
}, 200, [
46+
'Content-Type' => 'text/event-stream',
47+
'Cache-Control' => 'no-cache',
48+
'X-Accel-Buffering' => 'no',
49+
]);
50+
} else {
51+
return response()->json($response);
52+
}
53+
} catch (\Throwable $th) {
54+
Log::error($th);
55+
56+
return response()->json([
57+
'error' => $th->getMessage(),
58+
], 500);
59+
}
60+
61+
}
62+
63+
public function models()
64+
{
65+
$appName = config('laragent.app_name');
66+
foreach ($this->models as $model) {
67+
$models[] = [
68+
'id' => $model,
69+
'object' => 'model',
70+
'created' => 1753357877,
71+
'owned_by' => $appName,
72+
];
73+
}
74+
75+
return response()->json([
76+
'object' => 'list',
77+
'data' => $models,
78+
]);
79+
}
80+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace LarAgent\API\Completion\Traits;
4+
5+
use Illuminate\Support\Str;
6+
7+
trait HasSessionId
8+
{
9+
protected function setSessionId()
10+
{
11+
return Str::random(10);
12+
}
13+
}

0 commit comments

Comments
 (0)