Metadata-Version: 2.5
Name: bedrock-migrate
Version: 0.0.2
Summary: One-shot migration of inference from supported providers to Amazon Bedrock for Python repositories
Project-URL: Repository, https://github.com/HolboxAI/bedrock-migrate
License-Expression: Apache-2.0
Requires-Python: >=3.9
Requires-Dist: libcst>=1.1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# bedrock-migrate

One command migrates a **Python** repository's **inference** from a supported provider to **Amazon Bedrock** — same model, new host. It scans the repository, rewrites the inference calls, adds a small runtime helper, and opens a pull request.

- Hosted providers (Fireworks, Together AI, OpenRouter, Groq, Mistral AI, xAI, DeepSeek) migrate to Bedrock's **OpenAI-compatible** Chat Completions API — no Bedrock SDK required
- Model IDs are rewritten to their Bedrock IDs (e.g. `accounts/fireworks/models/kimi-k3` → `global.moonshotai.kimi-k3`, or `us.moonshotai.kimi-k3` with `--us-only`)
- Fine-tuned models, dedicated deployments, and non-inference APIs are detected and left untouched

## Requirements

- Python 3.9+
- A git repository for the branch/commit/PR flow (or `--no-git` to skip git entirely)
- To open the PR: the [`gh` CLI](https://cli.github.com/) or a git credential helper with a GitHub token
- At deploy time: an AWS role with `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` for each migrated inference profile

## Installation

```bash
pip install bedrock-migrate
```

Or run it once without installing:

```bash
uvx bedrock-migrate migrate --pr
# or
pipx run bedrock-migrate migrate --pr
```

## Quick start

```bash
cd path/to/your-repo
bedrock-migrate migrate --pr
```

On a git repository with a clean working tree, this checks out the branch `bedrock-migrate`, rewrites every migratable inference call site, adds the helper/config/dependencies, writes a migration report, pushes the branch, and opens a pull request.

## Commands

```
bedrock-migrate scan    [PATH] [--json]
bedrock-migrate migrate [PATH] [options]
```

`PATH` defaults to the current directory.

### `scan` — report without changing anything

Lists every inference touchpoint and how it would be handled:

```bash
bedrock-migrate scan . --json
```

### `migrate` — apply the migration

| Option | Effect |
|---|---|
| `--pr` | push the branch and open a pull request |
| `--us-only` | target `us.` inference profiles instead of each model's default profile |
| `--region <REGION>` | default region recorded in `f2b.json` (default `us-east-1`) |
| `--map SRC=TGT` | map a source model ID to a Bedrock model ID (repeatable; use `PROVIDER:SRC=TGT` to disambiguate the provider) |
| `--json` | machine-readable output on stdout |
| `--no-git` | skip branch/commit/push (no git required) |

## What it migrates

| Call site | Before | After |
|---|---|---|
| Model ID | `accounts/fireworks/models/kimi-k3` | `KIMI_K3` (`global.moonshotai.kimi-k3`) |
| OpenAI client | `OpenAI(base_url="https://api.fireworks.ai/inference/v1")` | `get_llm_client()` |
| Async OpenAI | `AsyncOpenAI(base_url=...)` | `get_async_llm_client()` |
| Fireworks SDK | `Fireworks(...)` / `AsyncFireworks(...)` | `get_llm_client()` / `get_async_llm_client()` |
| Fireworks LLM | `LLM(model=...)` | `get_llm_client().bind(KIMI_K3)` |
| LangChain | `ChatFireworks(...)` | `ChatOpenAI(..., **langchain_kwargs())` |
| Raw HTTP | `requests.post("https://api.fireworks.ai/...")` | flagged for review; helper client suggested |

Model coverage is data-driven: any source ID in `spec/models.json` is rewritten to its Bedrock target (for example `meta-llama/Llama-3.3-70B-Instruct-Turbo` → `us.meta.llama3-3-70b-instruct-v1:0`). The LiteLLM spelling `fireworks_ai/accounts/fireworks/models/kimi-k3` is rewritten too.

## What it leaves alone

These are detected, left untouched, and listed in the report and `f2b.json`:

- **Fine-tuned / uploaded models** — `accounts/<account>/models/<name>` where `<account>` is not `fireworks`
- **Models Bedrock does not host** — they stay on their source provider
- **Dedicated deployments** — Baseten, Modal, Replicate (migrate only with an explicit `--map`)
- **Non-inference APIs** — `fine_tuning`, `datasets`, `deployments`, `rerank`, `embeddings`, `batch`, `audio`, `image_generation`
- **LoRA adapters**

## Files it adds to your repository

| File | Purpose |
|---|---|
| `llm_client.py` | runtime helper, co-located with the migrated files |
| `f2b.json` | per-model backend routing (repo root) |
| `requirements.txt` | adds `openai` and `aws-bedrock-token-generator` (`boto3` only if a model routes to `converse`) |
| `BEDROCK_MIGRATION.md` | full change log, what was left alone, IAM policy, rollback |

## How the runtime helper works

`llm_client.py` keeps the OpenAI shape, so migrated call sites do not change:

```python
from llm_client import get_llm_client, KIMI_K3

client = get_llm_client()
resp = client.chat.completions.create(model=KIMI_K3, messages=[...])
```

Each model is routed at runtime by `f2b.json`:

| Backend | Meaning |
|---|---|
| `openai-compatible` | Bedrock's OpenAI-compatible Chat Completions API (default) |
| `converse` | Bedrock Converse API via `boto3`, OpenAI shape translated on the fly |
| a provider id (e.g. `fireworks`) | keep that model on its source provider (passthrough / rollback) |

Bedrock requests authenticate with a short-lived token generated from the ambient AWS credentials (`aws-bedrock-token-generator`). The default backend uses the OpenAI-compatible endpoint (`/openai/v1`) rather than the Converse API, so no Bedrock SDK is required. Blocked and unmapped model IDs are recorded in `f2b.json` at migration time and stay on their source provider.

## Classification and exit codes

Each finding is classified `MIGRATE`, `BLOCK`, or `REVIEW`. `REVIEW` items — for example a client whose `base_url` is assembled at runtime, or a model ID built from an f-string — need a decision before deploy and are listed in the report and in the JSON `review_items` field.

| Exit code | Meaning |
|---|---|
| `0` | migration applied (and PR opened with `--pr`) |
| `2` | invalid command line |
| `20` | nothing to migrate, or already migrated |
| `30` | environment problem (not a directory, dirty working tree, or checkout failed) |

## JSON output

`scan --json` and `migrate --json` each emit one JSON document:

```json
{
  "version": "0.0.2",
  "repo": "/abs/path/to/repo",
  "language": "python",
  "branch": "bedrock-migrate",
  "pr_url": "https://github.com/acme/repo/pull/12",
  "findings": [
    {
      "class": "MIGRATE",
      "file": "app/llm.py",
      "line": 14,
      "kind": "openai_client",
      "detail": "OpenAI(base_url=fireworks) -> get_llm_client()"
    }
  ],
  "checks": [],
  "review_items": [],
  "exit_code": 0
}
```

`checks` is reserved for future automatic verification and is empty today.

## Rollback

Set `F2B_BACKEND=<source-provider>` (for example `F2B_BACKEND=fireworks`) to route back to the source provider with no code change.

## Idempotency

Running `migrate` on an already-migrated repository is a no-op and exits `20`.

## For AI coding agents

An agent driving this CLI should read [`AGENTS.md`](AGENTS.md) — the full contract: commands, flags, exit codes, the JSON schema, and the `f2b.json` routing config. TL;DR: run `scan --json` first, then `migrate --json`.

## License

Apache-2.0
