关于 llm-to-bedrock
将 OpenAI、Gemini 或 Anthropic 调用迁移到 Amazon Bedrock 的技能,支持代码评估与迁移,也提供仅启用模型访问的路径。完整迁移依赖同时安装 `gcp-to-aws` 或 `azure-to-aws` 技能。
主要功能
- 01迁移到 Amazon Bedrock
- 02评估并改写模型调用代码
- 03单独办理模型访问启用
- 04要求安装配套迁移技能
适用场景
- 01将现有模型调用改为 Bedrock
- 02检查代码库并规划迁移
- 03仅启用所需 Bedrock 模型访问
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aws/ agent-toolkit-for-aws --skill llm-to-bedrock安装后在 Claude Code 中输入/llm-to-bedrock确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/helpers/bedrock-known-fixes/bedrock-known-fixes.mdreferences/helpers/bedrock-known-fixes/references/bedrock-iam-inference-profile.mdbedrock-inference-profile-model-id.mdbedrock-response-key-casing.mdbedrock-vision.mdbedrock-vision.py.templatereferences/helpers/behavior-delta-detection/behavior-delta-detection.mdreferences/helpers/behavior-delta-detection/references/anthropic-to-bedrock.mdgemini-to-bedrock.mdopenai-to-bedrock.mdreferences/helpers/dependency-conflict-resolution/dependency-conflict-resolution.mdreferences/helpers/resolve-bedrock-model-id/resolve-bedrock-model-id.mdreferences/helpers/run-source-model-baseline/run-source-model-baseline.mdreferences/vendored/telemetry/cli.pyPROTOCOL.mdversion.jsonreferences/vendored/telemetry/consent/accept.pycli.pynotice.pyopt_out.pyrecord.pyreferences/vendored/telemetry/metric_emission/client.pymigration_attributes.pymigration.pyscripts/bedrock_pricing.pyiam_policy.pyimage_input.pymodel_identity.pypreflight_bedrock.pypyproject.toml详细介绍
原文在翻译后有更新,译文可能不是最新。
迁移到 Bedrock(评估 + 执行)
一键 AI 迁移:OpenAI / Gemini / Anthropic → Amazon Bedrock。
需要同时安装 gcp-to-aws 或 azure-to-aws 技能。 此技能没有独立的评估实现——下文的阶段 A 会在同一会话中,直接读取并执行这两个技能中实际存在的那个技能自身的阶段指令文件来运行评估(采用与 agent-advisor 的 migration-plan 阶段相同的内联执行模式:不调用其他技能的工具,不跨回合,也不依赖你的 Agent 是否支持技能/子 Agent 分派机制——任何 Agent 都能读取文件)。如果这两个同伴技能都不存在,就没有能自行执行评估的回退路径。如果你单独安装了此技能(例如,只运行一次 npx skills add 安装单个技能),请先安装 gcp-to-aws 或 azure-to-aws,再使用此技能。
技能基础目录位于 harness 在加载时输出的“Base directory for this skill: X”行中。将其称为 <SKILL_BASE>。派生路径如下:
$SCRIPTS=<SKILL_BASE>/scripts$HELPERS=<SKILL_BASE>/references/helpers(原先的辅助技能,现在是参考资料)$ASSESS_SKILL/$ASSESS_BASE— 在步骤 0b 中解析一次:$ASSESS_SKILL是与此技能同时安装的gcp-to-aws/azure-to-aws中的一个(如果两者都存在,则优先选择gcp-to-aws),$ASSESS_BASE=<SKILL_BASE>/../$ASSESS_SKILL— 即该同伴技能自身的目录。阶段 A 会直接从此路径读取其指令文件;$ASSESS_SKILL文件中的所有相对引用(references/shared/...、references/phases/...等)都在$ASSESS_BASE下解析,与$ASSESS_SKILL独立运行时完全相同。
可选使用情况遥测
开始或恢复前,加载 references/vendored/telemetry/PROTOCOL.md 并运行其中的只读状态检查。使用返回的报告模式,而不是模型的身份。仅当该协议要求时,才完成现有的通知交互;遥测不可用或被拒绝都不会阻止使用此技能。
步骤 0 — 检查先决条件
0a. 检查是否可以使用 uv
uv --version 2>/dev/null || echo "MISSING"
如果未安装:“请先安装 uv — 参阅官方安装指南:https://docs.astral.sh/uv/getting-started/installation/(例如 brew install uv 或 pipx install uv)”。停止。
0a-bis. 路由:完整迁移还是仅启用访问权限
此技能有两种模式。在检查评估同伴技能之前(0b),先判断用户想使用哪种模式——仅启用访问权限的模式完全不会用到 gcp-to-aws / azure-to-aws,因此在此处要求安装其中一个,会阻止只想启用访问权限的用户。
在以下任一情况下,路由到仅启用访问权限模式(跳转到下方的“## 仅启用访问权限模式”部分,跳过 0b 和步骤 1 及后续步骤):
$ARGUMENTS包含明确的访问权限请求短语——model access、request access、enable model access、just access或preflight(且没有源代码路径)。不要仅因出现enable或access单独一词就进行路由——“enable the Bedrock rewrite”表示完整迁移,而不是访问权限请求。如有疑问,请使用下方的 AskUserQuestion,而不是关键词。或者- 用户的请求是关于启用 Bedrock 模型访问权限,而不是改写代码——例如:“帮我申请在 Bedrock 上使用 Claude 的权限”“在 Bedrock 上启用 GPT 模型”“我需要启用哪些模型”“我只想获得访问权限,迁移工作由工程师完成”。
如果请求含义不明确(用户同时提到代码库和访问权限),请使用 AskUserQuestion:
“我可以做两件事——你想要哪一种? [完整迁移] 端到端改写你的 OpenAI/Gemini/Anthropic 调用,以使用 Bedrock。 [仅启用模型访问权限] 检查并引导你为指定模型启用 Bedrock 访问权限, 不修改代码。”
选择 [仅启用模型访问权限] → 仅启用访问权限模式。选择 [完整迁移] → 继续执行 0b。
否则(提供了代码路径 / 明确表示要改写代码)→ 继续执行 0b,进行完整迁移。
0b. 检查是否已安装 gcp-to-aws 或 azure-to-aws 兄弟技能
下面的阶段 A 会在同一会话中直接读取并执行 $ASSESS_SKILL 自身的阶段说明文件(Discover → Clarify → Design → Estimate,仅限 AI 路径)来运行 Assess——本技能没有可供回退使用的 Assess 逻辑。gcp-to-aws 和
azure-to-aws 是独立技能,并未捆绑在本技能中——单技能安装
(例如 npx skills add ... --skill llm-to-bedrock)不会自动安装其中任何一个。
现在就检查是否已安装它们,再向用户承诺本部署无法运行的 Assess 阶段:
if [ -f "<SKILL_BASE>/../gcp-to-aws/SKILL.md" ]; then
ASSESS_SKILL=gcp-to-aws
elif [ -f "<SKILL_BASE>/../azure-to-aws/SKILL.md" ]; then
ASSESS_SKILL=azure-to-aws
else
echo ASSESS_SIBLING_MISSING
fi
$ASSESS_BASE = <SKILL_BASE>/../$ASSESS_SKILL。此操作会检查相对于
<SKILL_BASE>(如上所定义)的兄弟目录,因此适用于任何安装路径——原生插件安装和
npx skills add --skill '*' 都会将 gcp-to-aws/azure-to-aws 放在本技能目录的旁边。
它不依赖 ${CLAUDE_PLUGIN_ROOT}。如果两者都已安装,优先使用 gcp-to-aws——它是本技能最初的集成,经过了更深入的测试,因此能为安装了这两项技能的现有用户保留当前的默认行为。
-
$ASSESS_SKILL已解析(任一值)→ 继续步骤 1。 -
ASSESS_SIBLING_MISSING→ 在此停止——不要继续。 告诉用户:“此迁移需要在本技能旁边安装
gcp-to-aws或azure-to-aws技能之一——它负责扫描代码、检测 AI 工作负载并设计 Bedrock 模型;没有它,我无法完成这部分工作。它们和本技能都包含在同一个aws-startup-advisor插件中,因此如果你安装了整个插件,其中一个应该已经在我旁边——看起来你只安装了插件中的部分技能。请通过以下命令一起安装这两项技能:npx skills add aws/agent-toolkit-for-aws/plugins/aws-startup-advisor/skills --skill llm-to-bedrock --skill gcp-to-aws(如果源基础设施运行在 Azure 上,则将gcp-to-aws替换为azure-to-aws;使用与你安装本技能时相同的--agent和--global/项目范围),或者运行同一条npx skills add命令,并用--skill '*'替代指定单项技能,以一次性获取所有技能。然后重启你的 Agent,再让我重新执行迁移。”不要自行执行 Assess 阶段来变通——下面的阶段 A 明确指出,Assess 逻辑只存在于
$ASSESS_SKILL中;在此重新实现会导致它与该技能的 Discover/Clarify/Design 逻辑逐渐不同步。本技能没有独立的 Assess——此检查旨在尽早清晰地失败,而不是启用其他行为。
阶段 A 直接从磁盘读取 $ASSESS_SKILL 的文件,而不是以技能调用它,因此这里没有单独的 Agent 能力要求——任何能够读取文件并运行 Bash 的 Agent 都可以执行此操作。(本技能的早期版本曾通过跨技能 Skill 工具调用 gcp-to-aws;该机制仅适用于 Claude Code,且未在其他环境中验证,这正是阶段 A 不再使用它的原因。)
步骤 1 — 收集源代码路径
如果 $ARGUMENTS 包含路径,则将其用作 $REPO。否则使用 AskUserQuestion:
"您的源代码在哪里?请输入本地路径或 GitHub URL。"
如果是 GitHub URL,则使用 git clone 将其克隆到临时目录;将该路径用作 $REPO。
对 $REPO 的检查:
-
Git 根目录检查(比较解析后的路径 — 在 macOS 上,
/tmp会解析为/private/tmp, 因此直接比较原始字符串会产生误报):[ "$(git -C <REPO> rev-parse --show-toplevel 2>/dev/null)" = "$(cd <REPO> && pwd -P)" ] && echo GIT_ROOT_OK || echo GIT_ROOT_MISMATCHGIT_ROOT_OK→ 继续。GIT_ROOT_MISMATCH且命令报错(根本不是 git 仓库)→ 告知用户该路径必须是 git 仓库(交付内容是一个 git 分支);重新询问。GIT_ROOT_MISMATCH但路径位于某个仓库内(用户指向了子目录)→ AskUserQuestion: "改用仓库根目录"(推荐)/ "继续使用此子目录" / "中止"。
-
脏工作树检查:
git -C <REPO> status --porcelain如果存在未提交的更改,则显示这些更改,并使用 AskUserQuestion 询问:"仍要继续"或"让我先清理一下"。
在后续所有步骤中记录 $REPO。
步骤 1.5 — 检查是否已有用量配置文件
在 Phase A 开始之前,检查此前的 Discover 运行(来自 gcp-to-aws 或
azure-to-aws)是否已为此仓库记录了真实的 OpenAI/OpenRouter/Anthropic 用量 API 支出 — 如果有,则将其复用为成本基线,而不是之后回退到黄金数据集
外推。每次进入 Phase A 时,其自身的 $MIGRATION_DIR 解析和恢复逻辑都会从头开始,因此这一步必须严格在此之前运行,才能有机会跳过这些逻辑。
-
Glob
$REPO下所有.migration/<run-dir>/<provider>-usage-profile.json, 排除此技能自身的输出目录:for p in anthropic openai openrouter; do find "$REPO/.migration" -mindepth 2 -maxdepth 2 -name "${p}-usage-profile.json" \ -not -path "*/.bedrock-*/*" 2>/dev/null done-mindepth 2 -maxdepth 2会精确匹配.migration/<run-dir>/<provider>-usage-profile.json。-not -path "*/.bedrock-*/*"会排除 llm-to-bedrock 自己的$BEDROCK_RUN_DIR(.migration/.bedrock-<id>/)——该目录是此技能自己的输出,绝不是 gcp/azure Discover 的输出。此 glob 按提供商命名,而非按技能命名——无论是gcp-to-aws还是azure-to-aws的 Discover 写入了该文件,它都能找到该配置文件,因为两者都会在同一个$REPO/.migration/目录树下使用相同的文件名约定。 -
三项检查均无匹配项 → 当前行为保持不变;按现有流程进入 Phase A。
-
确定使用哪个运行的产物(确定性平局处理,并向用户说明源运行状态): 对于至少有一个匹配项的每个不同运行目录,读取该目录的
.phase-status.json→last_updated(完整的 ISO-8601 时间戳,确实包含年份),并将其作为主要排序键——last_updated最大的获胜。不要单独把目录自身的<MMDD-HHMM>名称视为按时间排序: 对<MMDD-HHMM>进行普通字典序比较只在同一个日历年内按时间排序——后一年中客观上更晚的 1 月 2 日0102-1200,字典序却排在 12 月 31 日1231-1200之前,因此仅用目录名可能会选中过时的运行。只有在候选项的last_updated缺失或无法解析时,才回退使用<MMDD-HHMM>目录名;使用此回退方式时,必须明确说明——该回退依赖于同一年这一假设,并不能保证普遍按时间排序,绝不能向用户表示它等同于真实时间戳比较。将.phase-status.json→current_phase作为要向用户说明的辅助信息(而非平局裁决依据)——例如,某次运行的current_phase显示 Discover 之后从未进入 Clarify,说明这很可能是已放弃或已拒绝的会话,即使其 usage-profile 文件仍是最新的。采用数据前,必须向用户明确说明这两项信息: "找到一个来自<MMDD-HHMM>运行的现有 [provider] usage profile(状态:<current_phase>)——将使用该配置文件。" 如果某个运行目录的.phase-status.json缺失或完全无法解析,仍可通过<MMDD-HHMM>回退将其作为候选项,但要显示status: unknown,而不是阶段名称。确实存在平局或歧义(两个或多个候选项都没有可用的last_updated,因此只能使用受同一年限制的<MMDD-HHMM>回退方式;或者它们的last_updated值仍有歧义):不要悄悄猜测——向用户列出所有存在歧义的候选项(目录名及各自已知的状态),并询问实际应该使用哪一个;采用上述“向用户明确说明这两项信息”的模式,而不是自动挑选一个。 -
对于胜出的运行目录,读取其中存在的所有 usage-profile 文件(一次运行可同时包含多个提供商的配置文件,包括 gcp 生成和 azure 生成的配置文件混合存在的情况——B1 无需区分哪个技能写入了哪个文件,因为无论由哪个生产者生成,配置文件的架构都相同)。
-
对每个配置文件,先检查
metadata.capture_warnings——非空表示某些端点调用失败,该类别的用量未知,而非零;将此注意事项传递下去。 -
将胜出运行中所有配置文件的
summary.monthly_cost_usd相加(绝不可取最大值或只选一个;沿用两个技能的estimate-ai.md中记载的求和规则——gcp 和 azure 的estimate-ai.md采用相同规则),并按相同的规范化规则合并usage_by_model[](OpenRouter 的prompt_tokens/completion_tokens→input_tokens/output_tokens;Anthropic 的cache_read_tokens/cache_creation_tokens计入用量总和——这是 Admin API 单独报告的额外 token 池,并非input_tokens的子项;完整的uncached_input_tokens + cache_read_input_tokens + cache_creation_input_tokens约定见任一同级技能estimate-ai.md的 Prerequisites 部分)。应用metadata.partial_window例外:部分时间窗口的配置文件数据仅作为带有active_days标签的参考数据,绝不可与月度基线混合(架构上的后果见下文)。第二个并行例外——metadata.cost_status(仅 Anthropic 配置文件,目前唯一会写入此字段的配置文件):cost_status为"cost_unavailable"的配置文件,其summary.monthly_cost_usd为null——将其从此美元金额求和中排除(绝不可将null相加,也绝不可替换为0),但仍将其usage_by_model[]token 总量计入下文的usage_by_model[]——该配置文件的用量获取成功,只有成本部分失败。此规则与partial_window例外形式相似(两者都只会排除美元金额),但这是一个独立条件——配置文件可以同时满足partial_window: false和cost_status: "cost_unavailable"。第三个例外——OpenRouter BYOK/重叠去重: 当胜出运行目录中同时存在openrouter-usage-profile.json和直接提供商配置文件(anthropic-usage-profile.json或openai-usage-profile.json)时,解决两者之间的重叠。无论下面适用哪个分支,都以相同方式匹配两个配置文件中的模型——OpenRouter 的usage_by_model[].model使用provider/model-name格式(例如"openai/gpt-4.1");如果直接配置文件中的模型名称等于 OpenRouter 模型名称最后一个/后面的子串,则二者视为“同一个”模型(例如"claude-sonnet-5")。 为保持向后兼容,请检查字段是否存在,而不是检查配置级别的值: 如果 OpenRouter 配置中完全没有usage_by_model[].cost_provenance字段(即在支持按模型检测之前写入的旧版配置),则直接采用下文所述的现有统一未知行为——不要要求按模型的字段必须存在,因为磁盘上旧的.migration/运行目录中不会有该字段。如果该字段存在(新格式配置),则改用按模型分支:- 对于 OpenRouter 的
usage_by_model[]中每一行,其模型名称与直连配置文件中的某个模型匹配:- 行的
cost_provenance为"byok_passthrough"→ 将该行的美元和 token 数值全部从此总和中排除(不只是排除美元数值——直连配置文件已经统计了该流量的 token,因此如果也计入 OpenRouter 的数据,两者都会重复计算;否则同一笔底层提供商支出会被计算两次:一次通过直连提供商自己的用量 API,一次通过 OpenRouter 路由的同一流量)。对于此模型,以直连提供商自己的数值为准。**例外——OpenRouter 自身的路由器/额度费用是一笔真实且独立的费用,并非重复费用:**纯 BYOK 行的monthly_cost_usd是 OpenRouter 自身的/activity.usage字段(根据 https://openrouter.ai/docs/api/api-reference/analytics/get-user-activity.md,`usage` =“消耗的 OpenRouter 额度总成本(美元)”),与直连配置文件已经统计的外部提供商自身推理账单(/activity.byok_usage_inference——“BYOK 推理成本(美元,消耗的外部额度)”)是分开的。排除该行的 token 数值始终正确(直连配置文件已经统计了这些 token)。但如果该行的monthly_cost_usd(即其汇总后的usage)非零,那么这笔金额就是 OpenRouter 自身的路由器费用,而非重复的推理成本——悄然丢弃它会导致少计,少计金额恰好就是该费用。排除byok_passthrough行时,若monthly_cost_usd非零,则追加一条capture_warnings条目(复用第 4 轮已确立的"mixed"行披露的确切机制):"<model> classified byok_passthrough — its $<amount> OpenRouter router/credit fee was excluded from this baseline along with its (correctly excluded, already-duplicated) token figures; this fee is a real charge NOT reflected in the figure above."若monthly_cost_usd为零(或可忽略,使用discover-openrouter-api.md分类逻辑采用的相同 $0.005 阈值),则无需披露——没有费用会被漏掉,与当前行为完全一致。不要将此费用加回总和(任务说明中的选项 (a))——此行的monthly_cost_usd是推理费用和路由器费用的合计值,并未拆分出单独的费用金额,因此无法根据此架构只加回“费用”部分;披露(选项 (b))是更简单且符合架构的修复方式。 - 行的
cost_provenance为"mixed"→ 出于与上述byok_passthrough相同的避免重复计算原因,将该行的美元和 token 数值全部从此总和中排除。与byok_passthrough不同,不要悄然丢弃其中的独立部分:"mixed"行同时包含 BYOK 直通流量(已通过直连配置文件统计)以及确实独立、由 OpenRouter 额度支付的流量,而此次排除也会移除后者,且没有其他地方会统计它——这部分独立流量是真实支出,却会从基线中漏掉,并未在其他地方重复计算。对于每一条被排除的"mixed"行,都要在计算出的基线警告中加入明确的少计披露(追加到capture_warnings——见下文),并在面向用户的摘要中披露:"<model> classified mixed (BYOK + independent OpenRouter usage) — excluded entirely to avoid double-counting the BYOK portion, but this also drops its independent OpenRouter-funded usage from the baseline, which is NOT reflected in the figure above."不要仅凭枚举值尝试估算或拆分独立部分的美元/token 数量——枚举值并未提供该行用量中各部分所占比例的信号;只需披露其缺失。触发此警告时,不要作出任何未经支持的“完整成本”或“节省”声明——遵循上文cost_unavailable/partial_window已采用的诚实披露方式。 - 行的
cost_provenance为"no_byok"→ 不要排除;这是已确认提供商不重叠的情况,因此应将 OpenRouter 自身对此模型的数据计入总和(比下文一概视为未知的回退方式更准确)。 - 行的
cost_provenance为"unknown"→ 仅对此行回退到保守策略:“按名称匹配,并排除所有匹配项”(与下文旧配置文件路径相同),并保留相同的注意事项句子,但只针对本次运行中实际使用回退策略的行,而非所有重叠模型,因为其中一些可能已通过no_byok/byok_passthrough/mixed得出结论。 - 行的
cost_provenance为"uncertain"→ 立即采用与上文"unknown"相同的保守处理方式:将该行的美元和 token 数值全部从此总和中排除(按名称匹配排除),并使用相同的注意事项句子,仅针对本次运行中使用此回退策略的行。不要复用"mixed"的少计披露分支——"uncertain"表示至少有一个参与的 permaslug 没有 Analytics 信号(部分验证),而不是像"mixed"那样确认存在由 OpenRouter 额度支付的独立部分;除了discover-openrouter-api.md在捕获时已经为此模型记录的capture_warnings条目所述内容外,没有其他具体情况可供披露。对"uncertain"采用"unknown"的策略,而不是另行创造一条规则,可以对确定性比"unknown"的保守默认值更低的数值,保持相同的防重复计算保护。
- 行的
- 像以前一样,将所有仅在 OpenRouter 中出现的模型(直连配置文件中没有匹配项)纳入总和。
- 不读取配置文件级别的
metadata.cost_provenance字段来判断是否执行此逐模型分支——只有是否存在逐模型字段才用于判断(见上文的门控条件)。 旧配置文件回退(不存在按模型区分的字段): 没有可靠信号可以自动判定重叠情况——请跨两个配置文件匹配模型(使用上述相同的名称匹配规则)。对于每一行 OpenRouterusage_by_model[],如果其模型与直接配置文件中的某个模型匹配,则将该行的美元金额和 token 数都从总和中排除。其余仅存在于 OpenRouter 中的模型(在直接配置文件中没有匹配项)仍按原样计入总和。在向用户展示的摘要中标明这项排除:"OpenRouter 和 <provider> 使用情况配置文件均存在;已从 OpenRouter 总计中排除 N 个重叠模型,以避免重复计算 BYOK 流量——由于来源未知,此排除假设这些模型完全重叠;如果 OpenRouter 仅对其部分流量使用 BYOK 路由,则可能导致统计偏低。"
- 对于 OpenRouter 的
-
暂存这些计算结果——此处不要写入。 文档进行到此处时,
$BEDROCK_RUN_DIR尚不存在(它仅在下方的“### A3 — 定位 Assess 输出,然后建立此技能自己的运行状态”中、Phase A 运行后才会设置);此处写入会指向尚未赋值的目录变量。请保留计算出的source_run_dir、source_profiles、captured_at、capture_warnings、windows、summary和usage_by_model[]值(结构见下方的“新建产物”),并立即向用户展示简短摘要: “找到现有使用情况数据:每月 $X,涵盖 N 个模型(来源:[提供商列表],捕获 于<date>)。我会将其用作当前成本基线,而不是根据采样的黄金数据集流量进行估算。” 实际文件将在 A3 中、设置$BEDROCK_RUN_DIR后立即写入——请参阅该处的交叉引用。 -
此步骤绝不会请求用户同意——它只会读取先前已获同意的运行所写入的文件。不会引入新的攻击面;无需新的同意关卡。
-
幂等性: 此步骤可在每次 Phase B 开始时安全地重新运行,包括恢复会话时(A2 的恢复路径)。它只执行只读文件操作——
usage-baseline.json实际会在稍后的 A3 中写入(见上面的第 7 步);该写入本身也是幂等的——使用相同输入重新运行时,覆盖usage-baseline.json会生成相同输出;使用不同输入重新运行时(例如上次运行后出现了更新的配置文件),则会正确地重新判定并覆盖。因此,在已运行过此步骤的恢复会话中再次运行也无害;此步骤不受任何“已运行”检查限制。
新产物:$BEDROCK_RUN_DIR/usage-baseline.json
在下文“### A3 — 定位评估输出,然后建立此技能自身的运行状态”中,设置 $BEDROCK_RUN_DIR 后立即将文件写入 $BEDROCK_RUN_DIR($REPO/.migration/.bedrock-<id>/)——不是在步骤 1.5 本身写入,因为步骤 1.5 运行时 $BEDROCK_RUN_DIR 尚不存在(步骤 1.5 的第 1–6 步仍在此处、Phase A 之前运行,因此 Phase A1 自身的全新 Discover 回退判断可以正确地提前结束;仅将文件的写入延后)。这样,Phase C 的报告生成器就有一个稳定且已解析的位置可读取,无需自行重新对 .migration/*/ 执行 glob(该 glob 仅属于此步骤)。
架构(按提供方划分的窗口状态映射,因此可以表示混合的部分窗口/完整窗口组合):
{
"source_run_dir": ".migration/0315-1030",
"source_profiles": ["anthropic-usage-profile.json", "openai-usage-profile.json"],
"captured_at": "<ISO 8601, max across source profiles>",
"capture_warnings": [],
"windows": {
"anthropic": { "window_days": 30, "active_days": 12, "partial_window": true, "cost_status": "partial_window" },
"openai": { "window_days": 30, "active_days": 30, "partial_window": false, "cost_status": "complete" }
},
"summary": {
"monthly_cost_usd": 0.0,
"monthly_cost_usd_is_blended_estimate": true,
"currency": "USD",
"models_seen": 0
},
"usage_by_model": [
{
"model": "string",
"provider": "openai|anthropic|openrouter",
"partial_window": true,
"input_tokens": 0,
"output_tokens": 0,
"num_model_requests": 0
}
]
}
验证规则:
source_profiles非空(仅当此步骤找到至少一个配置文件时才会写入该文件——文件不存在,而非空数组,表示“未找到现有配置文件”)。windows的键涵盖所有为usage_by_model[]提供数据的提供方;未出现在windows中的提供方也不得出现在usage_by_model[]中。usage_by_model[].provider用于区分来源;usage_by_model[].partial_window直接复制该行所属提供方的windows.<provider>.partial_window值——按行携带(而非仅按提供方携带),这样即使使用者只读取usage_by_model[]、不交叉引用windows,也能辨别哪些行仅供参考。summary.monthly_cost_usd_is_blended_estimate:只要windows中有任一提供方的partial_window: true,此值就为true——为true时,使用者(llm2bedrock-report-generator.md§6)必须将monthly_cost_usd标记为参考数值(并显示部分窗口提供方的active_days),不得将其作为不加限定的月度基线。当所有提供方的窗口均完整时,此标记为false,monthly_cost_usd即为正常的混合月度基线。这样既符合上文步骤 6 的要求,又无需将summary拆分为两个独立的顶层数值——唯一的monthly_cost_usd数值仍是步骤 6 计算的总和,布尔值则告知使用者应如何呈现它。windows.<provider>.cost_status:取值为"complete" | "partial_window" | "cost_unavailable",直接复制该提供方来源 usage-profile 的metadata.cost_status(目前只有 Anthropic usage profile 会写入此字段——参见任一同级技能中的discover-anthropic-api.md步骤 3;尚未写入该字段的 OpenAI/OpenRouter 配置文件,若partial_window为false,则默认设为"complete";若为true,则默认设为"partial_window",因为这些提供方尚未将窗口完整性和费用完整性拆分)。只要任一windows.<provider>.cost_status为"cost_unavailable",该提供方的金额就已在上文步骤 6 中从summary.monthly_cost_usd排除——使用者若要说明为何美元总额低于 token 用量所对应的预期金额,可在此按提供方查明原因,无需重新打开来源配置文件。- 明确说明
source_run_dir的路径解析方式:source_run_dir使用 POSIX 路径,且相对于$REPO(即每个子 Agent 上下文块中Repository:一行所传入的同一个$REPO)——绝不能是绝对路径,也不能相对于$BEDROCK_RUN_DIR。这样一来,仓库克隆或移动后,此产物仍可移植。写入方(上文步骤 7,必须从胜出的运行目录的绝对find结果路径中移除$REPO/前缀,然后再写入此处)和读取方(llm2bedrock-report-generator.md§6)都以相同方式解析:将$REPO与source_run_dir拼接得到绝对路径;当任一方需要直接重新打开来源配置文件,而非信任usage-baseline.json中已汇总的数值时,都使用此路径。
没有现有配置文件时的独立调用
如果此步骤没有找到任何匹配项,请完全依赖阶段 A 对 $ASSESS_BASE 自带的 discover.md 的现有内联执行(在本次运行中,由 Step 0b 将 $ASSESS_SKILL 解析为 gcp-to-aws 或 azure-to-aws,以实际解析结果为准)。该文件已通过自身的同意/触发逻辑提供 OpenAI/OpenRouter/Anthropic 用量发现功能——gcp-to-aws 目前在 Step 1e–1g 中实现此功能;azure-to-aws 也具备相同能力。此处不设计新的“提供实时发现”入口——在此步骤中重新实现会重复约 400 行的安全契约捕获逻辑,以及第三、第四、第五次重复提供/同意流程(每个云各一次),并可能导致不同调用点之间出现偏差。相反,阶段 A 的说明已经按路径指定了 $ASSESS_BASE/references/phases/discover/discover.md,并要求读者完整执行该文件——这就是现成的“按路径引用,不要重新实现”机制,可覆盖两个云。
需要进行一项调整:如果阶段 A 的内联 Discover 在新的 $MIGRATION_DIR 中捕获了一个全新的配置文件,请在阶段 A 的 Discover 子步骤末尾,针对该次运行自己的目录重新执行上述第 4–7 步(无需跨运行搜索),并以相同方式暂存计算结果(第 7 步的延迟写入原则在此同样适用——此时 $BEDROCK_RUN_DIR 仍不存在,因为 A3 尚未运行)。这样,无论配置文件是来自此步骤(复用)还是阶段 A 的新捕获,也无论由哪个云的 Assess 技能生成,阶段 C 始终会从同一位置读取。
**增量、可选——不影响现有流程:**如果不存在 usage-baseline.json,行为不会改变——现有的行为差异对比和基于黄金数据集样本外推的成本表(llm2bedrock-report-generator.md §6.3)会完全按当前方式运行。
阶段 A — 评估(内联运行 $ASSESS_SKILL 自带的 AI 路径文件)
不要自行从头阅读源代码、检测 AI SDK 或提出 Clarify 问题。
此阶段从磁盘读取 $ASSESS_SKILL 自带的阶段说明文件,并严格遵循这些说明,就像 $ASSESS_SKILL 独立运行一样——使用相同内容、相同状态文件和相同产物。与将 $ASSESS_SKILL 作为独立技能调用相比,唯一的区别是没有工具调用和轮次边界:以下所有内容都会在本次会话中内联运行。上文的 Step 1.5 已检查是否存在用量 API 配置文件;如果找到(来自 gcp-to-aws 或 azure-to-aws 的 Discover),则已将其计算结果作为 Bedrock 成本基线——暂存于内存中,直到下文“### A3 — 定位评估输出,然后建立此技能自身的运行状态”在 $BEDROCK_RUN_DIR 存在后将其写入 $BEDROCK_RUN_DIR/usage-baseline.json;无论如何,下面的阶段 A 都会完整运行——只有在未找到现有配置文件时,才会回退到 Discover 自身的全新用量 API 捕获或黄金数据集外推。
路径解析。 $ASSESS_SKILL 说明文件使用相对引用(references/phases/...、references/shared/...、references/vendored/...、references/design-refs/...、shared/...、design-refs/...、phases/...、data/...,包括简写形式)。所有此类引用都必须在 $ASSESS_BASE 下解析(定义见上文),并严格保留书写时使用的前缀,例如 shared/pricing-cache.md → $ASSESS_BASE/references/shared/pricing-cache.md。$MIGRATION_DIR 是唯一一个不在 $ASSESS_BASE 下解析的路径——它按照 $ASSESS_SKILL 自身约定保留在 $REPO 下(见下文 A1)。此处 $ASSESS_SKILL 的文件均为只读——此阶段绝不修改这些文件。
A1 — 运行 Discover、Clarify、Design 和 Estimate
开始前,告知用户:
“我现在将运行 Discover → Clarify → Design → Estimate 评估(使用
$ASSESS_SKILL独立运行时采用的相同逻辑),以检测您的 AI 工作负载并设计迁移到 Bedrock 的方案。过程中会向您提出一些问题,请作答。”
然后按顺序执行:
-
解析
$MIGRATION_DIR。 按照$ASSESS_BASE/references/phases/discover/discover.md的步骤 0 所述,检查$REPO中是否已有.migration/目录(如果有,列出已有运行,并提供 Resume/Fresh/Cancel 选项;否则,使用当前时间戳创建$REPO/.migration/<MMDD-HHMM>/,并将其设为$MIGRATION_DIR)。 已存在但尚无.phase-status.json的目录不算已有运行——将其视为新运行,并跳过 discover.md 中针对该目录的 Resume/Fresh/Cancel 提示(agent-advisor的migration-plan.md阶段 A 在其自身的内联 gcp-to-aws 委派中也记录了同样的例外)。这一点在此处很重要,因为 llm-to-bedrock 不会在此步骤之前预先创建$MIGRATION_DIR——但如果某次运行在此步骤创建目录后、discover.md 的步骤 0 写入.phase-status.json前中断,那么之后恢复时就会留下这样一个目录(存在,但没有状态文件);此时 discover.md 的 Resume 分支会尝试读取不存在的.phase-status.json。 -
完整阅读并执行
$ASSESS_BASE/references/phases/discover/discover.md,严格遵照其内容,包括其中初始化状态文件的步骤 0(恢复运行时跳过步骤 0——$MIGRATION_DIR已有.phase-status.json)以及步骤 1 的子发现关卡(1a–1e)。这些关卡已根据$REPO中实际存在的内容进行判断——只有存在 Terraform 文件时才会运行 IaC 发现,只有存在账单导出文件时才会运行账单发现,依此类推;无需对此进行引导。discover.md自身的使用情况 API 同意与采集操作(OpenAI、OpenRouter,以及 azure-to-aws 中的 Anthropic)会在适用时提供相应的 Admin API 使用情况发现;接受其中任何一项,都能让 Estimate 获得实际支出和 Token 用量,无需手动导出。(Anthropic 的 Admin 密钥没有可选权限范围——只能全部授权或不授权;旧句中“Usage 设置为 Read”的表述,对于 OpenAI 自有密钥来说本就有较窄意义上的错误,不应在这个概括性表述中继续沿用——请注意,这是在源文档中修复的相邻既有不准确之处,而非在此处照搬。)当 Discover 的步骤 0 写入本次运行的
.phase-status.json时,必须在owning_skill旁记录"initiated_by": "LLM_TO_BEDROCK"(owning_skill应设为实际执行的GCP_TO_AWS/AZURE_TO_AWS,即$ASSESS_SKILL自身的标识符——遵循 discover.md 对由其他技能启动的运行的说明):本次运行由 llm-to-bedrock 启动,遥测会据此进行归因。 当出现HANDOFF_OK时,$MIGRATION_DIR中至少存在以下文件之一:ai-workload-profile.json、gcp-resource-inventory.json/azure-resource-inventory.json或billing-profile.json。 -
完整读取并执行
$ASSESS_BASE/references/phases/clarify/clarify.md。它会自行进行路由——当ai-workload-profile.json是唯一的发现产物时,它会读取clarify-ai-only.md并运行该独立流程;如果还存在基础设施产物,则会改为运行片段/组装器拆分流程。无论哪种情况,都要严格遵循它加载的内容。 当出现HANDOFF_OK时,$MIGRATION_DIR中存在preferences.json。 -
完整读取并执行
$ASSESS_BASE/references/phases/design/design.md。当ai-workload-profile.json存在时,它会路由至design-ai.md——这是本技能的 Execute 阶段所依赖的文件。当出现HANDOFF_OK时,$MIGRATION_DIR中存在aws-design-ai.json(如果也运行了基础设施或计费路由,还会存在aws-design.json/aws-design-billing.json)。 -
完整读取并执行
$ASSESS_BASE/references/phases/estimate/estimate.md。无需继续执行 Estimate 之后的步骤——本技能只需要 Assess 产物(aws-design-ai.json、ai-workload-profile.json、preferences.json),因此一旦estimate.md到达 Estimate 后的决策关卡,选择不生成基础设施(或直接停在决策包处)即可;无需读取generate.md会生成的任何内容(Terraform、MIGRATION_GUIDE.md、migration-report.html)。
上面列出的每个文件都会按照自身协议管理 $MIGRATION_DIR/.phase-status.json——除步骤 2 中提到的 initiated_by 字段外,不要自行写入该文件。如果任何文件自身的 _postconditions/交接检查失败(GATE_FAIL),请停止并向用户原样展示该文件报告的内容;不要修改产物来强行通过关卡。
A2 — 确认 Assess 已完成
由于上方的阶段 A 在同一次执行中内联运行了所有步骤,直至 Estimate,因此 A1 的步骤 5 在未出现 GATE_FAIL 的情况下完成后,Assess 即视为完成。此检查是为了防止会话在 A1 中途被打断(例如,用户在 Clarify 期间停止,之后再恢复)——请重新读取 $MIGRATION_DIR/.phase-status.json,并从 phases 中尚未标记为 "completed" 的步骤继续执行 A1,而不是从 Discover 重新开始。
在信任重新读取的内容之前,先应用 $ASSESS_BASE/SKILL.md § State Validation(A1 各阶段文件读取此状态时也依赖同一套约束,因此此包装层的后备重新读取不能采用更宽松的标准)。具体而言:如果 .phase-status.json 无法解析(例如会话在写入过程中被打断,导致文件无效,而不只是阶段执行中途被打断),则必须且只能通过 § State Validation 检查 2 中的重建流程进行恢复——根据 $MIGRATION_DIR 中实际存在的产物推断已完成的阶段,将推断出的状态呈现给用户确认,并且只有在用户确认后才能重写 .phase-status.json。这是对 A1 中“不要自行写入该文件”规则的一项范围有限且明确的例外:根据此约束恢复 .phase-status.json,与阶段文件自身的状态管理不是同一行为;如果不执行恢复就继续,意味着此包装层会在未经验证的状态上恢复,而委托执行的每个阶段文件都不会如此处理。
**工作坊防护:**如果 phases.workshop == "in_progress",则 design.md 的内部工作坊流程正在重新定价(workshop-refresh.md 正在旧映射与新映射之间重写 aws-design-ai.json)——在将 Design 视为完成之前,先完成该循环(退出或拒绝时,它会将 phases.workshop 恢复为 "completed")。
A3 — 定位 Assess 输出,然后建立本技能自己的运行状态
使用已在 A1/A2 中解析并确认的同一个 $MIGRATION_DIR——此处不要重新选择目录。 $MIGRATION_DIR 已在 A1 步骤 1 中设置(恢复会话时,A2 重新读取并再次确认,但不会替换它);此时重新执行“查找最新目录”(ls -td "$REPO/.migration"/*/ | head -1)可能会选中一个比 A2 刚刚确认已完成的运行更新的不同运行——例如,其 Design 仍在进行中的同级运行。这样一来,B1 就会读取该同级运行尚未完成的模型映射,而不是此阶段实际评估的运行,且不会报错(较新的目录确实可能包含下方全部三个文件,但它们来自另一项尚未完成的评估)。如果此处 $MIGRATION_DIR 意外未设置(鉴于上面的 A1/A2 步骤,这种情况本不应发生),那就是此阶段自身状态跟踪中的错误——请停止并报告,而不要通过 ls -td 猜测目录。
请确认 $MIGRATION_DIR 中以下三个文件全部存在(Phase B 会读取每一个文件):
aws-design-ai.json(模型映射 + 架构)ai-workload-profile.json(检测到的工作负载)preferences.json(Clarify 收集的用户偏好)
如果缺少其中任何一个文件,说明 Assess 未正确完成 AI 路径——请指出缺少的文件,显示错误,然后停止。A1 顺利完成且没有 GATE_FAIL 时,理论上应已确保这些文件存在;此步骤是兜底检查,确保 Execute 读取这些文件之前确认无误。
建立此技能自身的运行状态(读取-合并-写入)。此技能会维护一个独立于委派运行的轻量级运行状态文件,以便在使用情况漏斗中以自身名称显示 AI 迁移。它会声明自己的状态结构(阶段 assess、execute);共享 DSL 的读取-合并-写入规则同样适用。此处不要改动委派运行的 .phase-status.json:由本次调用创建的运行已包含 initiated_by(A1 设置),而重写较早运行的文件会让遥测钩子将其历史误认为新工作。
-
将
$BEDROCK_RUN_DIR设置为$REPO/.migration/.bedrock-<id>/,其中<id>是$MIGRATION_DIR的基本名称(例如.migration/.bedrock-0910-1100/)。开头的点可使其避开上方的ls -td "$REPO/.migration"/*/查找,因此绝不会被误认为 Assess 运行目录;将其与委派运行关联,可确保恢复迁移时复用该目录。-
如果
$BEDROCK_RUN_DIR/.phase-status.json已存在,则表示正在恢复此迁移:保留该文件(包括其run_id)并继续。 -
否则,创建该目录并写入
.phase-status.json:{ "migration_id": ".bedrock-<id>", "last_updated": "<ISO 8601 now>", "current_phase": "execute", "run_id": "<fresh UUID from uuidgen>", "owning_skill": "LLM_TO_BEDROCK", "phases": { "assess": "completed", "execute": "in_progress" } }
assess/execute阶段名称尚未纳入遥测 API 模型,因此此运行只有运行级事件(RUN_STARTED、RUN_COMPLETED)会离开本机;AI 旅程的入口已通过委派运行的事件体现,这些事件包含initiatingSkill。 -
-
现在
$BEDROCK_RUN_DIR已存在,写入usage-baseline.json。 如果步骤 1.5(或步骤 1.5 的“独立调用”说明中提到的 Phase A 自身新捕获的 Discover 信息)在第 7 步中保留了计算结果,现在使用上文步骤 1.5“新工件”部分中的架构,将其写入$BEDROCK_RUN_DIR/usage-baseline.json。如果没有保留任何结果(步骤 1.5 中没有匹配项,且 Phase A 的 Discover 也没有新的配置文件),则完全跳过此写入操作——usage-baseline.json不存在本身就是“无基线”的信号,llm2bedrock-report-generator.md§6.3 会检查此信号。 -
创建或验证恢复的 Bedrock 运行后,按照
references/vendored/telemetry/PROTOCOL.md,以cli报告模式进行协调。 在hook模式下,将报告工作交由主机钩子处理。
Phase B — 执行准备工作
B1 — 读取 Assess 输出
读取 $MIGRATION_DIR/aws-design-ai.json 并提取:
ai_architecture.bedrock_models[]→{source_model, aws_model_id, use_case}数组- 将所有
aws_model_id值收集到$TARGET_MODELS(数组)中。保留每个值对应的use_case: 预检脚本会自动使用正确的 API 探测每个模型(聊天使用 Converse,嵌入使用 InvokeModel),但评估器的质量评分仅适用于聊天模型——嵌入目标只会进行格式/维度验证。
读取 $MIGRATION_DIR/ai-workload-profile.json 并提取:
summary.ai_source→ 源提供商
读取 $MIGRATION_DIR/preferences.json 并提取:
design_constraints.target_region→$REGION(如果不存在,默认为us-east-1)
验证: 如果 aws-design-ai.json 中没有 ai_architecture.bedrock_models[] 数组,或该数组为空,则停止:"Assess 输出不完整——缺少模型映射。"
B2 — 确认 AWS 身份
aws sts get-caller-identity 2>&1
如果命令失败(没有凭证、SSO 令牌过期):显示错误,并告知用户运行 aws configure 或 aws sso login(建议输入 ! aws sso login 在此会话中运行),然后重新执行 B2。未确认身份前不得继续。
成功后,通过 AskUserQuestion 显示 Account、Arn、UserId: "此 AWS 身份将用于调用 Bedrock。是否正确?"
选项:
- 是,使用此身份 → 继续
- 使用其他 AWS 配置文件 → 询问使用哪个配置文件,将其记录为
$AWS_PROFILE_CHOICE, 然后以aws sts get-caller-identity --profile $AWS_PROFILE_CHOICE重新执行 B2,并再次确认。 不要依赖导出AWS_PROFILE——环境变量不会在 Bash 工具调用之间持久保留,也不会传递给工作流子 Agent(见 B3)。应改为在所有地方显式传递此选项: 每次调用 aws CLI 时都使用--profile,并在 B4 预检命令前内联添加AWS_PROFILE=$AWS_PROFILE_CHOICE,以及在工作流参数(awsProfile字段)中指定,以便子 Agent 也能采用相同做法。
同时确认区域:"Bedrock 区域将为 $REGION。确定或要覆盖?"
B3 — 源 API 密钥(可选)
首先,创建产物目录,并立即将其设为自动忽略——务必在任何密钥存在之前执行,这样密钥就不会出现在未被忽略的工作树中(即使用户在重写器运行前中止也一样):
mkdir -p "$REPO/.saws-migrate" && printf '*\n' > "$REPO/.saws-migrate/.gitignore"
根据 B1 中的源提供商确定 $KEY_ENV_VAR(这是基线技能的解析器所期望的环境变量名称——不带 NAME= 前缀的裸密钥将无法解析):
openai→OPENAI_API_KEYanthropic→ANTHROPIC_API_KEYgoogle/gemini→GEMINI_API_KEY
密钥绝不能进入本次对话(硬性规则)。 不要要求用户在聊天中粘贴密钥,也绝不要在任何命令、问题或输出中回显、使用 cat 输出或插入密钥的值——Agent 只会处理文件路径。如果用户未经提示就在聊天中粘贴了密钥,不要使用它:告知用户该密钥现在已包含在对话记录中,建议轮换密钥,然后继续使用以下某种方式。
首先检查密钥是否已存在于 Shell 环境中(每次 Bash 调用都会从用户配置文件初始化,因此由配置文件导出的密钥在每次调用中都可见)。兼容 POSIX——printenv 在 bash 和 zsh 中都可用(${!VAR} 间接引用仅适用于 bash,在 zsh 中会报错)。此命令只显示密钥是否存在,绝不显示其值:
[ -n "$(printenv "$KEY_ENV_VAR")" ] && echo ENV_KEY_PRESENT || echo ENV_KEY_ABSENT
AskUserQuestion:“你有源模型的 API 密钥吗(例如 GPT-4o 的 OpenAI 密钥)?提供密钥即可进行并排质量比较。若不提供,评估将仅使用绝对评分。”
选项(仅当 ENV_KEY_PRESENT 时提供第一项):
-
使用我环境中已有的
$KEY_ENV_VAR→ 通过一条命令将环境变量写入文件——其值不会出现在对话记录中:printf '%s=%s\n' "$KEY_ENV_VAR" "$(printenv "$KEY_ENV_VAR")" > "$REPO/.saws-migrate/.source-provider-env" && chmod 600 "$REPO/.saws-migrate/.source-provider-env"然后运行下面的格式检查,并设置
sourceBaselineAvailable = true、sourceKeyRef = "$REPO/.saws-migrate/.source-provider-env"。 -
我会自行将其写入文件 → 告知用户在他们自己的终端中运行此命令(不要通过 Agent 运行;在 Claude Code 中,
!前缀会在会话中运行命令)——read -rs会在不回显的情况下读取密钥:read -rs k && printf '%s=%s\n' "<KEY_ENV_VAR>" "$k" > "<REPO>/.saws-migrate/.source-provider-env" && chmod 600 "<REPO>/.saws-migrate/.source-provider-env" && unset k呈现命令时,替换为实际的环境变量名称和仓库路径(这些都不是机密)。然后运行下面的格式检查,并设置与上面相同的标志。
-
跳过 →
sourceBaselineAvailable = false,sourceKeyRef = ""。
无论通过哪种方式写入文件,都要验证格式(不会输出密钥;可捕获未带 NAME= 前缀写入的值,否则基线解析器会静默漏掉它):
grep -qE '^(OPENAI|ANTHROPIC|GEMINI)_API_KEY=.+' "$REPO/.saws-migrate/.source-provider-env" && echo KEY_FORMAT_OK || echo KEY_FORMAT_BAD
如果出现 KEY_FORMAT_BAD,让写入文件的同一路径重写文件(不要回显文件内容)。
(.saws-migrate/ 已在上面的第一条命令中设为自动忽略;重写器会在任何提交之前再次确认这一设置,作为第二层保护。)
重要: 文件是交接机制——不要依赖 export 将密钥传递到后续步骤。在一次 Bash 调用中设置的 Shell 状态不会保留到其他调用或工作流子 Agent 中;只有由配置文件导出的变量(即上面的 ENV_KEY_PRESENT 路径)能可靠地保持可见,即便如此,也必须将其写入文件,供基线运行器使用。
B4 — Bedrock 预检
uv run --project $SCRIPTS python $SCRIPTS/preflight_bedrock.py --region $REGION --models <comma-separated $TARGET_MODELS> --dataset-size 200
(--dataset-size 200 与黄金数据集上限相同,因此配额警告反映的是最坏情况。如果 B2 选择了非默认配置文件,请在前面加上 AWS_PROFILE=$AWS_PROFILE_CHOICE。)
解析 JSON 输出。失败时,顶层会包含 reason/detail(取自首个失败的模型)以及 failing_models(所有失败的 ID);各模型的判定结果位于 models[] 中:
ok == false+reason: credentials→ 显示详情(配置/刷新凭证)并停止;用户修复后重新运行。ok == false+reason: model_access→ 尚未在 Bedrock 控制台中启用模型访问权限(这不是 IAM 问题):告知用户前往控制台的 Model access 页面,为失败的模型启用访问权限,然后停止;启用后重新运行 B4。ok == false+reason: authz→ IAM 拒绝了推理请求。对于 Converse/InvokeModel 目标,需要授予的操作是bedrock:InvokeModel;对于仅限 mantle 的openai.gpt-5*目标,需要授予bedrock-mantle:*这一组权限(参见 B4a)。detail会说明具体操作。告知用户需要授予的操作,然后停止。ok == false+reason: mantle_deps_missing→ 固定版本的脚本环境缺少openai/aws-bedrock-token-generator,因此完全无法探测仅限 mantle 的目标。这是环境故障,不是 Bedrock 判定结果:告知用户重新同步(uv sync --project $SCRIPTS),然后停止。不要继续——访问权限尚未经过验证。ok == false+reason: model_unavailable→ 阅读$HELPERS/resolve-bedrock-model-id/resolve-bedrock-model-id.md中的resolve-bedrock-model-id参考文档,并按照其中的流程处理failing_models中的每个 ID 和区域。通过 AskUserQuestion 向用户提供候选项:“使用<candidate>(跨区域推理配置文件)”/“粘贴其他模型 ID”/“中止”。用户选择后,替换$TARGET_MODELS中的 ID 并重新运行 B4。ok == false+ 其他任意reason→ 显示detail并停止。ok == true→ 继续。告知用户任何quota_warning,以及任何reason为embedding_unprobed的模型(预检无法探测的嵌入模型系列——提醒用户在控制台中确认模型访问权限)。
阶段 C — 执行
阶段 C 通过 Agent 工具按顺序分派五个插件 Agent(子 Agent 类型为
aws-startup-advisor:llm2bedrock-code-analyzer、aws-startup-advisor:llm2bedrock-log-ingestor、aws-startup-advisor:llm2bedrock-prompt-evaluator、
aws-startup-advisor:llm2bedrock-code-rewriter、aws-startup-advisor:llm2bedrock-report-generator)。每个 Agent 都会将结果写入
$PHASE_DIR = $REPO/.saws-migrate/phase-results/ 下的文件;在继续之前,你必须使用随附的验证器验证每个文件。这里没有工作流运行时——文件本身就是状态。
验证器(每一步都会使用)
uv run --project $SCRIPTS python $SCRIPTS/validate_result.py --schema <analysis|ingestion|eval|rewrite|delta-decisions> <file>
- 退出码为 0,且
RESULT=valid CONTROL=ok→ 阶段已完成;继续。 - 退出码为 0,且
CONTROL=blocked REASON=<r>→ 进入阻塞流程(如下)。 - 退出码为 0,且
CONTROL=partial COMPLETED=<n> TOTAL=<m>→ 进入部分完成流程(仅限评估)。 - 退出码为 1(
RESULT=invalid+ 错误行)或退出码为 2(文件缺失)→ 无状态修复重试: 分派一个同类型的新 Agent,其提示词由原始上下文块 + 文件路径 + 验证器逐字输出的错误信息 + 以下指令组成:“仅修复<path>处的输出文件,使其通过验证;除非文件确实缺少必填字段,否则不要重做该阶段的工作”。每个阶段最多重试 2 次;之后停止并显示错误。
上下文块(每次调度时实例化)
按照此精确格式构建每一行(Agent 会解析这些标签)。标记为可选的行若为空则省略:
Repository: <$REPO>
AWS region: <$REGION>
AWS profile (pass as --profile / AWS_PROFILE= inline on every aws/boto3 invocation): <$AWS_PROFILE_CHOICE — omit line if default>
Target Bedrock model(s): <comma-joined $TARGET_MODELS, with any resolved overrides already applied>
Migration plan dir: <$MIGRATION_DIR>
Resolved target model id: <override for the primary chat model — omit if none>
Scripts directory (pinned uv toolchain): <$SCRIPTS>
Report date suffix: <saved suffix from run-context — C5/C6 dispatches only>
Usage baseline path: <$BEDROCK_RUN_DIR/usage-baseline.json — C5/C6 dispatches only, omit line if the file is absent>
Source baseline available: <true|false>
Source provider env file: <path — omit if none>
User-supplied log files: <comma-joined — omit if none>
Golden dataset cap (max cases the ingestor may emit): 200
Phase results directory: <$PHASE_DIR>
Prior phase results (Read these files): <paths of already-validated phase JSONs>
Confirmed behavior-delta decisions file (Read it): <$PHASE_DIR/delta-decisions.json — C5 only>
<helper-reference lines — inject ONLY the ones this agent needs, per the table below>
前一阶段的结果以文件路径的形式传递——绝不要将其中的 JSON 内联到提示词中。
Helper 参考(以前的 helper 技能,现在位于 $HELPERS 下)。 Agent 不再按名称加载技能;而是由你注入绝对路径,让 Agent 读取 helper 参考。每次调度时,只添加该 Agent 会用到的 helper 行(依据其 # 4 部分):
| Agent(调度) | 要添加的 helper 参考行 |
|---|---|
| C1 llm2bedrock-code-analyzer | behavior-delta-detection reference: $HELPERS/behavior-delta-detection/behavior-delta-detection.md;resolve-bedrock-model-id reference: $HELPERS/resolve-bedrock-model-id/resolve-bedrock-model-id.md |
| C5 llm2bedrock-code-rewriter | bedrock-known-fixes reference: $HELPERS/bedrock-known-fixes/bedrock-known-fixes.md;behavior-delta-detection reference: $HELPERS/behavior-delta-detection/behavior-delta-detection.md;dependency-conflict-resolution reference: $HELPERS/dependency-conflict-resolution/dependency-conflict-resolution.md |
| C3 llm2bedrock-prompt-evaluator | bedrock-known-fixes reference: $HELPERS/bedrock-known-fixes/bedrock-known-fixes.md;resolve-bedrock-model-id reference: $HELPERS/resolve-bedrock-model-id/resolve-bedrock-model-id.md;run-source-model-baseline reference: $HELPERS/run-source-model-baseline/run-source-model-baseline.md |
| C2 llm2bedrock-log-ingestor、C6 llm2bedrock-report-generator | (无——这些 Agent 不加载 helper) |
将 $HELPERS 展开为其绝对路径(你有 <SKILL_BASE>),这样子 Agent(其中 ${CLAUDE_PLUGIN_ROOT} 为空)就能获取可解析的绝对路径。
C0 — 运行上下文门控(恢复安全)
Eval 阶段会为每个黄金用例调用一次付费的 Bedrock(如果提供了源密钥,还会为每个用例调用一次付费的源提供商)。在任何调度之前,告知用户评估将调用 Bedrock,费用由用户承担,且最多处理 200 个用例。
mkdir -p $PHASE_DIR。构建$PHASE_DIR/current-context.json,其中必须且只能包含以下字段(使用shasum -a 256计算哈希;密钥哈希是指纹——绝不存储密钥值):
{
"repo_root": "<cd $REPO && pwd -P>",
"migration_dir": "<$MIGRATION_DIR>",
"region": "<$REGION>",
"aws_profile": "<$AWS_PROFILE_CHOICE or \"\">",
"aws_account": "<Account from B2>",
"repo_head_sha": "<git -C $REPO rev-parse HEAD>",
"repo_branch": "<git -C $REPO rev-parse --abbrev-ref HEAD>",
"repo_dirty_sha256": "<sha256 of: git status --porcelain + git diff + git diff --cached, EACH with pathspecs -- . ':(exclude).saws-migrate' ':(exclude).migration' ':(exclude)MIGRATION_REPORT_*.md'; \"\" when all three are empty>",
"target_models": [{"source_model": "...", "aws_model_id": "...", "use_case": "..."}],
"resolved_model_overrides": {},
"source_provider": "<from B1>",
"source_baseline_available": <true|false from B3>,
"source_key_sha256": "<sha256 of .source-provider-env contents, \"\" when absent>",
"usage_baseline_sha256": "<sha256 of $BEDROCK_RUN_DIR/usage-baseline.json contents, \"\" when absent>",
"log_files": [{"path": "...", "sha256": "..."}],
"max_golden_cases": 200,
"assess_design_sha256": "<sha256 of $MIGRATION_DIR/aws-design-ai.json>",
"report_date_suffix": "<date +%Y-%m-%d>",
"schema_version": 1,
"plugin_version": "<version from <plugin>/.claude-plugin/plugin.json>"
}
-
阶段 0(C5 后规范化)。 如果
$PHASE_DIR/rewrite.json存在且验证为有效负载(CONTROL=ok),则不要使用实时的repo_*值。运行三项完整性检查: (1)rewrite.baseline_parent_sha等于已保存的repo_head_sha;(2)git rev-parse <rewrite.branch_name>等于rewrite.branch_tip_sha;(3)git status --porcelain(包含 工件排除项)为空。全部通过 → 将已保存的repo_*值原样复制到 current-context,继续步骤 3。检查 2 失败(分支提示已移动)→ 停止并提问 AskUserQuestion:"保留你的提交(仅重新生成报告,并注明混合作者)" / "将分支重置到重写器的提示,并从 C6 重新生成" / "中止"。检查 3 失败 (工作树有未提交更改)→ 停止并询问:提交/暂存(然后重新检查)或丢弃这些编辑。检查 1 失败 → 在步骤 3 中将其视为完整的repo_*不匹配。 -
如果
$PHASE_DIR/run-context.json存在,则进行比较:
uv run --project $SCRIPTS python $SCRIPTS/validate_result.py --check-run-context $PHASE_DIR/run-context.json --current $PHASE_DIR/current-context.json
RUN_CONTEXT=match→ 恢复执行:按顺序遍历 C1→C2→C3→(C4:delta-decisions.json)→C5→(C6:报告 文件);仅当阶段文件通过验证且为CONTROL=ok时,才算该阶段已完成(C6: 仅当rewrite.json为有效负载且MIGRATION_REPORT_<saved suffix>.md存在时才算完成)。 在首个缺失/无效/控制状态异常的文件处停止遍历——被阻止/不完整的文件按下方对应流程处理,绝不可计为已完成。向用户提供“跳过已完成阶段 X..Y,从 Z 恢复”的选项。无法解释的间隔之后的文件:与该间隔一起归档。RUN_CONTEXT=mismatch→ 执行范围限定的失效处理。根据下表映射每一行 MISMATCH,将列出的单元归档到$REPO/.saws-migrate/phase-results-archive/<saved suffix>-$(date +%H%M%S)/(与 phase-results/ 同级——绝不可嵌套其中),然后立即使用 current-context.json 覆盖 run-context.json(保留已保存的report_date_suffix,除非 REPORT 本身正在失效),再按顺序重新运行 失效的阶段。告知用户哪些字段不同以及需要重新运行哪些阶段。
| 不匹配的字段 | 归档(单元) | 保留 |
|---|---|---|
| repo_root, migration_dir, region, aws_profile, aws_account, source_provider, assess_design_sha256, schema_version, plugin_version | 所有内容 | — |
| repo_head_sha / repo_branch / repo_dirty_sha256 | 所有内容 | — |
| target_models / resolved_model_overrides | ANALYSIS, EVAL, REWRITE, REPORT | INGESTION |
| log_files / max_golden_cases | 所有内容 | — |
| source_key_sha256 / source_baseline_available | ANALYSIS, EVAL, REWRITE, REPORT | INGESTION |
| usage_baseline_sha256 | EVAL, REWRITE, REPORT | ANALYSIS, INGESTION |
单元:ANALYSIS = analysis.json · INGESTION = ingestion.json + .saws-migrate/golden-dataset/
· EVAL = eval.json + .saws-migrate/eval-results/(不含 cost_compare.py)· REWRITE =
rewrite.json + delta-decisions.json · REPORT = MIGRATION_REPORT_<saved suffix>.md。
C5 之后重新运行 C1–C3 需要迁移前的树。 如果 rewrite.json 的负载有效,而表格将 ANALYSIS/INGESTION/EVAL 标记为失效:先与用户确认是否丢弃旧迁移分支(如果分支尖端已移动,先执行保留或重置流程),然后执行 git checkout <saved repo_branch>,删除旧分支和 saws-migrate-baseline 标签,再从 C1 重新运行。如果用户拒绝,则停止——重新分析包含 rewrite 的树会产生垃圾结果。
- 没有已保存的运行上下文 → 全新运行:将 current-context.json 写入 run-context.json,调度 C1。
C1 — 分析器 · C2 — 导入器 · C3 — 评估器
按顺序处理每个阶段:使用上下文块(列出所有前序阶段的文件路径)调度 Agent,然后验证其输出文件:
| 步骤 | agentType | 输出文件 | Schema |
|---|---|---|---|
| C1 | aws-startup-advisor:llm2bedrock-code-analyzer | $PHASE_DIR/analysis.json | analysis |
| C2 | aws-startup-advisor:llm2bedrock-log-ingestor | $PHASE_DIR/ingestion.json | ingestion |
| C3 | aws-startup-advisor:llm2bedrock-prompt-evaluator | $PHASE_DIR/eval.json | eval |
阻塞流程(CONTROL=blocked):根据 REASON 与用户一起解决问题 —
model_access→ 用户在 Bedrock 控制台中启用模型(指纹所依据的内容没有变化;只重新调度被阻塞的阶段)model_unresolvable→ 用户选择或粘贴一个 ID → 将其记录到resolved_model_overrides,并将其合并到 Target 行source_key_auth→ 用户提供新密钥(重新运行 B3),或将基线设为不可用authz→ IAM 拒绝推理。detail指明需要授予的操作集: 对 Converse 目标,授予bedrock:InvokeModel*;对仅使用 mantle 的openai.gpt-5*目标,授予bedrock-mantle:*操作(CreateInference、CallWithBearerToken)。 用户修复 IAM;指纹所依据的内容没有变化,因此只重新调度被阻塞的阶段。 不要将其归为model_access——IAM 拒绝不能通过控制台的 Model access 页面修复,反之亦然。mantle_deps_missing→ 固定的脚本环境缺少openai/aws-bedrock-token-generator,因此完全无法探测 mantle 目标。用户重新同步(uv sync --project $SCRIPTS);只重新调度被阻塞的阶段。访问从未经过验证,因此不能将之前的通过视为仍然有效。assess_output_missing→ 重新运行 Phase A,然后从 C0 重新启动 Phase C
完成任何解决操作后,都要重新运行 C0 配方(重建 current-context、应用失效表、覆盖 run-context),并从最早的失效阶段开始重新调度——从哪里恢复执行由表格决定,而不是由阻塞位置决定。
部分流程(仅限评估,CONTROL=partial):使用 AskUserQuestion —
- 继续处理剩余案例 → 使用以下附加上下文行重新调度评估器:
Resume: raw_results.jsonl already contains completed cases — evaluate only prompts whose ids are not present in it, then re-score and overwrite eval.json - 按部分通过率继续 → 使用以下内容重新调度评估器:
Finalize partial: do NOT call Bedrock again — score the cases already in raw_results.jsonl and emit the FULL eval payload over only those cases, with total_cases = the number scored and a notes prefix line 'partial_coverage: <completed>/<total> cases (throttled)'。然后正常运行 C4。 - 中止 → 停止;文件会保留在磁盘上,以便稍后通过 C0 恢复。
C4 — 侧边栏(两个关卡)+ 持久化决策
关卡 (a) — 质量准入判定。 读取 $PHASE_DIR/eval.json。阈值为
通过率 >= 0.9 且 source_baseline_quality != 'poor'(若备注中有 no_golden_cases: true,
则没有质量信号 — 始终询问)。达到或超过阈值 → 静默继续。
低于阈值时,使用 AskUserQuestion:
- 仍然继续 → 进入关卡 (b)
- 更改目标模型 → 记录到
resolved_model_overrides,重新运行 C0(该表会使 ANALYSIS/EVAL 失效,并从 C1 恢复执行)。最多重试 2 次。 - 中止 → 停止,不修改代码。
关卡 (a.5) — 重写策略(来自迁移计划)。 从 $MIGRATION_DIR/aws-design-ai.json →
ai_architecture.code_migration.migration_path 读取 migration_path。
如果该值以 "mantle" 开头("mantle"、"mantle_openai_responses"),则设置
rewrite_strategy = "mantle"。否则(该值为 "converse"、"gpt-oss",或该字段不存在),设置
rewrite_strategy = "converse"。
无需询问用户 — 该决策已在 Assess/Design 阶段做出。
按前缀匹配,而非完全相等:对于同模型 OpenAI 迁移,Design 会写入更具体的
"mantle_openai_responses";若与 "mantle" 完全匹配,这些运行就会被静默导向 Converse 路径
——把可正常工作的同模型代码重写为针对不支持 Converse 接口的模型的 boto3 Converse 客户端。
关卡 (b) — 行为差异处理。 对每个 analysis.behavior_deltas[] 中
user_visible == true 的条目,使用行为差异检测参考中的选项调用 AskUserQuestion(读取 $HELPERS/behavior-delta-detection/behavior-delta-detection.md,以及其 references/ 目录下的 source_provider 子参考)。
持久化: 将决策数组(条目格式为 {delta_type, location, resolution_chosen, source};若没有用户可见的差异,则为 [])写入 $PHASE_DIR/delta-decisions.json
并进行校验(--schema delta-decisions)。该文件必须在 C5 之前存在 — 它能确保 C5 重试或崩溃后恢复时可独立完成。
C5 — 重写 · C6 — 报告
| 步骤 | agentType | 输出 | 架构 |
|---|---|---|---|
| C5 | aws-startup-advisor:llm2bedrock-code-rewriter | $PHASE_DIR/rewrite.json | rewrite |
| C6 | aws-startup-advisor:llm2bedrock-report-generator | 仓库根目录中的 MIGRATION_REPORT_<saved suffix>.md | (无——通过文件是否存在来检查是否完成) |
C5 的上下文块包含 Confirmed behavior-delta decisions file 行和 Report date suffix 行(来自 run-context;恢复运行时不使用今天的日期)。C6 的上下文块列出全部四个阶段结果文件路径。
当 rewrite_strategy == "mantle" 时,C5 的上下文块还会包含:
Rewrite strategy: mantle(Converse 时完全省略此行——缺少此行表示使用默认的 Converse 路径)Mantle model map: <source-model> -> <bedrock-model-id>——来源于计划中的ai_architecture.bedrock_models[]条目(每个source_model→aws_model_id对)。- 当任何映射的
aws_model_id是专有 GPT 模型(openai.gpt-5*)时,包含Mantle surface: responses和Mantle base path: /openai/v1。这些模型仅通过/openai/v1路径上的 Responses API 提供服务,与其他 mantle 模型使用的v1路径不同,因此重写器不得为它们生成/v1基础 URL 或 Chat Completions 调用。参见$ASSESS_BASE/references/shared/openai-on-bedrock.md。 - 当
bedrock_models[].model_change为false时,包含Same model: true。这会提示重写器保持模型参数不变,仅修改端点、凭证、模型 ID,以及(如果源代码使用了 Chat Completions)接口形式。
C7 — 渲染摘要
uv run --project $SCRIPTS python $SCRIPTS/render_report.py --phase-results $PHASE_DIR --repo $REPO --date-suffix <saved suffix>
打印摘要。告知用户 rewrite.branch_name(通常为 bedrock-migration;如果该分支已存在,则会使用带冲突后缀的名称,例如 bedrock-migration-2)和报告文件的位置。告知他们如何撤销——使用 rewrite.branch_name 中的实际分支名称替换分支名,切勿硬编码(发生冲突时,bedrock-migration 是用户原有的分支,删除它会毁掉他们的工作):
要放弃更改:
git checkout <your original branch>、git branch -D <rewrite.branch_name>、git tag -d saws-migrate-baseline和rm -rf .saws-migrate .migration会移除所有迁移产物(包括 API 密钥文件)。
关闭此技能的运行状态(对 $BEDROCK_RUN_DIR/.phase-status.json 执行读取-合并-写入):
将 phases.execute 设为 "completed",将 current_phase 设为 "complete",并更新 last_updated。此操作会在使用漏斗中标记 AI 迁移已完成。
写入此状态后,按照 references/vendored/telemetry/PROTOCOL.md 中的说明,在 cli 报告模式下进行协调;在 hook 模式下跳过 CLI。
仅访问模式
当用户希望检查或启用指定模型的 Bedrock 模型访问权限,而非进行代码迁移时,会从步骤 0a-bis 进入此模式。它会复用身份检查(B2)和预检(B4),但模型列表由用户提供,并且不会涉及 gcp-to-aws、Assess、源密钥(B3)或阶段 C。它回答“我需要启用哪些模型,以及如何启用?”——然后停止。不会创建 git 分支,也不会进行重写。
AC1 — 设定预期(准确性 — 首先说明)
在收集模型之前,先告诉用户:
"关于在 Bedrock 上“访问模型”,有几点需要说明:
- Claude、Llama、Nova、Mistral 等是
bedrock-runtime端点上的 Bedrock 基础模型 — 推理使用bedrock:InvokeModel/Converse。在商业区域中,只要调用方拥有 AWS Marketplace 权限(aws-marketplace:Subscribe/Unsubscribe/ViewSubscriptions),默认即可访问 — 首次调用时会自动订阅模型。控制台中的 Model access 页面用于明确启用模型及查看目录(也是 GovCloud 中的必需流程)。Anthropic 模型在调用前还需要每个账户填写一次 First-Time-Use 表单(PutUseCaseForModelAccess)— 但通过bedrock-mantle访问时除外。- Bedrock 上有几种形式的 OpenAI 模型,都确实存在 — 它们使用哪个端点取决于具体 ID,并非一概而论:
- 开放权重
gpt-oss(openai.gpt-oss-20b-1:0、openai.gpt-oss-120b-1:0)→ 通过bedrock:InvokeModel/Converse使用bedrock-runtime(其 Responses API 也可通过bedrock-mantle使用)。- 纯专有 GPT ID(
openai.gpt-5*,不包括gpt-oss)→ 在bedrock-mantle端点上进行探测(Responses API)。- 以
us./in./global.为前缀的 GPT-5.6 CRIS profile ID(例如global.openai.gpt-5.6-sol)→ 支持 Converse 的bedrock-runtime目标。bedrock-mantle路径使用一套独立的操作 —bedrock-mantle:*(例如AmazonBedrockMantleInferenceAccess托管策略:bedrock-mantle:CreateInference+CallWithBearerToken)— 与bedrock:InvokeModel不同。预检会针对每个模型分别判断;不要想当然。- 在 Bedrock 上无法调用的是 OpenAI 自己托管在 api.openai.com 上的 API — 这仍由 OpenAI 提供。“Bedrock 上的 GPT”指的是上述由 AWS 提供服务、通过 AWS 端点和 IAM 访问的模型。
预检会自动通过正确的 API 探测每个模型,并准确报告每个模型需要启用的访问权限;我会转告你。"
AC2 — 收集请求的模型和区域(暂不解析易读名称)
- **模型(仅原始请求):**如果
$ARGUMENTS中指定了模型 ID,直接使用 — 跳过 解析,因为它们已经是 ID。否则,使用 AskUserQuestion 提问:“你想访问哪些模型? 请提供 Bedrock 模型 ID(例如anthropic.claude-sonnet-4-5-v1:0、openai.gpt-oss-120b-1:0),或提供提供商 + 名称,我会解析 ID。”将用户提供的所有内容 (ID 和/或易读名称)收集到$REQUESTED_MODELS中 — 此处不要调用易读名称解析器。 解析器自己的命令 (aws bedrock list-foundation-models/list-inference-profiles)需要指定--region, 并且会使用调用时处于活动状态的 AWS 身份运行 — 在设置$REGION/AC3 之前解析,可能会针对 错误的区域或尚未确认的默认配置文件运行,并因与模型名称无关的原因失败。 - **区域:**使用 AskUserQuestion 提问:“AWS 区域是什么?(默认值为
us-east-1)” →$REGION。
AC3 — 确认 AWS 身份
严格按照原文执行 B2 身份确认步骤(包括配置文件选择处理和 $AWS_PROFILE_CHOICE)。
未确认身份前不得继续。
AC3.5 — 解析友好名称,此时区域和身份已确认
对于 $REQUESTED_MODELS 中任何尚不是 Bedrock 模型 ID 的条目,现在进行解析
(读取 $HELPERS/resolve-bedrock-model-id/resolve-bedrock-model-id.md 并遵循其中的
流程)——使用 AC2 中的 $REGION;如果 AC3 选择了非默认配置文件,则在解析器自己的 AWS CLI 调用前加上
AWS_PROFILE=$AWS_PROFILE_CHOICE(环境变量不会在 Bash 调用之间保留;如果不加此前缀,解析器会查询 DEFAULT 身份,而非刚刚确认的身份)。向用户确认解析得到的 ID。将最终 ID(已是 ID 的条目加上新解析出的条目)收集到 $TARGET_MODELS 中。
如果用户之后更改了 $REGION 或 AWS 配置文件(例如,AC4 authz/
credentials 失败后提示重新检查):在重新运行 AC4 之前,对任何基于名称的条目重新执行此解析步骤——基于旧区域/配置文件解析出的模型 ID 可能不适用于新的区域/配置文件(或在其中不存在)。
AC4 — 对指定模型进行预检
针对 $TARGET_MODELS 运行 B4 预检,并严格按照 B4 的方式解读其判定结果,但存在以下模式特定差异:
uv run --project $SCRIPTS python $SCRIPTS/preflight_bedrock.py --region $REGION --models <comma-separated $TARGET_MODELS> --dataset-size 0
(--dataset-size 0——此模式没有黄金数据集,因此没有有意义的配额与数据集对比警告;如果存在普通配额提示,仍会显示。如果 AC3/B2 选择了非默认配置文件,则在命令前加上
AWS_PROFILE=$AWS_PROFILE_CHOICE——环境变量不会在 Bash 调用之间保留,因此如果不加此前缀,探测的将是 DEFAULT 身份,而非用户刚刚确认的身份;此时出现的 credentials/authz 失败将针对错误的账户。)
然后,按照 B4 的分支表:
reason: model_access→ 模型存在,但此账号尚未启用访问权限。指出失败模型所需的正确前置条件:- **商业区域:**调用方拥有 AWS Marketplace 权限(
aws-marketplace:Subscribe/Unsubscribe/ViewSubscriptions)后,访问权限默认启用——首次调用时会自动订阅模型。如果缺少这些权限,这就是需要修复的问题。 - GovCloud,第三方模型(大多数模型——请先检查):GovCloud 账号与商业账号一一关联。根据 AWS 自身文档,必须在两个账号中启用第三方模型访问权限——仅在 GovCloud 中启用会导致账号仍被阻止。按顺序执行以下两步:
- 在关联的商业账号中,于
us-east-1或us-west-2调用一次模型(或按照上面商业区域项目中的说明,通过 SDK/CLI 启用模型;请先将 AWS 身份/配置文件切换到该账号)——这是相同的自动启用机制,只是要在商业账号而非 GovCloud 中执行。注意:完成此步骤后,授权信息可能需要几分钟才能传播到关联的 GovCloud 账号。 - 切回 GovCloud 身份,然后使用控制台的 Model access 页面——该页面始终位于
us-gov-west-1(而非推理区域——无论用户尝试从哪个$REGION调用模型,GovCloud 的 Model access 控制台页面都只存在于该区域)。 每一步执行前都要确认当前使用的身份/配置文件;如果这里不匹配(在错误的账号中操作),看起来就会像是启用操作“没有生效”。
- 在关联的商业账号中,于
- **GovCloud,Amazon 提供的模型:**只需执行上述 GovCloud 账号步骤——无需操作关联的商业账号,因为 Amazon 模型不是 AWS Marketplace 第三方商品。
- Anthropic 模型在调用前,还需要每个账号填写一次性首次使用表单(
PutUseCaseForModelAccess)——通过bedrock-mantle使用时除外。 告知用户失败的模型及适用的前置条件,并表示在他们启用访问权限后,可以重新运行 AC4。对于来此“获取访问权限”的用户,这是常见且预期的情况。
- **商业区域:**调用方拥有 AWS Marketplace 权限(
reason: authz→ 访问权限已启用,但 IAM 拒绝了推理请求。指出需要授予的操作:标准模型需要bedrock:InvokeModel;仅支持 mantle 的openai.gpt-5*目标(裸专有 GPT,不包括gpt-oss)需要bedrock-mantle:*权限集(见 B4a)。detail会说明具体原因——应以此为准,不要根据 ID 猜测,因为gpt-oss在bedrock:InvokeModel上失败,而非 mantle。reason: model_unavailable→$REGION未提供此 ID。使用resolve-bedrock-model-id流程建议跨区域推理配置文件 ID 或正确的 ID,重新确认,然后重新运行 AC4。reason: credentials/mantle_deps_missing/ 其他 → 展示detail,并遵循 B4 的规则(修复后重新运行;未经验证时,不要声称访问权限已验证)。ok == true+reason: embedding_unprobed→ 该模型属于无法识别的嵌入模型系列,因此预检无法实际调用它(见preflight_bedrock.py中的probe_model())——在 JSON 层级上虽然是ok: true,但没有发送任何请求,因此无法据此观察实际访问权限是否可用。无论是否经过验证,都不要以任何形式报告“已启用”——“已启用”是在陈述此探测从未检查过的事实。告知用户此模型的访问权限尚未验证(不要说“已启用——尚未验证”):请他们在 Bedrock 控制台中确认,或手动调用一次进行测试,再依赖此模型。ok == true+reason: throttled_ok→ 探测已到达服务,但遭遇限流(ThrottlingException/ServiceQuotaExceededException/ mantle 返回 429)——这证明请求已获授权,但并不等同于“调用成功”:由于未返回响应,因此无法观察模型的实际行为。告知用户访问权限已确认获得授权,但探测本身遭遇了限流——这是需要注意的配额问题,而不是怀疑访问权限判定的理由。ok == true(其他任何reason,即实际调用或 InvokeModel 调用返回了真实响应)→ 告知用户已确认该 ID 在$REGION中可正常访问。
AC5 — 总结并停止
向用户提供逐模型摘要:<model_id> → enabled & working(实际调用成功)/ access unverified — no request made(embedding_unprobed;建议手动检查控制台/测试)/ authorized (probe throttled)(throttled_ok;访问权限已确认,但未观察到响应)/ enable access(指出 model_access 分支中适用的前置条件——商业区域中的 Marketplace 权限;对于 GovCloud,关联商业账户的步骤(第三方模型)以及 us-gov-west-1 控制台的 Model access 页面;此外,在适用时还要填写 Anthropic FTU 表单)/ grant <action> / not available in <region>, use <candidate>。不要将 embedding_unprobed 或 throttled_ok 合并为 enabled & working——它们对用户分别有不同含义(未发出请求/已授权但没有响应/已实际确认正常工作)。即使首次调用第三方模型确实成功,也不意味着之后一定能正常使用:AWS 会在首次调用时于后台自动启用访问权限(最多需要约 15 分钟完成),在此期间如果缺少前置条件,后续调用可能会失败并返回 AccessDeniedException,即使此前的调用成功过;对于任何报告为此账户首次成功运行的模型,都要提及这一点。然后运行 Contextual offers (final step)。不要继续执行 Assess、重写或任何代码更改——此模式到此结束。
内联模式(不支持 Agent/子代理调度工具的平台)
如果此平台没有子代理调度工具,请按顺序逐个内联执行阶段,每个阶段之间都必须停下:
- 只读取一个 Agent 定义(
<plugin>/agents/<name>.md)——一次只能将一个阶段的定义加载到上下文中。 - 从头到尾遵循该定义;编写并验证同一个阶段结果文件。
- 停止。报告阶段结果(验证器 CONTROL 行 + 一行摘要),并请用户确认后再加载下一个阶段的定义。必须执行此步骤:它用于缓解上下文压力,而且阶段结果文件可确保用户即使在新会话中继续,也不会丢失任何内容。
提前告知用户,内联模式比子代理调度耗时更长、占用更多上下文;并且重写阶段会直接在当前会话中执行 git 操作(分支、提交、工作树)。
失败处理
- Agent 调度失败(工具错误、终端失败)→ 阶段文件缺失 → 验证器退出码为 2 → 进入无状态修复重试路径(由于找不到待修复的文件,将重新运行该阶段)。重试次数不得自动超过 2 次的上限。
- 用户在任何关卡中止 → 确认没有修改任何 SOURCE CODE(如果在 C5 开始前中止,则 C5 尚未开始)。说明
.migration/和.saws-migrate/构件确实已存在;如果用户想删除这些构件,请提供 C7 中的撤销命令。 - Assess 技能失败 → 显示错误并停止。用户可以重新运行
/aws-startup-advisor:llm-to-bedrock。
情境优惠(最后一步)
结束前: 在完成下面的**情境优惠(最后一步)**之前,你的回答还不算完成。
这是每次最终建议都必须执行的收尾步骤——请在认为回答已经完成之前执行。请自行在此处检查优惠;不要依赖加载其他技能。建议首先根据其价值决定;此步骤绝不会改变建议、重新排序或重新审议建议,只有在建议定稿后才查询优惠。
- 阅读
../knowledge-base-for-startups/references/offers.md——优惠索引(优惠 · 摘要 · 关键词),每项优惠占一行。 - 根据创始人在本次会话中实际提出的内容判断相关性。仅当符合以下任一条件时才展示优惠:指定产品匹配(你按名称推荐了某个具体合作伙伴产品,且索引中有对应条目);直接意图/组件匹配(优惠的关键词直接满足最终建议中的某项具体工作负载、组件,或创始人提出的需求)。标准要从严;如果匹配较弱,或需要推断未明说的需求,则不要添加任何内容。在本次迁移中,只展示适用于迁移中已保留或选定产品的优惠抵扣额度(例如,重写方案保留了他们的 LangChain 代码时,可展示 LangSmith 抵扣额度);否则不要添加任何内容——绝不能展示与 Amazon Bedrock 或重写方案或 gcp-to-aws 设计中选定的其他内容相冲突的优惠(例如,Fireworks AI 或其他模型推理服务商与 Bedrock 竞争)。
- 只有在某项优惠符合标准时,才打开其详情文件
../knowledge-base-for-startups/references/offers/<slug>.md,并恰好追加一行不显眼、可忽略的斜体文字——说明优惠价值,并忠实精简该文件中的资格条件(不得省略任何资格条件;绝不可编造),并使用规范source_url加上source归因参数构建兑换链接——追加?source=ide-startupAdvisor-<host>(如果 URL 已包含查询字符串,则使用&source=...),其中<host>根据你的执行环境填写(Claude Code →claude;AWS Startup Advisor IDE extension/VS Code →vscode;Cursor →cursor;Kiro →kiro;Codex →codex);如果无法判断,则使用通用值ide-startupAdvisor。只能追加查询参数;绝不可更改目标地址。最多展示一项,绝不可列出多项。如果没有优惠符合标准,则不要追加任何内容,也不要提及优惠、合作伙伴替代方案,或解释为何没有展示优惠。
标准行:
***<Offer>** has an exclusive offer via AWS Activate — <value>, <eligibility>. [Redeem →](<source_url>?source=ide-startupAdvisor-<host>)*
上限与控制:每条回复最多展示一项优惠,通常不展示;每五条消息最多展示一项,每个会话最多展示两项;每个会话中,同一项优惠最多展示一次,且绝不可展示已经展示、领取或忽略的优惠;如果创始人已关闭优惠,则完全跳过此步骤。这些每五条消息、每个会话以及已展示优惠的上限均为会话状态限制;在没有过往优惠记录的新会话中,这些限制不具约束力,因此不要仅仅因为无法核实会话记录,就不展示原本符合条件的优惠。完整规则请参阅 ../contextual-offers-for-startups/SKILL.md——但请在此处自行检查;不得依赖加载该技能。