스트리밍
SessionConfig에서streaming: true를 지정하면 Copilot에서의 응답을 스트리밍으로 받을 수 있습니다.
원래 stdio 모드에서도 TCP 모드에서도 스트리밍과 같은 동작입니다. 차이는 ASSISTANT_MESSAGE_DELTA를 수신하게 되는 점입니다.
use Revolution\Copilot\Contracts\CopilotSession;
use Revolution\Copilot\Facades\Copilot;
use Revolution\Copilot\Types\SessionEvent;
$config = new SessionConfig(
streaming: true,
);
Copilot::start(function (CopilotSession $session) {
$session->on(function (SessionEvent $event): void {
if ($event->isAssistantMessageDelta()) {
// delta에서는 메시지가 조각으로 도착. 도중 경과 표시 등에 사용. 또는 이것만으로 완결시킬 수도 있음. 유창한 표시를 실현할 수 있음.
echo $event->deltaContent();
} elseif ($event->isAssistantMessage()) {
// 조각으로 나뉘어 있던 delta의 송신이 끝나면 ASSISTANT_MESSAGE로 전문을 포함한 하나짜리 메시지가 송신됨.
// 다음 메시지가 있으면 다시 delta로 도착.
// 하나의 프롬프트에 대해 여러 메시지가 도착하는 경우도 있으므로 streaming의 경우 여러 delta와 전문 1회가 반복됨.
} elseif ($event->isAssistantReasoningDelta()) {
// reasoning delta도 마찬가지로 조각으로 도착.
echo $event->deltaContent();
}
});
$session->sendAndWait(prompt: 'Tell me something about Laravel.');
}, config: $config);
- 유창한 표시를 위해서는 줄바꿈을 추가하지 않고 그대로 표시합니다.
- Laravel Prompts의
spin()은 표시가 깨지므로 함께 사용하지 않습니다.
구체적인 사용 패턴
Artisan 명령어
위와 같이echo 또는 $this->output->write()로 직접 출력합니다. Copilot CLI를 직접 사용한 경우와 같은 표시라서 이해하기 쉽습니다. Laravel Prompts의 stream()을 사용하면 페이드인 효과와 함께 표시할 수도 있습니다.
use function Laravel\Prompts\stream;
Artisan::command('copilot:streaming {--resume=}', function () {
$config = new SessionConfig(
streaming: true,
);
Copilot::start(function (CopilotSession $session) {
info('Starting Copilot with streaming: '.$session->id());
$stream = stream();
$session->on(function (SessionEvent $event) use ($stream) {
if ($event->isAssistantMessageDelta()) {
// delta로 조각난 메시지가 도착. Laravel Prompts의 stream()을 사용해 페이드인 효과와 함께 유창하게 표시.
$stream->append($event->deltaContent());
} elseif ($event->isIdle()) {
// idle까지 완료되면 close. 같은 세션 중에 여러 번 프롬프트를 송신해도 정상 동작하므로 다시 `$stream = stream()`은 불필요.
$stream->close();
}
});
while (true) {
$prompt = text(
label: 'Enter your prompt',
placeholder: 'Ask me anything...',
required: true,
hint: 'Ctrl+C to exit',
);
$session->sendAndWait($prompt);
}
}, config: $config, resume: $this->option('resume'));
})->purpose('Copilot streaming');
웹 페이지에서 Server-Sent Events(SSE)로 전송
response()->eventStream()을 사용하는 방법
response()->eventStream()이 추가된 것은 Laravel 12 릴리스 직전 무렵입니다. 이 패키지는 Laravel 12 이상만 지원하므로 사용 가능합니다.
Route::get('/copilot/sse', function () {
return response()->eventStream(function () {
yield from Copilot::stream(function (CopilotSession $session) {
foreach ($session->sendAndStream('Tell me something about Laravel.') as $event) {
if ($event->isAssistantMessageDelta()) {
yield $event->deltaContent();
}
}
}, config: new SessionConfig(streaming: true));
});
});
Route::get('/copilot', function () {
return view('copilot');
});
response()->stream()을 사용하는 방법
Route::get('/copilot/sse', function () {
return response()->stream(function () {
Copilot::start(function (CopilotSession $session) {
$session->on(function (SessionEvent $event) {
if ($event->isAssistantMessageDelta()) {
echo "event: update\n";
echo 'data: '.$event->deltaContent()."\n\n";
ob_flush();
flush();
}
});
$session->sendAndWait('Tell me something about Laravel.');
}, config: new SessionConfig(streaming: true));
echo "event: update\n";
echo "data: </stream>\n\n";
ob_flush();
flush();
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'X-Accel-Buffering' => 'no',
]);
});
Route::get('/copilot', function () {
return view('copilot');
});
프론트엔드 측 코드 예시
copilot.blade.php는 간이 표시 확인용입니다. 프로덕션용으로는 React나 Vue를 사용한다면 Laravel 공식 npm 패키지 사용을 권장합니다. @laravel/stream-react나 @laravel/stream-vue.
<html>
<body>
<script>
const source = new EventSource('/copilot/sse');
source.addEventListener('update', (event) => {
if (event.data === '</stream>') {
source.close();
return;
}
console.log(event.data);
document.getElementById("output").innerHTML += event.data;
});
</script>
<h1>Copilot SSE Test</h1>
<div id="output"></div>
</body>
</html>
WebSocket으로 전송
Laravel의 통지 기능, 브로드캐스트 기능, Reverb를 조합해delta를 WebSocket으로 전송할 수도 있습니다.
Livewire의 wire:stream
Livewire에서도 wire:stream 디렉티브를 사용해 스트리밍 표시가 가능합니다.
<?php
use Livewire\Attributes\Layout;
use Livewire\Component;
use Revolution\Copilot\Contracts\CopilotSession;
use Revolution\Copilot\Enums\SessionEventType;
use Revolution\Copilot\Facades\Copilot;
use Revolution\Copilot\Types\SessionEvent;
new class extends Component
{
public string $prompt = 'Tell me about Laravel Livewire.';
public string $question = '';
public string $answer = '';
public ?string $sessionId = null;
function submitPrompt(): void
{
$this->question = $this->prompt;
$this->prompt = '';
$this->answer = '';
$this->js('$wire.copilot()');
}
public function copilot(): void
{
Copilot::start(function (CopilotSession $session) {
$this->sessionId = $session->id();
$session->on(SessionEventType::ASSISTANT_MESSAGE_DELTA, function (SessionEvent $event): void {
$this->stream(
content: $event->deltaContent(),
to: 'answer',
);
$this->answer = $event->deltaContent();
});
$response = $session->sendAndWait(prompt: $this->question);
$this->answer = $response->content();
}, config: ['streaming' => true], resume: $this->sessionId);
}
};
?>
<div>
<div class="border border-gray-200 rounded-lg p-4 mb-6">
@if ($question)
<h3 class="font-extrabold mb-3">{{ $question }}</h3>
<div wire:stream="answer">{{ $answer }}</div>
@endif
</div>
<flux:input.group>
<flux:input wire:model="prompt" placeholder="Ask Copilot"/>
<flux:button wire:click="submitPrompt" icon="paper-airplane" icon:variant="outline"></flux:button>
</flux:input.group>
</div>
최신 정보는 GitHub 저장소를 참고하세요.