-
-
Notifications
You must be signed in to change notification settings - Fork 14
DRAFT: Streaming support #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <?php | ||
|
|
||
| namespace Prism\Bedrock; | ||
|
|
||
| use Aws\BedrockRuntime\BedrockRuntimeClient; | ||
| use GuzzleHttp\HandlerStack; | ||
|
|
||
| class BedrockClientFactory | ||
| { | ||
| public function make(?HandlerStack $handler = null): BedrockRuntimeClient | ||
| { | ||
| $config = [ | ||
| 'region' => config('prism.providers.bedrock.region', 'eu-central-1'), | ||
| 'version' => config('prism.providers.bedrock.version', 'latest'), | ||
| 'credentials' => [ | ||
| 'key' => config('prism.providers.bedrock.api_key', ''), | ||
| 'secret' => config('prism.providers.bedrock.api_secret', ''), | ||
| ], | ||
| ]; | ||
|
|
||
| if ($handler instanceof \GuzzleHttp\HandlerStack) { | ||
| $config['http'] = ['handler' => $handler]; | ||
| } | ||
|
|
||
| return new BedrockRuntimeClient($config); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
|
|
||
| namespace Prism\Bedrock\Contracts; | ||
|
|
||
| use Aws\BedrockRuntime\BedrockRuntimeClient; | ||
| use Generator; | ||
| use Prism\Bedrock\Bedrock; | ||
| use Prism\Prism\Text\Request; | ||
|
|
||
| abstract class BedrockStreamHandler | ||
| { | ||
| public function __construct( | ||
| protected Bedrock $provider, | ||
| protected BedrockRuntimeClient $client | ||
| ) {} | ||
|
|
||
| abstract public function handle(Request $request): Generator; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| namespace Prism\Bedrock\Exceptions; | ||
|
|
||
| class BedrockException extends \Exception {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| <?php | ||
|
|
||
| namespace Prism\Bedrock; | ||
|
|
||
| use Aws\Result; | ||
| use Generator; | ||
| use Prism\Bedrock\Exceptions\BedrockException; | ||
| use Prism\Prism\Enums\ChunkType; | ||
| use Prism\Prism\Exceptions\PrismException; | ||
| use Prism\Prism\Providers\Anthropic\Maps\FinishReasonMap; | ||
| use Prism\Prism\Text\Chunk; | ||
| use Prism\Prism\Text\Request; | ||
|
|
||
| trait HandlesStream | ||
| { | ||
| public function processStream(Result $result, Request $request, int $depth = 0): Generator | ||
| { | ||
| $this->state->reset(); | ||
|
|
||
| $this->validateToolCallDepth($request, $depth); | ||
|
|
||
| yield from $this->processStreamChunks($result, $request, $depth); | ||
|
|
||
| if ($this->state->hasToolCalls()) { | ||
| yield from $this->handleToolUseFinish($request, $depth); | ||
| } | ||
| } | ||
|
|
||
| protected function validateToolCallDepth(Request $request, int $depth): void | ||
| { | ||
| if ($depth >= $request->maxSteps()) { | ||
| throw new PrismException('Maximum tool call chain depth exceeded'); | ||
| } | ||
| } | ||
|
|
||
| protected function processStreamChunks(Result $result, Request $request, int $depth): Generator | ||
| { | ||
|
|
||
| foreach ($result->get('stream') as $event) { | ||
|
|
||
| $outcome = $this->processChunk($event, $request, $depth); | ||
|
|
||
| if ($outcome instanceof Generator) { | ||
| yield from $outcome; | ||
| } | ||
|
|
||
| if ($outcome instanceof Chunk) { | ||
| yield $outcome; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| protected function processChunk(array $chunk, Request $request, int $depth): Generator|Chunk|null | ||
| { | ||
| return match (array_key_first($chunk) ?? null) { | ||
| 'messageStart' => $this->handleMessageStart(data_get($chunk, 'messageStart')), | ||
| 'contentBlockDelta' => $this->handleContentBlockDelta(data_get($chunk, 'contentBlockDelta')), | ||
| 'contentBlockStop' => $this->handleContentBlockStop(), | ||
| 'messageStop' => $this->handleMessageStop(data_get($chunk, 'messageStop'), $depth), | ||
| 'metadata' => $this->handleMetadata(data_get($chunk, 'metadata')), | ||
| 'modelStreamErrorException' => $this->handleException(data_get($chunk, 'modelStreamErrorException')), | ||
| 'serviceUnavailableException' => $this->handleException(data_get($chunk, 'serviceUnavailableException')), | ||
| 'throttlingException' => $this->handleException(data_get($chunk, 'throttlingException')), | ||
| 'validationException' => $this->handleException(data_get($chunk, 'validationException')), | ||
| default => null, | ||
| }; | ||
| } | ||
|
|
||
| protected function handleMessageStart(array $chunk): null | ||
| { | ||
| // { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how to best store this information. |
||
| // messageStart: { | ||
| // role: assistant | ||
| // } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| protected function handleContentBlockDelta(array $chunk): ?Chunk | ||
| { | ||
| if ($text = data_get($chunk, 'delta.text')) { | ||
| return $this->handleTextBlockDelta($text, (int) data_get($chunk, 'contentBlockIndex')); | ||
| } | ||
|
|
||
| if ($reasoningContent = data_get($chunk, 'delta.reasoningContent')) { | ||
| return $this->handleReasoningContentBlockDelta($reasoningContent); | ||
| } | ||
|
|
||
| if ($toolUse = data_get($chunk, 'delta.toolUse')) { | ||
| return $this->handleToolUseBlockDelta($toolUse); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| protected function handleTextBlockDelta(string $text, int $contentBlockIndex): Chunk | ||
| { | ||
| $this->state->appendText($text); | ||
|
|
||
| return new Chunk( | ||
| text: $text, | ||
| additionalContent: [ | ||
| 'contentBlockIndex' => $contentBlockIndex, | ||
| ], | ||
| chunkType: ChunkType::Text | ||
| ); | ||
| } | ||
|
|
||
| protected function handleReasoningContentBlockDelta(array $reasoningContent): Chunk | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not tested yet. |
||
| { | ||
| $text = data_get($reasoningContent, 'reasoningText.text', ''); | ||
| $signature = data_get($reasoningContent, 'reasoningText.signature', ''); | ||
|
|
||
| $this->state->appendThinking($text); | ||
| $this->state->appendThinkingSignature($signature); | ||
|
|
||
| return new Chunk( | ||
| text: $text, | ||
| chunkType: ChunkType::Thinking | ||
| ); | ||
| } | ||
|
|
||
| protected function handleContentBlockStop(): void | ||
| { | ||
| $this->state->resetContentBlock(); | ||
| } | ||
|
|
||
| protected function handleMessageStop(array $chunk, int $depth): Generator|Chunk | ||
| { | ||
| $this->state->setStopReason(data_get($chunk, 'stopReason')); | ||
|
|
||
| if ($this->state->isToolUseFinish()) { | ||
| return $this->handleToolUseFinish($chunk, $depth); | ||
| } | ||
|
|
||
| return new Chunk( | ||
| text: $this->state->text(), | ||
| finishReason: FinishReasonMap::map($this->state->stopReason()), | ||
| additionalContent: $this->state->buildAdditionalContent(), | ||
| chunkType: ChunkType::Meta | ||
| ); | ||
| } | ||
|
|
||
| protected function handleMetadata(array $chunk): void | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what to do with this information. |
||
| { | ||
| // {"metadata":{"usage":{"inputTokens":11,"outputTokens":48,"totalTokens":59},"metrics":{"latencyMs":1269}}} | ||
| // not sure yet where to store this information. | ||
| } | ||
|
|
||
| protected function handleException(array $chunk): void | ||
| { | ||
| throw new BedrockException(data_get($chunk, 'message')); | ||
| } | ||
|
|
||
| protected function handleToolUseBlockDelta(array $toolUse): void | ||
| { | ||
| throw new \Exception('Tool use not yet supported'); | ||
| } | ||
|
|
||
| public function handleToolUseFinish(Request $request, int $depth): void | ||
| { | ||
| throw new \Exception('Tool use not yet supported'); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you'll need to add
'token' => config('prism.providers.bedrock.session_token', ''),to work with sts.