Урок 09

Практика: документ-поиск

Соберём RAG-агента для поиска по документам.

Шаг 1: Миграция

Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536)->index();
    $table->timestamps();
});

Шаг 2: Модель

class Document extends Model
{
    protected $fillable = ['title', 'content', 'embedding'];

    protected function casts(): array
    {
        return [
            'embedding' => 'array',
        ];
    }
}

Шаг 3: Job для embedding

class ProcessDocument implements ShouldQueue
{
    use Dispatchable;

    public function __construct(public Document $document) {}

    public function handle(): void
    {
        $embedding = Str::of($this->document->content)->toEmbeddings();
        $this->document->update(['embedding' => $embedding]);
    }
}

Шаг 4: Агент

use Laravel\Ai\Tools\SimilaritySearch;
use Laravel\Ai\Concerns\RemembersConversations;

class DocumentSearch implements Agent, HasTools
{
    use Promptable, RemembersConversations;

    public function instructions(): string
    {
        return 'Ты — ассистент по документам. Отвечай на основе найденных фрагментов.';
    }

    public function tools(): iterable
    {
        return [
            SimilaritySearch::usingModel(Document::class, 'embedding')
                ->minSimilarity(0.4),
        ];
    }
}

Шаг 5: Загрузка

Route::post('/documents', function (Request $request) {
    $document = Document::create([
        'title' => $request->title,
        'content' => $request->content,
    ]);

    ProcessDocument::dispatch($document);

    return response()->json(['status' => 'processing']);
});

Шаг 6: Поиск

Route::post('/search', function (Request $request) {
    $response = (new DocumentSearch)->prompt($request->query);
    return response()->json(['answer' => (string) $response]);
});

Streaming вариант

Route::get('/search/stream', function (Request $request) {
    return (new DocumentSearch)
        ->stream($request->query)
        ->usingVercelDataProtocol();
});

Итоги

  • Миграция с vector
  • Job для embedding
  • Агент с SimilaritySearch
  • Route для загрузки и поиска
  • Streaming для UX