diff --git a/app/Domains/Sources/BaseSource.php b/app/Domains/Sources/BaseSource.php index ad1d044a..98de2a8f 100644 --- a/app/Domains/Sources/BaseSource.php +++ b/app/Domains/Sources/BaseSource.php @@ -18,11 +18,12 @@ use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Log; use LlmLaraHub\LlmDriver\LlmDriverFacade; +use LlmLaraHub\LlmDriver\ToolsHelper; use LlmLaraHub\TagFunction\Jobs\TagDocumentJob; abstract class BaseSource { - use ChatHelperTrait; + use ChatHelperTrait, ToolsHelper; public string $batchTitle = 'Chunking Source'; diff --git a/app/Domains/Sources/EmailSource.php b/app/Domains/Sources/EmailSource.php index 1d5091ba..ee4406b7 100644 --- a/app/Domains/Sources/EmailSource.php +++ b/app/Domains/Sources/EmailSource.php @@ -53,72 +53,73 @@ public function handle(Source $source): void $this->meta_data = $this->mailDto->toArray(); - Log::info('[LaraChain] - Running Email Source'); - - //use the users source prompt to create this next step - //track the results in a chat_id thread - //and as a message? - //then using the source the results will do something with the content - // and then delete the email - // but we can leave it if it returns false? - // still too much coding there how do I let the tool do it. - $prompt = PromptMerge::merge( ['[CONTEXT]'], [$this->content], $source->getPrompt() ); + Log::info('[LaraChain] - Running Email Source', [ + 'prompt' => $prompt, + ]); + + $chat = $source->chat; - $chat->addInput( - message: $prompt, - role: RoleEnum::User, - show_in_thread: true, - meta_data: MetaDataDto::from([ - 'driver' => $source->getDriver(), - 'source' => $source->title, - ]), - ); + $results = LlmDriverFacade::driver( $source->getDriver() )->completion($prompt); - $chat->addInput( - message: $results->content, - role: RoleEnum::Assistant, - show_in_thread: true, - meta_data: MetaDataDto::from([ - 'driver' => $source->getDriver(), - 'source' => $source->title, - ]), - ); - - //@TODO how to look for false - // surface this "power" into the UI. - // tag or store the fact we checked this emails - - $document = Document::updateOrCreate([ - 'source_id' => $source->id, - 'type' => TypesEnum::Email, - 'subject' => $this->mailDto->subject, - 'collection_id' => $source->collection_id, - ], [ - 'summary' => $results->content, - 'meta_data' => $this->mailDto->toArray(), - 'original_content' => $this->mailDto->body, - 'status_summary' => StatusEnum::Pending, - 'status' => StatusEnum::Pending, - ]); - - Bus::batch([new ChunkDocumentJob($document)]) - ->name("Processing Email {$this->mailDto->subject}") - ->allowFailures() - ->dispatch(); + if($this->ifNotActionRequired($results->content)) { + Log::info('[LaraChain] - Email Source Skipping', [ + 'prompt' => $prompt, + ]); + } else { + + $userMessage = $chat->addInput( + message: $prompt, + role: RoleEnum::User, + show_in_thread: true, + meta_data: MetaDataDto::from([ + 'driver' => $source->getDriver(), + 'source' => $source->title, + ]), + ); + + $document = Document::updateOrCreate([ + 'source_id' => $source->id, + 'type' => TypesEnum::Email, + 'subject' => $this->mailDto->subject, + 'collection_id' => $source->collection_id, + ], [ + 'summary' => $results->content, + 'meta_data' => $this->mailDto->toArray(), + 'original_content' => $this->mailDto->body, + 'status_summary' => StatusEnum::Pending, + 'status' => StatusEnum::Pending, + ]); + + Bus::batch([new ChunkDocumentJob($document)]) + ->name("Processing Email {$this->mailDto->subject}") + ->allowFailures() + ->dispatch(); + + $assistantMessage = $chat->addInput( + message: $results->content, + role: RoleEnum::Assistant, + show_in_thread: true, + meta_data: MetaDataDto::from([ + 'driver' => $source->getDriver(), + 'source' => $source->title, + ]), + ); + + $this->savePromptHistory( + message: $assistantMessage, + prompt: $prompt); + } - //should we delete the email? - // right now it gets set to seen - // on the MailDto we have options } } diff --git a/app/Helpers/ChatHelperTrait.php b/app/Helpers/ChatHelperTrait.php index 1ac3c767..344e0b78 100644 --- a/app/Helpers/ChatHelperTrait.php +++ b/app/Helpers/ChatHelperTrait.php @@ -33,4 +33,19 @@ public function getUserId(Collection $collection): ?int return $collection->team?->user_id; } + + public function ifNotActionRequired(string $results): bool { + // @NOTE llms sometimes do not return the right + // string for example. + // false becomes false, "false" or "False" etc. + + $results = str($results) + ->trim() + ->lower() + ->remove('"') + ->remove("'") + ->toString(); + + return $results == "false"; + } } diff --git a/app/Jobs/ChunkDocumentJob.php b/app/Jobs/ChunkDocumentJob.php index 643d4528..336a512f 100644 --- a/app/Jobs/ChunkDocumentJob.php +++ b/app/Jobs/ChunkDocumentJob.php @@ -2,7 +2,9 @@ namespace App\Jobs; +use App\Helpers\TextChunker; use App\Models\Document; +use App\Models\DocumentChunk; use Illuminate\Bus\Batch; use Illuminate\Bus\Batchable; use Illuminate\Bus\Queueable; @@ -10,7 +12,16 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Log; +use LlmLaraHub\LlmDriver\LlmDriverFacade; +use LlmLaraHub\TagFunction\Jobs\TagDocumentJob; +/** + * @NOTE + * Only really good for chunking content that can fit in the Original Content + * of a Document model. + */ class ChunkDocumentJob implements ShouldQueue { use Batchable; @@ -34,5 +45,62 @@ public function handle(): void return; } + + Log::info('[LaraChain] - Chunking Document', [ + 'document' => $this->document->id, + ]); + + $document = $this->document; + + $page_number = 0; + + $pageContent = $this->document->original_content; + + $size = config('llmdriver.chunking.default_size'); + + $chunked_chunks = TextChunker::handle($pageContent, $size); + + foreach ($chunked_chunks as $chunkSection => $chunkContent) { + $guid = md5($chunkContent); + + $DocumentChunk = DocumentChunk::updateOrCreate( + [ + 'document_id' => $document->id, + 'sort_order' => $page_number, + 'section_number' => $chunkSection, + ], + [ + 'guid' => $guid, + 'content' => to_utf8($chunkContent), + ] + ); + + $chunks[] = [ + new VectorlizeDataJob($DocumentChunk), + ]; + } + + $name = sprintf($document->type->name, $document->id); + + + + Bus::batch($chunks) + ->name($name) + ->allowFailures() + ->finally(function (Batch $batch) use ($document) { + + Bus::batch([ + [ + new SummarizeDocumentJob($document), + new TagDocumentJob($document), + new DocumentProcessingCompleteJob($document), + ] + ]) + ->name(sprintf("Final Document Steps Document %s id %d", $document->type->name, $document->id)) + ->allowFailures() + ->dispatch(); + }) + ->onQueue(LlmDriverFacade::driver($document->getDriver())->onQueue()) + ->dispatch(); } } diff --git a/resources/js/Pages/Sources/EmailSource/Components/Resources.vue b/resources/js/Pages/Sources/EmailSource/Components/Resources.vue index 3195660b..6c3555c3 100644 --- a/resources/js/Pages/Sources/EmailSource/Components/Resources.vue +++ b/resources/js/Pages/Sources/EmailSource/Components/Resources.vue @@ -9,7 +9,9 @@
-
diff --git a/tests/Feature/EmailSourceTest.php b/tests/Feature/EmailSourceTest.php index d23c7940..64154f4b 100644 --- a/tests/Feature/EmailSourceTest.php +++ b/tests/Feature/EmailSourceTest.php @@ -131,4 +131,49 @@ public function tests_creates_chat_and_message() Bus::assertBatchCount(1); } + + + public function test_no_action_required() + { + Bus::fake(); + $source = Source::factory()->create([ + 'slug' => 'test', + 'type' => SourceTypeEnum::EmailSource, + ]); + + LlmDriverFacade::shouldReceive('driver->completion')->once()->andReturn( + CompletionResponse::from([ + 'content' => 'False', + ]) + ); + + $body = <<<'BODY' +Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut.Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut. +Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut. +Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut. +Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut. +Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut. +Quis ea esse velit id id eu consectetur deserunt exercitation exercitation. Nisi aliqua ipsum fugiat laborum aliquip nostrud eu tempor non cillum Lorem non dolor proident sunt. Irure commodo aliqua reprehenderit deserunt sint irure in excepteur quis eiusmod ullamco aliquip. Dolore tempor ea non ut. + +BODY; + + $dto = MailDto::from([ + 'to' => 'info+12345@llmassistant.io', + 'from' => 'foo@var.com', + 'subject' => 'This is it', + 'header' => 'This is header', + 'body' => $body, + ]); + + $emailSource = new \App\Domains\Sources\EmailSource(); + $emailSource->setMailDto($dto)->handle($source); + + $this->assertDatabaseCount('documents', 0); + $this->assertDatabaseCount('chats', 1); + $this->assertDatabaseCount('messages', 1); + + $this->assertNotNull($source->chat_id); + + Bus::assertBatchCount(0); + } } diff --git a/tests/Feature/Jobs/ChunkDocumentJobTest.php b/tests/Feature/Jobs/ChunkDocumentJobTest.php index a5f716d7..983fa1fc 100644 --- a/tests/Feature/Jobs/ChunkDocumentJobTest.php +++ b/tests/Feature/Jobs/ChunkDocumentJobTest.php @@ -2,6 +2,9 @@ namespace Tests\Feature\Jobs; +use App\Jobs\ChunkDocumentJob; +use App\Models\Document; +use Illuminate\Support\Facades\Bus; use Tests\TestCase; class ChunkDocumentJobTest extends TestCase @@ -9,10 +12,22 @@ class ChunkDocumentJobTest extends TestCase /** * A basic feature test example. */ - public function test_example(): void + public function test_chunking(): void { - $response = $this->get('/'); + Bus::fake(); - $response->assertStatus(200); + $document = Document::factory()->create(); + + $this->assertDatabaseCount('document_chunks', 0); + + + [$job, $batch] = (new ChunkDocumentJob($document))->withFakeBatch(); + + + $job->handle(); + + $this->assertDatabaseCount('document_chunks', 1); + + Bus::assertBatchCount(1); } }