Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,19 @@ public function handle(Branch $branch, File $file, int $order): void
);
} else {
$completion = $llm->describeFile($project, $file, PromptRequestType::DOCUMENT_FILE);
$branch->systemComponents()->updateOrCreate([
'path' => $file->path,
], [
'order' => $order,
'sha' => $file->sha,
'path' => $file->path,
'markdown_docs' => $this->generateMarkdown(json_decode($completion->completion, true), $file->path),
'file_contents' => $team->stores_code ? $file->contents() : null,
'json_docs' => json_decode($completion->completion, true),
'status' => SystemComponentStatus::Generated,
]);
}
$branch->systemComponents()->updateOrCreate([
'path' => $file->path,
], [
'order' => $order,
'sha' => $file->sha,
'path' => $file->path,
'markdown_docs' => $this->generateMarkdown(json_decode($completion->completion, true), $file->path),
'file_contents' => $team->stores_code ? $file->contents() : null,
'json_docs' => json_decode($completion->completion, true),
'status' => SystemComponentStatus::Generated,
]);


ProcessingLogEntry::write($branch, $file->path, class_basename($llm), $llm->modelName(), $completion);
// @codeCoverageIgnoreStart
Expand All @@ -96,7 +97,9 @@ public function handle(Branch $branch, File $file, int $order): void
private function generateMarkdown(?array $completion, $path): ?string
{
if(!$completion) {
// @codeCoverageIgnoreStart
return null;
// @codeCoverageIgnoreEnd
}

$completion = FormatterHelper::convertArrayKeysToLowerCase($completion);
Expand Down
34 changes: 34 additions & 0 deletions app/Actions/Helper/GetPromptTokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Actions\Helper;

use Illuminate\Console\Command;
use Lorisleiva\Actions\Concerns\AsAction;
use Yethee\Tiktoken\EncoderProvider;

class GetPromptTokens
{
use AsAction;

public string $commandSignature = 'helper:get-prompt-tokens {model} {prompt}';


public function handle(string $model, string $prompt): int
{
$provider = new EncoderProvider();
$encoder = $provider->getForModel($model);
$tokens = $encoder->encode($prompt);
return count($tokens);
}

// @codeCoverageIgnoreStart
public function asCommand(Command $command): void
{
$model = $command->argument('model');
$prompt = $command->argument('prompt');

$tokensCount = $this->handle($model, $prompt);
$command->info($tokensCount);
}
// @codeCoverageIgnoreEnd
}
17 changes: 17 additions & 0 deletions app/Actions/Helper/GetTotalPromptRequestTokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Actions\Helper;

use Lorisleiva\Actions\Concerns\AsAction;

class GetTotalPromptRequestTokens
{
use AsAction;

public function handle(string $userPrompt, string $systemPrompt, string $model): int
{
$userPromptTokens = GetPromptTokens::run($model, $userPrompt);
$systemPromptTokens = GetPromptTokens::run($model, $systemPrompt);
return $userPromptTokens + $systemPromptTokens;
}
}
16 changes: 16 additions & 0 deletions app/LLM/Contracts/CanSupportPartialRequestInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\LLM\Contracts;

use App\LLM\DTO\CompletionResponse;

interface CanSupportPartialRequestInterface

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Laravel's convention is to not use the word Interface, and that the interface is like an action. i.e. SupportsPartialRequests, what do u think?

{
public function handlePartialCompletionResponse(PromptRequest $promptRequest, string $partialPromptConst): ?CompletionResponse;

public function getTotalPromptTokens(string $userPrompt, string $systemPrompt): int;

public function getMaxAmountOfTokens(): ?int;

public function makeRequest(string $prompt, string $systemPrompt, ?string $previousAnswer = null): ?CompletionResponse;
}
16 changes: 16 additions & 0 deletions app/LLM/Contracts/Llm.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use App\LLM\PromptRequests\PromptRequestType;
use App\Models\Project;
use App\SourceCode\DTO\File;
use App\Traits\HasFileDTO;
use App\Traits\HasProject;

abstract class Llm
{
Expand All @@ -18,9 +20,23 @@ abstract public function getPromptRequest(PromptRequestType $promptRequestType):
public function describeFile(Project $project, File $file, PromptRequestType $promptRequestType): CompletionResponse
{
$promptRequest = $this->getPromptRequest($promptRequestType);
$this->handleClassTraits($promptRequest, $project, $file);
$system = $promptRequest->systemPrompt($project, $file);
$user = $promptRequest->userPrompt($project, $file);

return $this->completion($system, $user);
}

private function handleClassTraits(&$promptRequest, $project, $file)
{
$classTraits = class_uses($promptRequest);

if(in_array(HasProject::class, $classTraits)) {
$promptRequest->setProject($project);
}

if(in_array(HasFileDTO::class, $classTraits)) {
$promptRequest->setFile($file);
}
}
}
29 changes: 27 additions & 2 deletions app/LLM/DumbLocalLlm.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,38 @@

namespace App\LLM;

use App\Actions\Helper\GetTotalPromptRequestTokens;
use App\LLM\Contracts\CanSupportPartialRequestInterface;
use App\LLM\Contracts\Llm;
use App\LLM\Contracts\PromptRequest;
use App\LLM\DTO\CompletionResponse;
use App\LLM\PromptRequests\DumpLlm\DumpLlmPromptRequest;
use App\LLM\PromptRequests\DumpLlm\DumpLlmPromptTechStackRequest;
use App\LLM\PromptRequests\PromptRequestType;
use App\Traits\CanSupportPartialRequest;

class DumbLocalLlm extends Llm
class DumbLocalLlm extends Llm implements CanSupportPartialRequestInterface
{
use CanSupportPartialRequest;

public ?PromptRequest $promptRequest = null;

public function getPromptRequest(PromptRequestType $promptRequestType): PromptRequest
{
return match ($promptRequestType) {
$this->promptRequest = match ($promptRequestType) {
PromptRequestType::TECH_STACK => new DumpLlmPromptTechStackRequest(),
default => new DumpLlmPromptRequest()
};

return $this->promptRequest;
}

public function completion(string $systemPrompt, string $userPrompt): CompletionResponse
{
return $this->handlePartialCompletionResponse($this->promptRequest, 'lorem ipsum');
}

public function makeRequest(string $prompt, string $systemPrompt, ?string $previousAnswer = null): ?CompletionResponse
{
return CompletionResponse::make(
completion: $this->getCompletionResponse($systemPrompt),
Expand All @@ -30,6 +44,17 @@ public function completion(string $systemPrompt, string $userPrompt): Completion
);
}

public function getMaxAmountOfTokens(): ?int
{
return 1;
}


public function getTotalPromptTokens(string $userPrompt, string $systemPrompt): int
{
return GetTotalPromptRequestTokens::run($userPrompt, $systemPrompt, config('services.openai.completion_model'));
}

public function modelName(): string
{
return 'dumb';
Expand Down
96 changes: 76 additions & 20 deletions app/LLM/OpenAI.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,36 @@

namespace App\LLM;

use App\Actions\Helper\GetTotalPromptRequestTokens;
use App\LLM\Contracts\CanSupportPartialRequestInterface;
use App\LLM\Contracts\HasApiKey;
use App\LLM\Contracts\Llm;
use App\LLM\Contracts\PromptRequest;
use App\LLM\DTO\CompletionResponse;
use App\LLM\PromptRequests\OpenAI\DocumentFilePromptRequest;
use App\LLM\PromptRequests\OpenAI\GenerateTechStackPromptRequest;
use App\LLM\PromptRequests\PromptRequestType;
use App\Traits\CanSupportPartialRequest;
use OpenAI\Client;

class OpenAI extends Llm implements HasApiKey
class OpenAI extends Llm implements HasApiKey, CanSupportPartialRequestInterface
{
use CanSupportPartialRequest;

private ?string $key = null;

private ?string $model = null;

public ?PromptRequest $promptRequest = null;

public function getPromptRequest(PromptRequestType $promptRequestType): PromptRequest
{
return match ($promptRequestType) {
$this->promptRequest = match ($promptRequestType) {
PromptRequestType::DOCUMENT_FILE => new DocumentFilePromptRequest(),
PromptRequestType::TECH_STACK => new GenerateTechStackPromptRequest(),
};

return $this->promptRequest;
}

public function withModel(string $model): static
Expand All @@ -37,10 +46,59 @@ public function modelName(): string
return $this->model ?? config('services.openai.completion_model');
}

public function checkApiKey(string $key): bool
{
try {
$this->client($key)->models()->list();
} catch (\Exception $e) {
return false;
}

return true;
}

public function usingApiKey(?string $key): static
{
$this->key = $key;

return $this;
}

private function client(?string $key = null): Client
{
return (new \OpenAI\Factory())
->withApiKey($key ?? $this->key ?? config('openai.api_key'))
->make();
}

public function completion(string $systemPrompt, string $userPrompt): CompletionResponse
{
if(app()->environment('testing')) {
return $this->makeRequest($userPrompt, $systemPrompt);
}

// @codeCoverageIgnoreStart
$const = $this->getConstPrompt();

$completionResponse = $this->handlePartialCompletionResponse($this->promptRequest, $const);

if($completionResponse) {
return $completionResponse;
}

return $this->makeRequest($userPrompt, $systemPrompt);

// @codeCoverageIgnoreEnd
}

public function makeRequest(string $prompt, string $systemPrompt, ?string $previousAnswer = null): ?CompletionResponse
{
$start = intval(microtime(true) * 1000);
// wrapping on a retry function to avoid the limit per minute error
if($previousAnswer) {
// @codeCoverageIgnoreStart
$prompt .= 'An this was the previous answer: '.$previousAnswer;
// @codeCoverageIgnoreEnd
}
$response = retry(3, fn () => $this->client()->chat()->create([
'model' => $this->modelName(),
'response_format' => [ "type" => "json_object" ],
Expand All @@ -51,12 +109,13 @@ public function completion(string $systemPrompt, string $userPrompt): Completion
],
[
'role' => 'user',
'content' => $userPrompt,
'content' => $prompt,
],
],
'stop' => ['-----', "\nEND"],
]), 61500);
$end = intval(microtime(true) * 1000);
$previousAnswer = $response->choices[0]->message->content;
return CompletionResponse::make(
completion: $response->choices[0]->message->content,
processingTimeMilliseconds: $end - $start,
Expand All @@ -66,28 +125,25 @@ public function completion(string $systemPrompt, string $userPrompt): Completion
);
}

public function checkApiKey(string $key): bool
// @codeCoverageIgnoreStart
public function getTotalPromptTokens(string $userPrompt, string $systemPrompt): int
{
try {
$this->client($key)->models()->list();
} catch (\Exception $e) {
return false;
}

return true;
return GetTotalPromptRequestTokens::run($userPrompt, $systemPrompt, $this->modelName());
}

public function usingApiKey(?string $key): static
public function getMaxAmountOfTokens(): ?int
{
$this->key = $key;

return $this;
$tokens = config('services.openai.tokens');
return isset($tokens[$this->modelName()])? $tokens[$this->modelName()] : null;
}

private function client(?string $key = null): Client
private function getConstPrompt(): ?string
{
return (new \OpenAI\Factory())
->withApiKey($key ?? $this->key ?? config('openai.api_key'))
->make();
return match(true) {
$this->promptRequest instanceof GenerateTechStackPromptRequest => 'You are writing documentation for a file in a project with this name: '. $this->promptRequest->project?->name. '. These are the file dependencies of the project: ',
$this->promptRequest instanceof DocumentFilePromptRequest => 'You are writing documentation for a file in a project with this name: '. $this->promptRequest->project?->name. '. These is the content of the file: '
};
}
// @codeCoverageIgnoreEnd

}
4 changes: 4 additions & 0 deletions app/LLM/PromptRequests/DumpLlm/DumpLlmPromptRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
use App\LLM\Contracts\PromptRequest;
use App\Models\Project;
use App\SourceCode\DTO\File;
use App\Traits\HasFileDTO;
use App\Traits\HasProject;

class DumpLlmPromptRequest implements PromptRequest
{
use HasProject, HasFileDTO;

public function systemPrompt(Project $project, File $file): string
{
return 'Lorem ipsum dolor sit amet';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
use App\LLM\PromptRequests\PromptRequestType;
use App\Models\Project;
use App\SourceCode\DTO\File;
use App\Traits\HasFileDTO;
use App\Traits\HasProject;

class DumpLlmPromptTechStackRequest implements PromptRequest
{
use HasFileDTO, HasProject;

public function systemPrompt(Project $project, File $file): string
{
return PromptRequestType::TECH_STACK->value;
Expand Down
Loading