Кроки потокової передачі
Кроки потокової передачі дають змогу робочому процесу поступово створювати вихідні дані. Кожен переданий фрагмент одразу записується в базу даних, тож якщо крок завершується помилкою посеред потоку, уже збережені фрагменти залишаються доступними для повторної спроби.
Це корисно для потокових відповідей LLM, обробки великих файлів і будь-яких сценаріїв, де не можна втрачати частковий прогрес.
Інтерфейс StreamingStep
Крок потокової передачі реалізує Queuety\Contracts\StreamingStep:
namespace Queuety\Contracts;
interface StreamingStep {
public function stream( array $state, array $existing_chunks = [] ): \Generator;
public function on_complete( array $chunks, array $state ): array;
public function config(): array;
}
| Метод | Опис |
|---|---|
stream( $state, $existing_chunks ) |
Генератор, що передає рядкові фрагменти. Кожне передавання одразу запускає запис у БД. |
on_complete( $chunks, $state ) |
Викликається після завершення потоку. Отримує всі фрагменти (попередні й нові) та повертає дані для об’єднання зі станом робочого процесу. |
config() |
Необов’язкова конфігурація. Підтримує ключі max_attempts і backoff; для backoff задається назва стратегії (exponential, linear, fixed), оскільки список затримок наразі відкидається (#16). |
Як зберігаються фрагменти
Щоразу, коли ваш генератор передає значення, Queuety викликає ChunkStore::append_chunk(), щоб записати його в таблицю queuety_chunks. Пакетного запису чи буферизації немає. Одне передавання — один INSERT.
У рядку фрагмента зберігаються job_id, workflow_id, step_index, chunk_index (відлік від нуля) і рядок content.
Можливість відновлення
Якщо після помилки крок потокової передачі запускається повторно, виконавець завантажує всі раніше збережені фрагменти й передає їх як параметр $existing_chunks. Спосіб відновлення визначає ваша реалізація:
- Пропускайте вже оброблені елементи, перевіряючи кількість наявних фрагментів
- Зміщуйте позицію в розбитому на сторінки API, використовуючи кількість фрагментів як зміщення сторінки
- Починайте спочатку, повністю ігноруючи
$existing_chunks(фрагменти з попередньої спроби все одно будуть додані на початок)
public function stream( array $state, array $existing_chunks = [] ): \Generator {
$offset = count( $existing_chunks );
$pages = $this->client->paginate( $state['query'], offset: $offset );
foreach ( $pages as $page ) {
yield json_encode( $page );
}
}
Сигнали активності під час потокової передачі
Тривалі потоки мають надсилати сигнали активності, щоб запобігти повторному призначенню завдання детектором застарілих завдань. Викликайте Heartbeat::beat() у циклі генератора:
use Queuety\Heartbeat;
public function stream( array $state, array $existing_chunks = [] ): \Generator {
foreach ( $this->client->stream( $state['prompt'] ) as $i => $chunk ) {
Heartbeat::beat( [ 'chunks' => $i + 1 ] );
yield $chunk;
}
}
Докладніше див. у розділі Сигнали активності про інтерфейс Heartbeat API.
Очищення фрагментів
Коли потік успішно завершується й on_complete() повертає результат, виконавець викликає ChunkStore::clear_chunks(), щоб видалити всі фрагменти завдання. Об’єднаний результат із on_complete() є надійним записом. Фрагменти тимчасові за задумом.
Якщо крок завершується помилкою і запускається повторно, фрагменти не видаляються. Під час наступної спроби їх знову передають через $existing_chunks.
Повний приклад: потокова передача LLM
use Queuety\Contracts\StreamingStep;
use Queuety\Heartbeat;
class StreamLLMResponse implements StreamingStep {
public function stream( array $state, array $existing_chunks = [] ): \Generator {
// If we have existing chunks from a previous attempt, we can skip
// re-requesting. The on_complete method will concatenate everything.
if ( ! empty( $existing_chunks ) ) {
return;
}
$response = OpenAI::chat()->createStreamed( [
'model' => 'gpt-4',
'messages' => $state['messages'],
] );
foreach ( $response as $i => $chunk ) {
$text = $chunk->choices[0]->delta->content ?? '';
if ( '' !== $text ) {
Heartbeat::beat( [ 'tokens' => $i ] );
yield $text;
}
}
}
public function on_complete( array $chunks, array $state ): array {
return [
'llm_response' => implode( '', $chunks ),
];
}
public function config(): array {
return [ 'max_attempts' => 5 ];
}
}
Використовуйте це в робочому процесі так само, як будь-який інший крок:
use Queuety\Queuety;
Queuety::workflow( 'ai_chat' )
->then( BuildPromptHandler::class )
->then( StreamLLMResponse::class )
->then( SaveResponseHandler::class )
->dispatch( [ 'user_input' => $input ] );
Повний приклад: обробка файлів
use Queuety\Contracts\StreamingStep;
use Queuety\Heartbeat;
class ProcessCSVRows implements StreamingStep {
public function stream( array $state, array $existing_chunks = [] ): \Generator {
$file = fopen( $state['csv_path'], 'r' );
$offset = count( $existing_chunks );
$row = 0;
// Skip the header.
fgetcsv( $file );
while ( $line = fgetcsv( $file ) ) {
$row++;
// Skip rows already processed in a previous attempt.
if ( $row <= $offset ) {
continue;
}
$result = $this->process_row( $line );
Heartbeat::beat( [ 'row' => $row ] );
yield json_encode( $result );
}
fclose( $file );
}
public function on_complete( array $chunks, array $state ): array {
$results = array_map( 'json_decode', $chunks );
return [
'processed_count' => count( $results ),
'results' => $results,
];
}
public function config(): array {
return [];
}
private function process_row( array $line ): array {
// Your row processing logic here.
return [ 'id' => $line[0], 'status' => 'ok' ];
}
}
Клас ChunkStore API
Клас ChunkStore керує збереженням фрагментів. Отримати доступ до нього можна через Queuety::chunk_store().
$store = Queuety::chunk_store();
| Метод | Повертає | Опис |
|---|---|---|
get_chunks( int $job_id ) |
string[] |
Отримує всі фрагменти завдання, упорядковані за індексом фрагмента |
append_chunk( int $job_id, int $chunk_index, string $content, ?int $workflow_id, ?int $step_index ) |
void |
Додає один фрагмент |
clear_chunks( int $job_id ) |
void |
Видаляє всі фрагменти завдання |
chunk_count( int $job_id ) |
int |
Підраховує кількість фрагментів завдання |
get_accumulated( int $job_id ) |
string |
Об’єднує всі фрагменти в один рядок |
Зазвичай вам не потрібно викликати ChunkStore безпосередньо. Виконавець автоматично керує збереженням і очищенням під час обробки StreamingStep. API доступний для складніших сценаріїв, наприклад перевірки фрагментів у процесі передавання або ручного очищення.