Skip to content

Commit

Permalink
Merge pull request #6 from alnutile/prism_002
Browse files Browse the repository at this point in the history
Prism 002
  • Loading branch information
alnutile authored Nov 5, 2024
2 parents 4c5ece5 + 64055c6 commit 4492f1b
Show file tree
Hide file tree
Showing 20 changed files with 568 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ["8.2"]
php-versions: ["8.3"]
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Project Assistant


This is the code used in the "PHP and LLMs" book long with [https://github.com/alnutile/php-llms](https://github.com/alnutile/php-llms)

This is a full working system with Automations, Projects, Tasks and more!
Expand Down
45 changes: 45 additions & 0 deletions app/Console/Commands/ClearAllHorizonQueues.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;

class ClearAllHorizonQueues extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'horizon:clear-all-horizon-queues';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear them all!';

/**
* Execute the console command.
*/
public function handle()
{
$queues = config('horizon.defaults');
foreach ($queues as $keyName => $queue) {
$names = data_get($queue, 'queue', []);
foreach ($names as $name) {
try {
$this->info("Clearing queue: $name");
Log::info("Clearing queue: $name");
Artisan::call('queue:clear --force --queue '.$name);
} catch (\Exception $e) {
$this->error($e->getMessage());
Log::error($e->getMessage().' '.$name);
}
}
}
}
}
2 changes: 1 addition & 1 deletion app/Jobs/FinalizeReportJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function handle(): void
$results
PROMPT;

$results = LlmDriverFacade::driver(config('llmdriver.driver'))
$results = LlmDriverFacade::driver('groq')
->completion($prompt);

$this->report->updateQuietly([
Expand Down
21 changes: 21 additions & 0 deletions app/Models/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use App\Domains\Campaigns\StatusEnum;
use App\Services\LlmServices\Requests\MessageInDto;
use App\Services\LlmServices\RoleEnum;
use EchoLabs\Prism\ValueObjects\Messages\AssistantMessage;
use EchoLabs\Prism\ValueObjects\Messages\UserMessage;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -147,6 +149,25 @@ public function addInput(

}

public function getPrismMessage(int $limit = 10): array
{
return $this->messages()
->limit($limit)
->whereIn('role', [
\App\Services\LlmServices\RoleEnum::User->value,
\App\Services\LlmServices\RoleEnum::Assistant->value,
])
->orderBy('id', 'desc')
->get()
->transform(function ($message) {
if ($message->role == \App\Services\LlmServices\RoleEnum::User) {
return new UserMessage($message->content);
} else {
return new AssistantMessage($message->content);
}
})->toArray();
}

public function getMessageThread(int $limit = 10): array
{
$latestMessages = $this->messages()
Expand Down
6 changes: 4 additions & 2 deletions app/Services/LlmServices/Functions/TaskList.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ public function handle(
Project $project,
array $args = []): FunctionResponse
{
Log::info('List Tasks called');
Log::info('List Tasks called', [
'args' => $args,
]);

$state = data_get($args, 'state', 'open');

$tasks = Task::where('project_id', $project->id)
->when($state === 'closed', function ($query) {
return $query->where('completed', true);
return $query->whereNotNull('completed_at');
})
->get();

Expand Down
126 changes: 126 additions & 0 deletions app/Services/LlmServices/Orchestration/PrismOrchestrate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace App\Services\LlmServices\Orchestration;

use App\Events\ScheduleLogEvent;
use App\Models\Project;
use App\Services\LlmServices\RoleEnum;
use App\Services\Prism\Tools\CreateTask;
use App\Services\Prism\Tools\SendEmailToTeam;
use App\Services\Prism\Tools\TaskList;
use EchoLabs\Prism\Facades\Tool;
use EchoLabs\Prism\Prism;
use EchoLabs\Prism\ValueObjects\Messages\AssistantMessage;
use Facades\App\Services\LlmServices\Orchestration\Orchestrate as OrchestrateFacade;
use Illuminate\Support\Facades\Log;

class PrismOrchestrate
{
protected bool $logScheduler = false;

public function setLogScheduler(bool $logScheduler): self
{
$this->logScheduler = $logScheduler;

return $this;
}

public function handle(Project $project,
string $prompt = '',
RoleEnum $role = RoleEnum::User): void
{
if (! empty($prompt)) {
$project->addInput(
message: $prompt,
role: $role,
user: (auth()->check()) ? auth()->user() : $project->user
);
}

$messages = $project->getPrismMessage(limit: 10);

$currentDateTime = sprintf('Current date and time: %s', now()->toDateTimeString());

$systemPrompt = $project->system_prompt;

$systemPrompt = <<<SYSTEM_PROMPT
## Current date and time: {$currentDateTime}
## System prompt:
$systemPrompt
SYSTEM_PROMPT;

$prism = Prism::text()
->withSystemPrompt($systemPrompt)
->withMessages($messages)
->using('anthropic', 'claude-3-5-sonnet-latest')
->withPrompt('List out my open tasks')
->withTools([
new TaskList($project),
new CreateTask($project),
new SendEmailToTeam($project),
])
->withMaxSteps(10)
->generate();

foreach ($prism->responseMessages as $message) {
if ($message instanceof AssistantMessage) {
Log::info('Prism Assistant Message', [
'content' => $message->content(),
]);
}
}

// $project->addInput(
// message: $response->content,
// role: RoleEnum::Assistant,
// );
//
// if ($this->logScheduler) {
// ScheduleLogEvent::dispatch($project, $project->toArray());
// }
//
// Log::info('Orchestration Tools Found', [
// 'tool_calls' => count($response->tool_calls),
// ]);
//
// if (! empty($response->tool_calls)) {
//
// Log::info('Orchestration Tools Found', [
// 'tool_calls' => collect($response->tool_calls)
// ->pluck('name')->toArray(),
// ]);
//
// $count = 1;
// foreach ($response->tool_calls as $tool_call) {
// Log::info('[LaraChain] - Tool Call '.$count, [
// 'tool_call' => $tool_call->name,
// 'tool_count' => count($response->tool_calls),
// ]);
//
// $tool = app()->make($tool_call->name);
//
// $functionResponse = $tool->handle($project, $tool_call->arguments);
//
// $project->addInput(
// message: sprintf('Tool %s used with results %s', $tool_call->name, $functionResponse->content),
// role: RoleEnum::User,
// tool_id: $tool_call->id,
// tool_name: $tool_call->name,
// tool_args: $tool_call->arguments,
// created_by_tool: true,
// );
//
// if ($this->logScheduler) {
// ScheduleLogEvent::dispatch($project, $project->toArray());
// }
//
// $count++;
// }
//
// Log::info('This Tools Complete doing sending it through again');
//
// OrchestrateFacade::handle($project);
// }
}
}
96 changes: 96 additions & 0 deletions app/Services/Prism/Tools/CreateTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace App\Services\Prism\Tools;

use App\Models\Project;
use App\Models\Task;
use EchoLabs\Prism\Schema\ArraySchema;
use EchoLabs\Prism\Schema\ObjectSchema;
use EchoLabs\Prism\Schema\StringSchema;
use EchoLabs\Prism\Tool;
use Illuminate\Support\Facades\Log;

class CreateTask extends Tool
{
public function __construct(public Project $project)
{
$taskSchema = new ObjectSchema(
name: 'task',
description: 'Task object',
properties: [
'name' => new StringSchema(
name: 'name',
description: 'Name of the task',
),
'details' => new StringSchema(
name: 'details',
description: 'Detailed info of the task',
),
'due_date' => new StringSchema(
name: 'due_date',
description: 'Due date if any format "Y-m-d"',
),
'assistant' => new StringSchema(
name: 'assistant',
description: 'Should the assistant be assigned this true or false',
),
'user_id' => new StringSchema(
name: 'user_id',
description: 'User id if assigned to a user',
),
],
requiredFields: ['name', 'details'],
);

$tasksParameter = new ArraySchema(
name: 'tasks',
description: 'an array of tasks objects',
item: $taskSchema,
);

$this
->as('create_tasks_tool')
->for('If the Project needs to have tasks created or the users prompt requires it you can use this tool to make multiple tasks')
->withParameter($tasksParameter)
->using($this);
}

public function __invoke(array $tasks): string
{

Log::info('PrismOrchestrate::create_tasks_tool called');

$tasksCreated = collect();

foreach ($tasks as $task) {

$tasksCreated->add(Task::updateOrCreate([
'name' => $task['name'],
'project_id' => $this->project->id,
],
[
'details' => $task['details'],
'due_date' => data_get($task, 'due_date'),
'assistant' => data_get($task, 'assistant', false),
])
);
}

return $tasksCreated
->map(function ($task) {
return sprintf(
"Task Name: %s\n".
"Task Id: %d\n".
"Due: %s\n".
"Completed At: %s\n".
"Details: \n%s\n",
$task->name,
$task->id,
$task->due_date ? $task->due_date->format('Y-m-d') : 'N/A',
$task->completed_at ? $task->completed_at->format('Y-m-d') : 'null',
$task->details
);
})
->implode("\n");
}
}
35 changes: 35 additions & 0 deletions app/Services/Prism/Tools/SendEmailToTeam.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Services\Prism\Tools;

use App\Models\Project;
use App\Notifications\DailyReport;
use EchoLabs\Prism\Tool;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;

class SendEmailToTeam extends Tool
{
public function __construct(public Project $project)
{
$this
->as('send_email_to_team')
->for('Send an email to the team')
->withStringParameter(name: 'message', description: 'The message for the body of the email', required: true)
->using($this);
}

public function __invoke(string $message): string
{
Log::info('PrismOrchestrate::send_email_to_team called');

$count = 0;

foreach ($this->project->team->users as $user) {
Notification::send($user, new DailyReport($message, $this->project));
$count++;
}

return sprintf('Sent email to the %d members of the team', $count);
}
}
Loading

0 comments on commit 4492f1b

Please sign in to comment.