Skip to content
Merged
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
198 changes: 198 additions & 0 deletions app/Commands/EnhanceWorkerCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php

declare(strict_types=1);

namespace App\Commands;

use App\Services\EnhancementQueueService;
use App\Services\OllamaService;
use App\Services\QdrantService;
use LaravelZero\Framework\Commands\Command;

use function Laravel\Prompts\error;
use function Laravel\Prompts\info;
use function Laravel\Prompts\table;
use function Laravel\Prompts\warning;

class EnhanceWorkerCommand extends Command
{
protected $signature = 'enhance:worker
{--once : Process one item and exit}
{--status : Show queue status and exit}';

protected $description = 'Process the enhancement queue using Ollama';

public function handle(
EnhancementQueueService $queue,
OllamaService $ollama,
QdrantService $qdrant,
): int {
if ((bool) $this->option('status')) {
return $this->showStatus($queue);
}

if (! $ollama->isAvailable()) {
warning('Ollama is not available. Skipping enhancement processing.');

return self::SUCCESS;
}

$processOnce = (bool) $this->option('once');

if ($processOnce) {
return $this->processOne($queue, $ollama, $qdrant);
}

return $this->processAll($queue, $ollama, $qdrant);
}

private function processOne(
EnhancementQueueService $queue,
OllamaService $ollama,
QdrantService $qdrant,
): int {
$item = $queue->dequeue();

if ($item === null) {
info('Enhancement queue is empty.');

return self::SUCCESS;
}

return $this->processItem($item, $queue, $ollama, $qdrant);
}

private function processAll(
EnhancementQueueService $queue,
OllamaService $ollama,
QdrantService $qdrant,
): int {
$pending = $queue->pendingCount();

if ($pending === 0) {
info('Enhancement queue is empty.');

return self::SUCCESS;
}

info("Processing {$pending} entries in enhancement queue...");

$processed = 0;
$failed = 0;

while (($item = $queue->dequeue()) !== null) {
$result = $this->processItem($item, $queue, $ollama, $qdrant);

if ($result === self::SUCCESS) {
$processed++;
} else {
$failed++;
}
}

info("Enhancement complete: {$processed} processed, {$failed} failed.");

return $failed > 0 ? self::FAILURE : self::SUCCESS;
}

/**
* @param array{entry_id: string|int, title: string, content: string, category?: string|null, tags?: array<string>, project: string, queued_at: string} $item
*/
private function processItem(
array $item,
EnhancementQueueService $queue,
OllamaService $ollama,
QdrantService $qdrant,
): int {
$entryId = $item['entry_id'];
$project = $item['project'];

$this->line("Enhancing entry: {$item['title']}");

$enhancement = $ollama->enhance([
'title' => $item['title'],
'content' => $item['content'],
'category' => $item['category'] ?? null,
'tags' => $item['tags'] ?? [],
]);

if ($enhancement['tags'] === [] && $enhancement['summary'] === '') {
$queue->recordFailure("Empty enhancement response for entry {$entryId}");
error("Failed to enhance entry {$entryId}: empty response");

return self::FAILURE;
}

$fields = $this->buildUpdateFields($enhancement, $item);

$success = $qdrant->updateFields($entryId, $fields, $project);

if (! $success) {
$queue->recordFailure("Failed to update Qdrant for entry {$entryId}");
error("Failed to store enhancement for entry {$entryId}");

return self::FAILURE;
}

$queue->recordSuccess();
$this->line("<fg=green>Enhanced entry: {$item['title']}</>");

return self::SUCCESS;
}

/**
* Build the fields to update from enhancement results.
*
* @param array{tags: array<string>, category: string|null, concepts: array<string>, summary: string} $enhancement
* @param array{entry_id: string|int, title: string, content: string, category?: string|null, tags?: array<string>, project: string, queued_at: string} $item
* @return array<string, mixed>
*/
private function buildUpdateFields(array $enhancement, array $item): array
{
$fields = [
'enhanced' => true,
'enhanced_at' => now()->toIso8601String(),
];

// Merge AI tags with existing tags (deduplicated)
$existingTags = $item['tags'] ?? [];
$allTags = array_values(array_unique(array_merge($existingTags, $enhancement['tags'])));
$fields['tags'] = $allTags;

// Only set category if not already set
if (($item['category'] ?? null) === null && $enhancement['category'] !== null) {
$fields['category'] = $enhancement['category'];
}

if ($enhancement['concepts'] !== []) {
$fields['concepts'] = $enhancement['concepts'];
}

if ($enhancement['summary'] !== '') {
$fields['summary'] = $enhancement['summary'];
}

return $fields;
}

private function showStatus(EnhancementQueueService $queue): int
{
$status = $queue->getStatus();

info('Enhancement Queue Status');

table(
['Field', 'Value'],
[
['Status', $status['status']],
['Pending', (string) $status['pending']],
['Processed', (string) $status['processed']],
['Failed', (string) $status['failed']],
['Last Processed', $status['last_processed'] ?? 'Never'],
['Last Error', $status['last_error'] ?? 'None'],
]
);

return self::SUCCESS;
}
}
13 changes: 11 additions & 2 deletions app/Commands/KnowledgeAddCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Commands;

use App\Exceptions\Qdrant\DuplicateEntryException;
use App\Services\EnhancementQueueService;
use App\Services\GitContextService;
use App\Services\QdrantService;
use App\Services\WriteGateService;
Expand Down Expand Up @@ -36,7 +37,8 @@ class KnowledgeAddCommand extends Command
{--branch= : Git branch name}
{--commit= : Git commit hash}
{--no-git : Skip automatic git context detection}
{--force : Skip write gate and duplicate detection}';
{--force : Skip write gate and duplicate detection}
{--skip-enhance : Skip queueing for Ollama enhancement}';

protected $description = 'Add a new knowledge entry';

Expand All @@ -46,7 +48,7 @@ class KnowledgeAddCommand extends Command

private const VALID_STATUSES = ['draft', 'validated', 'deprecated'];

public function handle(GitContextService $gitService, QdrantService $qdrant, WriteGateService $writeGate): int
public function handle(GitContextService $gitService, QdrantService $qdrant, WriteGateService $writeGate, EnhancementQueueService $enhancementQueue): int
{
/** @var string $title */
$title = (string) $this->argument('title');
Expand Down Expand Up @@ -82,6 +84,8 @@ public function handle(GitContextService $gitService, QdrantService $qdrant, Wri
$noGit = (bool) $this->option('no-git');
/** @var bool $force */
$force = (bool) $this->option('force');
/** @var bool $skipEnhance */
$skipEnhance = (bool) $this->option('skip-enhance');

// Validate required fields
if ($content === null || $content === '') {
Expand Down Expand Up @@ -181,6 +185,11 @@ public function handle(GitContextService $gitService, QdrantService $qdrant, Wri
return $this->handleDuplicate($e, $data, $qdrant, (int) $confidence);
}

// Queue for Ollama enhancement unless skipped
if (! $skipEnhance && (bool) config('search.ollama.enabled', true)) {
$enhancementQueue->queue($data);
}

info('Knowledge entry created!');

$this->displayEntryTable($id, $title, $category, $priority, (int) $confidence, $data['tags'] ?? null);
Expand Down
36 changes: 33 additions & 3 deletions app/Commands/KnowledgeShowCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Commands;

use App\Services\EnhancementQueueService;
use App\Services\EntryMetadataService;
use App\Services\QdrantService;
use LaravelZero\Framework\Commands\Command;
Expand All @@ -19,7 +20,7 @@ class KnowledgeShowCommand extends Command

protected $description = 'Display full details of a knowledge entry';

public function handle(QdrantService $qdrant, EntryMetadataService $metadata): int
public function handle(QdrantService $qdrant, EntryMetadataService $metadata, EnhancementQueueService $enhancementQueue): int
{
$id = $this->argument('id');

Expand All @@ -46,7 +47,7 @@ public function handle(QdrantService $qdrant, EntryMetadataService $metadata): i

$qdrant->incrementUsage($id);

$this->renderEntry($entry, $metadata);
$this->renderEntry($entry, $metadata, $enhancementQueue);

// Show supersession history
$history = $qdrant->getSupersessionHistory($id);
Expand All @@ -58,7 +59,7 @@ public function handle(QdrantService $qdrant, EntryMetadataService $metadata): i
/**
* @param array<string, mixed> $entry
*/
private function renderEntry(array $entry, EntryMetadataService $metadata): void
private function renderEntry(array $entry, EntryMetadataService $metadata, EnhancementQueueService $enhancementQueue): void
{
$this->newLine();

Expand Down Expand Up @@ -111,6 +112,17 @@ private function renderEntry(array $entry, EntryMetadataService $metadata): void
$rows[] = ['Superseded Reason', $entry['superseded_reason'] ?? 'N/A'];
}

// Enhancement status
$rows[] = ['Enhanced', $this->enhancementStatus($entry, $enhancementQueue)];

if (isset($entry['concepts']) && $entry['concepts'] !== []) {
$rows[] = ['Concepts', implode(', ', $entry['concepts'])];
}

if (isset($entry['summary']) && $entry['summary'] !== '') {
$rows[] = ['AI Summary', $entry['summary']];
}

table(['Field', 'Value'], $rows);

$this->newLine();
Expand Down Expand Up @@ -147,6 +159,24 @@ private function renderSupersessionHistory(array $entry, array $history): void
}
}

/**
* @param array<string, mixed> $entry
*/
private function enhancementStatus(array $entry, EnhancementQueueService $enhancementQueue): string
{
if (isset($entry['enhanced']) && $entry['enhanced'] === true) {
$enhancedAt = $entry['enhanced_at'] ?? 'Unknown';

return "<fg=green>Yes</> <fg=gray>({$enhancedAt})</>";
}

if ($enhancementQueue->isQueued($entry['id'])) {
return '<fg=yellow>Pending</>';
}

return '<fg=gray>No</>';
}

private function colorize(string $text, string $color): string
{
return "<fg={$color}>{$text}</>";
Expand Down
10 changes: 10 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
use App\Contracts\HealthCheckInterface;
use App\Services\DailyLogService;
use App\Services\DeletionTracker;
use App\Services\EnhancementQueueService;
use App\Services\HealthCheckService;
use App\Services\KnowledgeCacheService;
use App\Services\KnowledgePathService;
use App\Services\OdinSyncService;
use App\Services\OllamaService;
use App\Services\QdrantService;
use App\Services\RuntimeEnvironment;
use App\Services\StubEmbeddingService;
Expand Down Expand Up @@ -165,5 +167,13 @@ public function register(): void

// Health check service for service status commands
$this->app->singleton(HealthCheckInterface::class, fn (): HealthCheckService => new HealthCheckService);

// Ollama service
$this->app->singleton(OllamaService::class, fn (): \App\Services\OllamaService => new OllamaService);

// Enhancement queue service
$this->app->singleton(EnhancementQueueService::class, fn ($app): \App\Services\EnhancementQueueService => new EnhancementQueueService(
$app->make(KnowledgePathService::class)
));
}
}
Loading
Loading