Skip to content

Commit eb3ad20

Browse files
Merge pull request #31 from bootstrapguru/dev-1.1.1
Dev 1.1.1 (Upcoming Release)
2 parents feaf587 + c7f4dee commit eb3ad20

File tree

11 files changed

+189
-50
lines changed

11 files changed

+189
-50
lines changed

app/Commands/DexorCommand.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ public function handle(): int
3333
return self::FAILURE;
3434
}
3535

36-
if ($this->option('new')) {
37-
$this->chatAssistant->createNewAssistant();
38-
}
36+
// Determine if the new assistant should be created
37+
$isNew = $this->option('new'); // Get the value of the --new option
3938

40-
$thread = $this->chatAssistant->createThread();
39+
// Pass the --new option value to the createThread method
40+
$thread = $this->chatAssistant->createThread($isNew); // Directly pass value to createThread method
4141

4242
while (true) {
43-
$message = ask('<span class="mt-1 mx-1">🍻:</span>');
43+
$message = ask('<span class="mt-1 mx-1">🍺:</span>');
4444

4545
if ($message === 'exit') {
4646
break;
@@ -55,4 +55,4 @@ public function handle(): int
5555

5656
return self::SUCCESS;
5757
}
58-
}
58+
}

app/Integrations/Ollama/Requests/ChatRequest.php

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
namespace App\Integrations\Ollama\Requests;
44

55
use App\Data\MessageData;
6+
use App\Data\ToolCallData;
7+
use App\Data\ToolFunctionData;
68
use App\Models\Thread;
9+
use Illuminate\Support\Str;
710
use JsonException;
811
use Saloon\Contracts\Body\HasBody;
912
use Saloon\Enums\Method;
@@ -31,27 +34,84 @@ public function defaultBody(): array
3134
{
3235
$assistant = $this->thread->project->assistant;
3336

34-
return [
37+
$body = [
3538
'model' => $assistant->model,
3639
'messages' => $this->formatMessages($assistant),
3740
'stream' => false,
3841
'raw' => true,
39-
'tools' => array_values($this->tools),
42+
'tools' => $this->formatTools(),
4043
];
44+
45+
46+
return $body;
4147
}
4248

4349
private function formatMessages($assistant): array
4450
{
4551
$systemMessage = [
4652
'role' => 'system',
47-
'content' => sprintf(
48-
'[INST]%s[/INST] [AVAILABLE_TOOLS]%s[/AVAILABLE_TOOLS]',
49-
$assistant->prompt,
50-
json_encode(array_values($this->tools))
51-
),
53+
'content' => $assistant->prompt,
5254
];
5355

54-
return [$systemMessage, ...$this->thread->messages->toArray()];
56+
$formattedMessages = [$systemMessage];
57+
58+
foreach ($this->thread->messages as $message) {
59+
$formattedMessage = [
60+
'role' => $message['role'],
61+
'content' => $message['content'],
62+
];
63+
64+
if (!empty($message['tool_calls'])) {
65+
$formattedMessage['tool_calls'] = $this->formatToolCalls($message['tool_calls']);
66+
}
67+
68+
$formattedMessages[] = $formattedMessage;
69+
}
70+
71+
return $formattedMessages;
72+
}
73+
74+
private function formatToolCalls($toolCalls): array
75+
{
76+
return array_map(function ($toolCall) {
77+
$function = $toolCall['function'];
78+
79+
// Ensure arguments is a JSON object string
80+
$arguments = is_string($function['arguments'])
81+
? json_decode($function['arguments'], true) // Decode string to ensure it's valid JSON
82+
: json_encode($function['arguments'], JSON_FORCE_OBJECT); // Encode array/object to JSON object
83+
84+
return [
85+
'id' => $toolCall['id'],
86+
'type' => 'function',
87+
'function' => [
88+
'name' => $function['name'],
89+
'arguments' => $arguments, // Always a JSON object
90+
],
91+
];
92+
}, $toolCalls);
93+
}
94+
95+
private function formatTools(): array
96+
{
97+
98+
$formattedTools = [];
99+
foreach ($this->tools as $key => $tool) {
100+
if (is_array($tool) && isset($tool['function'])) {
101+
$formattedTools[] = [
102+
'type' => 'function',
103+
'function' => [
104+
'name' => $tool['function']['name'] ?? $key,
105+
'description' => $tool['function']['description'] ?? '',
106+
'parameters' => $tool['function']['parameters'] ?? [],
107+
],
108+
];
109+
} else {
110+
}
111+
}
112+
113+
114+
return $formattedTools;
55115
}
56116

57117
/**
@@ -60,6 +120,34 @@ private function formatMessages($assistant): array
60120
public function createDtoFromResponse(Response $response): MessageData
61121
{
62122
$data = $response->json();
63-
return MessageData::from($data['message'] ?? []);
123+
$message = $data['message'] ?? [];
124+
$tools = collect();
125+
126+
if (isset($message['tool_calls'])) {
127+
foreach ($message['tool_calls'] as $toolCall) {
128+
$arguments = $toolCall['function']['arguments'];
129+
// Ensure arguments is a JSON string
130+
if (is_array($arguments)) {
131+
$arguments = json_encode($arguments, JSON_FORCE_OBJECT);
132+
} elseif (!is_string($arguments)) {
133+
$arguments = json_encode([$arguments], JSON_FORCE_OBJECT);
134+
}
135+
136+
$fn = ToolFunctionData::from([
137+
'name' => $toolCall['function']['name'],
138+
'arguments' => $arguments
139+
]);
140+
141+
$tools->push(ToolCallData::from([
142+
'id' => $toolCall['id'] ?? 'ollama-'.Str::random(10),
143+
'type' => 'function',
144+
'function' => $fn
145+
]));
146+
}
147+
148+
$message['tool_calls'] = $tools;
149+
}
150+
151+
return MessageData::from($message);
64152
}
65153
}

app/Integrations/OpenAI/OpenAIConnector.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Integrations\OpenAI;
44

5+
use App\Models\Assistant;
56
use Saloon\Http\Connector;
67
use Saloon\Traits\Plugins\AcceptsJson;
78
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
@@ -15,12 +16,20 @@ class OpenAIConnector extends Connector
1516

1617
protected int $requestTimeout = 120;
1718

19+
public function __construct(protected readonly string $service) {
20+
//
21+
}
22+
1823
/**
1924
* The Base URL of the API
2025
*/
2126
public function resolveBaseUrl(): string
2227
{
23-
return 'https://api.openai.com/v1';
28+
return match ($this->service) {
29+
'openai' => 'https://api.openai.com/v1',
30+
'deep_seek' => 'https://api.deepseek.com/v1',
31+
};
32+
2433
}
2534

2635
/**

app/Models/Assistant.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Assistant extends Model
1515
'description',
1616
'prompt',
1717
'tools',
18-
'service',
18+
'service'
1919
];
2020

2121
protected $casts = [

app/Services/ChatAssistant.php

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use function Laravel\Prompts\form;
2222
use function Laravel\Prompts\select;
2323
use function Laravel\Prompts\spin;
24+
use function Laravel\Prompts\text;
2425
use function Termwind\render;
2526

2627
class ChatAssistant
@@ -49,33 +50,41 @@ public function __construct(OnBoardingSteps $onBoardingSteps)
4950
* @throws FatalRequestException
5051
* @throws RequestException
5152
*/
52-
public function getCurrentProject(): Project
53+
public function getCurrentProject(bool $isNew): Project
5354
{
5455
$projectPath = getcwd();
5556
$project = Project::where('path', $projectPath)->first();
5657

57-
if ($project) {
58+
if ($isNew && $project) {
59+
// Update the existing project if isNew is true
60+
$project->assistant_id = $this->createNewAssistant()->id; // Update based on new assistant
61+
$project->save();
5862
return $project;
5963
}
6064

61-
$userChoice = select(
62-
label: 'No existing project found. Would you like to create a new assistant or use an existing one?',
63-
options: [
64-
'create_new' => 'Create New Assistant',
65-
'use_existing' => 'Use Existing Assistant',
66-
]
67-
);
65+
if (!$project) {
66+
// If there's no existing project, create a new one
67+
$userChoice = select(
68+
label: 'No project found. Would you like to create a new assistant or use an existing one?',
69+
options: [
70+
'create_new' => 'Create New Assistant',
71+
'use_existing' => 'Use Existing Assistant',
72+
]
73+
);
6874

69-
$assistantId = match ($userChoice) {
70-
'create_new' => $this->createNewAssistant()->id,
71-
'use_existing' => $this->selectExistingAssistant(),
72-
default => throw new Exception('Invalid choice'),
73-
};
75+
$assistantId = match ($userChoice) {
76+
'create_new' => $this->createNewAssistant()->id,
77+
'use_existing' => $this->selectExistingAssistant(),
78+
default => throw new Exception('Invalid choice'),
79+
};
7480

75-
return Project::create([
76-
'path' => $projectPath,
77-
'assistant_id' => $assistantId,
78-
]);
81+
return Project::create([
82+
'path' => $projectPath,
83+
'assistant_id' => $assistantId,
84+
]);
85+
}
86+
87+
return $project;
7988
}
8089

8190
/**
@@ -115,16 +124,16 @@ public function createNewAssistant(): Assistant
115124
'description' => $assistant['description'],
116125
'model' => $assistant['model'],
117126
'prompt' => $assistant['prompt'],
118-
'service' => $service,
127+
'service' => $service
119128
]);
120129
}
121130

122131
/**
123132
* @throws Exception
124133
*/
125-
public function createThread()
134+
public function createThread(bool $isNew): \App\Models\Thread
126135
{
127-
$project = $this->getCurrentProject();
136+
$project = $this->getCurrentProject($isNew);
128137
$latestThread = $project->threads()->latest()->first();
129138

130139
if ($latestThread && $this->shouldUseExistingThread()) {
@@ -161,6 +170,11 @@ public function getAnswer($thread, ?string $message): string
161170
$thread->load('messages');
162171

163172
$service = $thread->assistant->service;
173+
174+
if (!config("aiproviders.{$service}")) {
175+
throw new Exception("Service {$service} is not configured");
176+
}
177+
164178
$connector = $this->getConnector($service);
165179
$chatRequest = $this->getChatRequest($service, $thread);
166180

@@ -179,8 +193,18 @@ private function handleTools($thread, $message): string
179193
{
180194
$answer = $message->content;
181195

182-
$thread->messages()->create($message->toArray());
183-
if ($message->tool_calls !== null && $message->tool_calls->isNotEmpty()) {
196+
$messageData = [
197+
'role' => $message->role,
198+
'content' => $message->content,
199+
];
200+
201+
if (!empty($message->tool_calls)) {
202+
$messageData['tool_calls'] = $message->tool_calls;
203+
}
204+
205+
$thread->messages()->create($messageData);
206+
207+
if (!empty($message->tool_calls)) {
184208
$this->renderAnswer($answer);
185209

186210
foreach ($message->tool_calls as $toolCall) {
@@ -211,7 +235,7 @@ private function getModels(string $service): Collection
211235
$listModelsRequestClass = config("aiproviders.{$service}.listModelsRequest");
212236

213237
if ($listModelsRequestClass !== null) {
214-
$connector = new $connectorClass();
238+
$connector = new $connectorClass($service);
215239
return $connector->send(new $listModelsRequestClass())->dto();
216240
}
217241

@@ -255,7 +279,7 @@ private function shouldUseExistingThread(): bool
255279
private function getConnector(string $service): object
256280
{
257281
$connectorClass = config("aiproviders.{$service}.connector");
258-
return new $connectorClass();
282+
return new $connectorClass($service);
259283
}
260284

261285
private function getChatRequest(string $service, $thread): object
@@ -295,7 +319,6 @@ private function executeToolCall($thread, $toolCall): void
295319

296320
private function ensureAPIKey(string $service): void
297321
{
298-
$apiKeyConfigName = strtoupper($service).'_API_KEY';
299322
if (!config("aiproviders.{$service}.api_key")) {
300323
$this->onBoardingSteps->requestAPIKey($service);
301324
}

app/Services/Request/ChatRequest.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,23 @@ public function defaultBody(): array
4040
$messages = [[
4141
'role' => 'system',
4242
'content' => $assistant->prompt,
43-
],
44-
...$this->thread->messages,
45-
];
43+
]];
44+
45+
foreach ($this->thread->messages as $message) {
46+
$messages[] = [
47+
'role' => $message->role,
48+
'content' => $message->content,
49+
];
50+
51+
if ($message->tool_calls) {
52+
$messages[count($messages) - 1]['tool_calls'] = $message->tool_calls;
53+
}
54+
}
4655

4756
return [
4857
'model' => $assistant->model,
4958
'messages' => $messages,
5059
'tools' => array_values($this->tools),
5160
];
5261
}
53-
}
62+
}

app/Utils/OnBoardingSteps.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ private function configurationFileExists(): bool
5555
return true;
5656
}
5757

58+
/**
59+
* @throws Exception
60+
*/
5861
public function requestAPIKey(string $service): string
5962
{
6063
$apiKey = password(

builds/dexor

620 KB
Binary file not shown.

0 commit comments

Comments
 (0)