95 lines
6.6 KiB
Markdown
95 lines
6.6 KiB
Markdown
# Bedrock Runtime
|
|
|
|
**Protocol:** REST JSON
|
|
**Endpoint:** `POST http://localhost:4566/model/{modelId}/...`
|
|
|
|
Floci emulates the AWS Bedrock Runtime data-plane API. The response shape matches the real AWS Converse and InvokeModel contracts so AWS SDK and CLI clients accept the reply without error.
|
|
|
|
Two backends are available, selected via `floci.services.bedrock-runtime.backend`:
|
|
|
|
- `stub` (default) — no real model inference: every call returns a fixed assistant turn plus synthetic token usage metadata.
|
|
- `proxy` — forwards `Converse` and `ConverseStream` requests to any OpenAI-compatible `/chat/completions` endpoint (Ollama, OpenRouter, LiteLLM, vLLM, ...), translating request/response shapes including tool use. `InvokeModel` still falls back to the stub response in this mode. See [Proxy Backend](#proxy-backend) below.
|
|
|
|
The Bedrock management plane (`aws bedrock ...`: `ListFoundationModels`, `GetFoundationModel`, customization) is not yet emulated.
|
|
|
|
## Supported Operations
|
|
|
|
| Operation | Endpoint | Notes |
|
|
|-----------|----------|-------|
|
|
| `Converse` | `POST /model/{modelId}/converse` | `stub`: returns a static assistant message. `proxy`: forwards to the configured OpenAI-compatible backend and translates the reply. |
|
|
| `InvokeModel` | `POST /model/{modelId}/invoke` | Returns Anthropic-shaped body for `anthropic.*` and `*.anthropic.*` model ids; generic `{"outputs": [...]}` shape otherwise. Not proxied yet — always the stub response, regardless of backend. |
|
|
| `ConverseStream` | `POST /model/{modelId}/converse-stream` | `stub`: emits a static assistant turn as `messageStart`/`contentBlockDelta`/`contentBlockStop`/`messageStop`/`metadata` events. `proxy`: forwards a streaming request to the configured OpenAI-compatible backend and translates each SSE chunk into a Bedrock event as it arrives. Text deltas reach the client incrementally, not all at once. Response body is `application/vnd.amazon.eventstream`-framed. If the upstream stream ends without a `finish_reason` or `"[DONE]"` (e.g. a dropped connection), the failure is reported as an in-band `modelStreamErrorException` event, since the HTTP 200 is already committed by the time streaming starts, matching real Bedrock's own behavior for a mid-stream failure. |
|
|
| `InvokeModelWithResponseStream` | `POST /model/{modelId}/invoke-with-response-stream` | Returns 501 `UnsupportedOperationException` |
|
|
|
|
`modelId` is URL-decoded by JAX-RS and echoed verbatim. Plain model ids (e.g. `anthropic.claude-3-haiku-20240307-v1:0`), inference-profile ids (e.g. `us.anthropic.claude-3-5-sonnet-20241022-v2:0`), and full ARNs containing slashes (e.g. `arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0`) are all accepted.
|
|
|
|
Converse accepts `messages`, `system`, `inferenceConfig`, and `toolConfig` fields. Only `messages` is validated (non-empty array). With the `stub` backend, other fields are accepted and ignored, and tool-use round-tripping is not implemented. With the `proxy` backend, `system`, `toolConfig.tools` and `toolConfig.toolChoice`, and `inferenceConfig` (`maxTokens`, `temperature`, `topP`, `stopSequences`) are translated and forwarded (see below).
|
|
|
|
InvokeModel bodies are passed through as opaque bytes; neither backend parses request payloads.
|
|
|
|
## Configuration
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_ENABLED` | `true` | Enable or disable the service |
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_BACKEND` | `stub` | `stub` or `proxy` |
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_URL` | _(none)_ | Base URL of the OpenAI-compatible backend, e.g. `http://localhost:11434/v1`. Required when `backend=proxy`. |
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_API_KEY` | _(none)_ | Sent as `Authorization: Bearer {value}` when set |
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_DEFAULT_MODEL` | _(none)_ | Fallback OpenAI-side model id when no mapping matches and passthrough is disabled |
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_MODEL_MAPPING` | _(none)_ | Comma-separated `bedrockModelId=openaiModelId` pairs, e.g. `anthropic.claude-3-sonnet-20240229-v1:0=claude-3-sonnet` |
|
|
| `FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_PASSTHROUGH` | `false` | When true, forward the raw Bedrock model id as-is if no mapping matches |
|
|
|
|
## Proxy Backend
|
|
|
|
Model resolution order for an incoming `modelId`: explicit `model-mapping` entry, then (if `passthrough=true`) the raw `modelId` as-is, then `default-model`, else a `400 ValidationException`.
|
|
|
|
```bash
|
|
# Ollama (local, no API key needed)
|
|
export FLOCI_SERVICES_BEDROCK_RUNTIME_BACKEND=proxy
|
|
export FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_URL=http://localhost:11434/v1
|
|
export FLOCI_SERVICES_BEDROCK_RUNTIME_PROXY_DEFAULT_MODEL=llama3.2
|
|
```
|
|
|
|
Useful for integration testing: applications that call Bedrock directly don't need to stand up their own stub/mock of Bedrock — point the existing SDK client at Floci and get real responses from whatever OpenAI-compatible backend is already available, with the stubbing concern handled by Floci instead of the application under test.
|
|
|
|
## Examples
|
|
|
|
```bash
|
|
export AWS_ENDPOINT_URL=http://localhost:4566
|
|
export AWS_DEFAULT_REGION=us-east-1
|
|
export AWS_ACCESS_KEY_ID=test
|
|
export AWS_SECRET_ACCESS_KEY=test
|
|
|
|
# Converse
|
|
aws bedrock-runtime converse \
|
|
--model-id anthropic.claude-3-haiku-20240307-v1:0 \
|
|
--messages '[{"role":"user","content":[{"text":"hi"}]}]'
|
|
|
|
# InvokeModel (Anthropic Claude)
|
|
aws bedrock-runtime invoke-model \
|
|
--model-id anthropic.claude-3-haiku-20240307-v1:0 \
|
|
--body '{"anthropic_version":"bedrock-2023-05-31","max_tokens":100,"messages":[{"role":"user","content":"hi"}]}' \
|
|
--cli-binary-format raw-in-base64-out \
|
|
/tmp/response.json
|
|
cat /tmp/response.json
|
|
```
|
|
|
|
```python
|
|
import boto3
|
|
client = boto3.client("bedrock-runtime", endpoint_url="http://localhost:4566")
|
|
resp = client.converse(
|
|
modelId="anthropic.claude-3-haiku-20240307-v1:0",
|
|
messages=[{"role": "user", "content": [{"text": "hi"}]}],
|
|
)
|
|
print(resp["output"]["message"]["content"][0]["text"])
|
|
```
|
|
|
|
## Out of Scope
|
|
|
|
- Real model inference with the `stub` backend (always returns a fixed string).
|
|
- `InvokeModel` against the `proxy` backend (still returns the stub response).
|
|
- `InvokeModelWithResponseStream` returns 501, regardless of backend.
|
|
- `ConverseStream` tool calls are still accumulated across chunks and emitted as a single complete `contentBlockDelta` once the stream ends, rather than as incremental argument fragments — SDKs consume both the same way, since they concatenate `delta.toolUse.input` across events for a given `contentBlockIndex` before parsing it as JSON.
|
|
- Bedrock management plane (`ListFoundationModels`, `GetFoundationModel`, model customisation).
|
|
- Bedrock Agents, Knowledge Bases, Guardrails, provisioned throughput.
|