大部分 AI 專案死在「demo 很成功、上線三個月後沒人敢動」。因為沒有人知道:模型現在準不準?這個月的 token 花了多少、花在誰身上?想更新模型會不會把線上搞掛?出錯了要去哪裡看? 「能跑」是原型,「敢上線、敢更新、出事查得到、花多少算得清」才是生產系統。這中間隔著的東西,叫 MLOps。 AWS 原生的好消息是:可觀測性、部署策略、成本治理這些「非功能」的東西,很多都是 managed service 免費附贈或一行設定就開——CloudWatch 收指標、X-Ray 追鏈路、Bedrock 呼叫日誌記每一次推論、SageMaker 藍綠部署零停機、Cost Allocation Tag 讓每個系統的花費一目了然。 這是 Part 5,也是整個系列的收束:把前四個「能跑」的系統,變成「敢上線」的系統。
一、情境與痛點:「能跑」到「敢上線」之間的鴻溝
回顧一下前四篇,我們蓋了四個 AI 系統:RAG 問答、IDP 管線、即時推薦、自主 Agent。每一個 cdk deploy 之後都能動。但「能動」的 demo 跟「敢放給幾十萬人用」的生產系統,中間隔著四個要命的問題:
問題 症狀 沒解決的下場
──────────────────────────────────────────────────────────────────────
① 看不見 模型變笨了、延遲飆高了,沒人發現 客訴爆了才知道
② 不敢改 想換個更好的模型,但怕改壞線上 系統凍結,技術債累積
③ 算不清 這個月 AI 花了多少?哪個系統燒最兇? 帳單來了才心臟病
④ 追不到 Agent 做錯決定 / RAG 答錯,查不出為什麼 無法歸因,無法改進
這四個問題,對應 MLOps 的四根支柱:可觀測性(看得見)、部署策略(敢改)、成本治理(算得清)、可追溯性(追得到)。這一篇就逐一拆解,並且用 CDK 把它們變成基礎設施的一部分——MLOps 不是上線後才補的東西,而是從第一天就 code 進 stack 裡。
二、可觀測性:看得見模型的健康
AI 系統的可觀測性比一般後端多一層。一般後端你看「延遲、錯誤率、吞吐」;AI 系統你還要看「模型行為本身」——它答對了嗎?它拒答了嗎?token 用了多少?信心分數如何?
2.1 三層可觀測性
┌──────────────────────────────────────────────────────────┐
│ 第三層:模型品質(Model Quality) │
│ 準確率、拒答率、幻覺率、信心分數分佈、使用者回饋(讚/倒讚) │
├──────────────────────────────────────────────────────────┤
│ 第二層:AI 用量(AI Usage) │
│ token in/out、每次呼叫成本、模型延遲、限流(429)次數 │
├──────────────────────────────────────────────────────────┤
│ 第一層:基礎設施(Infra) │
│ Lambda 延遲/錯誤、API Gateway 4xx/5xx、DynamoDB 節流 │
└──────────────────────────────────────────────────────────┘
第一層用 CloudWatch 內建指標就有。第二層要靠 Bedrock model invocation logging。第三層要靠你自己埋(把信心分數、使用者回饋寫成自訂指標)。
2.2 Bedrock 模型呼叫日誌:記下每一次推論
Bedrock 可以把每一次模型呼叫的完整輸入輸出記到 CloudWatch Logs 與 S3。這是 AI 系統可觀測性與合規審計的基石——出事時,你能精確重現「當時餵了什麼 prompt、模型回了什麼」。
1// lib/observability.ts 片段:開啟 Bedrock 呼叫日誌
2import * as bedrock from 'aws-cdk-lib/aws-bedrock';
3import * as logs from 'aws-cdk-lib/aws-logs';
4import * as s3 from 'aws-cdk-lib/aws-s3';
5import * as iam from 'aws-cdk-lib/aws-iam';
6
7const invocationLogGroup = new logs.LogGroup(this, 'BedrockInvocationLogs', {
8 retention: logs.RetentionDays.THREE_MONTHS,
9});
10const invocationLogBucket = new s3.Bucket(this, 'BedrockLogBucket');
11
12const loggingRole = new iam.Role(this, 'BedrockLoggingRole', {
13 assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'),
14});
15invocationLogGroup.grantWrite(loggingRole);
16invocationLogBucket.grantWrite(loggingRole);
17
18// 帳號層級的 Bedrock logging 設定(每個帳號一份)
19new bedrock.CfnApplicationInferenceProfile; // (示意:實務上用 CfnModelInvocationLoggingConfiguration 或 API)
Bedrock 的呼叫日誌是帳號層級設定(
PutModelInvocationLoggingConfiguration),開一次全帳號生效。務必啟用——它同時滿足「除錯」「成本歸因」「合規審計」三個需求。注意 prompt/completion 可能含 PII,S3 桶要加密並限制存取。
2.3 自訂指標與儀表板
把「模型品質」指標埋成 CloudWatch 自訂指標,例如 RAG 的「無引用回答率」、IDP 的「轉人工比例」、Agent 的「平均 ReAct 輪數」。
1// 在各系統的 Lambda 裡發自訂指標
2import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';
3const cw = new CloudWatchClient({});
4
5await cw.send(new PutMetricDataCommand({
6 Namespace: 'AISystems/RAG',
7 MetricData: [
8 { MetricName: 'AnsweredWithoutCitation', Value: citations.length === 0 ? 1 : 0, Unit: 'Count' },
9 { MetricName: 'TokensIn', Value: usage.inputTokens, Unit: 'Count' },
10 { MetricName: 'TokensOut', Value: usage.outputTokens, Unit: 'Count' },
11 ],
12}));
用 CDK 把儀表板與告警也 code 進去:
1import * as cw from 'aws-cdk-lib/aws-cloudwatch';
2import * as actions from 'aws-cdk-lib/aws-cloudwatch-actions';
3import * as sns from 'aws-cdk-lib/aws-sns';
4
5const alarmTopic = new sns.Topic(this, 'AIAlarms');
6
7// 例:RAG「無引用回答率」超過 5% 就告警(可能是檢索壞了或模型在幻覺)
8new cw.Metric({ namespace: 'AISystems/RAG', metricName: 'AnsweredWithoutCitation', statistic: 'Average' })
9 .createAlarm(this, 'NoCitationAlarm', {
10 threshold: 0.05, evaluationPeriods: 3,
11 comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
12 })
13 .addAlarmAction(new actions.SnsAction(alarmTopic));
14
15// 例:Bedrock 限流(429)升高告警
16new cw.Metric({ namespace: 'AWS/Bedrock', metricName: 'InvocationClientErrors', statistic: 'Sum' })
17 .createAlarm(this, 'ThrottleAlarm', {
18 threshold: 50, evaluationPeriods: 1,
19 comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
20 })
21 .addAlarmAction(new actions.SnsAction(alarmTopic));
2.4 端到端追蹤:X-Ray
一個 Agent 請求可能穿過 API Gateway → Lambda → Bedrock Agent → 多個工具 Lambda → DynamoDB。出錯時你要知道是哪一段慢/壞。開啟 X-Ray tracing,整條鏈路的火焰圖就有了。
1// Lambda 開 tracing
2new lambdaNode.NodejsFunction(this, 'Fn', {
3 tracing: lambda.Tracing.ACTIVE, // X-Ray
4 /* ... */
5});
6// API Gateway、Step Functions 也各有 tracingEnabled 選項
三、部署策略:敢改的底氣
AI 系統最常見的「不敢動」,是怕換模型/改 prompt 把線上搞壞。解法是漸進式部署:新版本先吃一小撮流量,指標沒問題再擴大,壞了自動回滾。
3.1 SageMaker 的三種部署形態(呼應 Part 3)
形態 適用 更新策略
──────────────────────────────────────────────────────────
即時 Endpoint 高頻低延遲(推薦排序) 藍綠 / 金絲雀,零停機
Serverless 稀疏流量、可容忍冷啟動 直接換,免管容量
Async Endpoint 大 payload、長推論(影片/大文件) 佇列式,免同步等待
3.2 SageMaker Endpoint 的藍綠/金絲雀更新
SageMaker 原生支援更新 endpoint 時做金絲雀(canary)或線性(linear) 流量轉移,搭配 CloudWatch alarm 自動回滾。
1// 更新 SageMaker Endpoint 時的部署設定(CfnEndpoint 的 deploymentConfig)
2const endpoint = new sagemaker.CfnEndpoint(this, 'RankingEndpoint', {
3 endpointConfigName: newConfig.attrEndpointConfigName,
4 deploymentConfig: {
5 blueGreenUpdatePolicy: {
6 trafficRoutingConfiguration: {
7 type: 'CANARY', // 先切一小塊
8 canarySize: { type: 'CAPACITY_PERCENT', value: 10 },
9 waitIntervalInSeconds: 600, // 觀察 10 分鐘
10 },
11 terminationWaitInSeconds: 600,
12 },
13 autoRollbackConfiguration: {
14 alarms: [{ alarmName: modelLatencyAlarm.alarmName }], // 延遲/錯誤超標自動回滾
15 },
16 },
17});
3.3 Bedrock 的版本控制:用 Alias 而非 hardcode 模型
對 Bedrock 應用,「敢改」的關鍵是別把模型 ID 寫死在程式碼裡。用抽象層隔開:
- Agent / Prompt:用 Agent Alias(如
prod、staging)指向特定版本。要升級就把 alias 指到新版本,要回滾就指回去——程式碼不動。 - 模型選擇:把 model ID 放進 SSM Parameter Store 或環境變數,換模型時改設定不改 code。
- Prompt:用 Bedrock Prompt Management 管理 prompt 版本,而不是散落在程式碼字串裡。
1// 用 SSM 參數解耦模型選擇,換模型只改參數不改 code
2import * as ssm from 'aws-cdk-lib/aws-ssm';
3const modelParam = new ssm.StringParameter(this, 'RagModelId', {
4 parameterName: '/ai/rag/model-id',
5 stringValue: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
6});
7queryFn.addEnvironment('MODEL_PARAM', modelParam.parameterName);
8modelParam.grantRead(queryFn);
3.4 影子部署(Shadow)評估新模型
要換模型又不敢直接切?影子模式:把線上真實流量複製一份餵給新模型,但不回傳給使用者,只記錄新模型的表現。比較新舊模型在同一批真實流量上的差異,有數據後再切。SageMaker 的 shadow variant 原生支援這件事。
四、成本治理:算得清
AI 系統的帳單很容易失控,因為成本藏在「每次呼叫的 token」裡,不像 EC2 那樣一眼看到。治理的核心是歸因:每一塊錢花在哪個系統、哪個功能、哪個團隊。
4.1 用 Cost Allocation Tag 分帳
給每個系統的資源打上一致的 tag,就能在 Cost Explorer 按系統/團隊/環境切分帳單。CDK 可以在 stack 層級一次打好。
1// bin/app.ts:給整個 app 打 tag
2import { Tags } from 'aws-cdk-lib';
3
4const app = new cdk.App();
5const ragStack = new RagStack(app, 'RagStack');
6Tags.of(ragStack).add('ai-system', 'rag-chatbot');
7Tags.of(ragStack).add('team', 'platform');
8Tags.of(ragStack).add('env', 'prod');
9Tags.of(ragStack).add('cost-center', 'CC-1042');
打完 tag 記得去 Billing console 把這些 tag 啟用為 Cost Allocation Tag(打 tag 本身不會自動出現在帳單分析裡,要手動啟用)。這是最多人漏的一步。
4.2 五個系統的成本形狀總覽
跨五篇,我們反覆看到「延遲需求決定成本形狀」這個母題。彙整如下:
系統 成本主導項 成本形狀 省錢的關鍵槓桿
──────────────────────────────────────────────────────────────────────────
RAG(P1) OpenSearch 向量庫 固定成本大 低流量改 Aurora pgvector
IDP(P2) Textract 每頁費 變動(線性) 前置過濾、只對需要的頁開貴功能
推薦(P3) SageMaker 常駐實例 常駐運算 兩階段召回、小模型、自動擴縮
Agent(P4) LLM token × 迴圈輪數 變動(放大) 清楚的工具定義、簡單任務降級
MLOps(P5) 日誌儲存 + 監控 變動(小) 日誌設 retention、抽樣高頻 trace
這張表是整個系列最重要的一張。 它說明:沒有「AWS AI 系統的通用成本模型」,成本形狀由系統的延遲與吞吐需求決定。要求 100ms 就得養常駐機器(推薦);能接受零流量零成本就用 serverless(RAG 查詢層);按件處理就是變動成本(IDP)。設計時先問「我的延遲需求是什麼」,成本形狀就出來了。
4.3 主動成本護欄
- AWS Budgets:設定每個 tag 的預算,超過門檻自動告警(甚至觸發 Lambda 限流)。
- Bedrock Provisioned Throughput vs On-Demand:穩定高流量用 provisioned(折扣),尖峰不定用 on-demand。錯配是常見的浪費。
- Prompt caching:Bedrock 支援 prompt caching,重複的長 system prompt 能省下可觀的 input token 費用。
五、CDK CI/CD:讓基礎設施自己部署自己
前四篇我們都手動 cdk deploy。生產環境要的是:推 code 到 git → 自動測試 → 自動部署到 staging → 人工核准 → 部署到 prod。用 CDK Pipelines(自我變異的 CodePipeline)實現,整條 pipeline 本身也是 CDK 描述的。
1// lib/pipeline-stack.ts 片段
2import * as pipelines from 'aws-cdk-lib/pipelines';
3
4const pipeline = new pipelines.CodePipeline(this, 'AISystemsPipeline', {
5 synth: new pipelines.ShellStep('Synth', {
6 input: pipelines.CodePipelineSource.gitHub('yennanliu/ai-systems', 'main'),
7 commands: ['npm ci', 'npm run test', 'npx cdk synth'],
8 }),
9});
10
11// staging:自動部署
12const staging = new AISystemsStage(this, 'Staging', { env: stagingEnv });
13pipeline.addStage(staging, {
14 post: [new pipelines.ShellStep('SmokeTest', {
15 commands: ['npm run e2e -- --endpoint $API_URL'], // 打真實 endpoint 做煙霧測試
16 })],
17});
18
19// prod:人工核准後才部署
20const prod = new AISystemsStage(this, 'Prod', { env: prodEnv });
21pipeline.addStage(prod, {
22 pre: [new pipelines.ManualApprovalStep('PromoteToProd')],
23});
這帶來一個閉環:第二節的 CloudWatch 告警、第三節的自動回滾、第四節的成本 tag、第五節的 pipeline——它們全都是同一份 CDK 程式碼的一部分。基礎設施、監控、部署流程、成本標籤,全部版本控制、全部 code review、全部可回溯。這就是「MLOps 從第一天就 code 進 stack」的完整樣貌。
六、技術選型考量:為什麼選 X 不選 Y
6.1 CloudWatch vs 第三方可觀測性(Datadog / Langfuse)
選擇 CloudWatch/X-Ray 第三方(Datadog/Langfuse)
──────────────────────────────────────────────────────────────────────
整合 AWS 原生,零額外設定 要接 SDK / agent
LLM 專屬視角 基礎(要自己埋品質指標) Langfuse 對 LLM trace/評估更專精
成本 按用量,無額外授權費 另付授權
跨雲 綁 AWS 跨雲/跨供應商統一視圖
翻盤條件:當你需要LLM 專屬的深度可觀測性(prompt 版本比較、逐步 trace、內建評估、標註),Langfuse 這類 LLM-native 工具體驗遠勝 CloudWatch;跨雲或已重度使用 Datadog 的團隊也會選第三方。但若你就在 AWS 上、要快、不想多付授權費,CloudWatch + X-Ray + Bedrock 日誌自己拼,基本盤是夠的。
6.2 CDK Pipelines vs 一般 CodePipeline vs GitHub Actions
- CDK Pipelines:pipeline 本身用 CDK 定義、能自我變異(改了 pipeline 定義,下次跑會自己更新自己),跟 CDK app 天生一體。
- 一般 CodePipeline:更多手動配置,適合非 CDK 的部署。
- GitHub Actions:跑
cdk deploy也完全可行,團隊已在 GitHub 生態時最省事;缺點是憑證要跨到 AWS(用 OIDC role 解決)。
CDK 專案用 CDK Pipelines 最順;但如果團隊的 CI 標準已是 GitHub Actions,用 Actions 跑 cdk deploy 沒有錯,別為了「純」而硬換。
6.3 自動回滾 vs 人工回滾
自動回滾(綁 CloudWatch alarm)快、無人值守,適合指標明確的劣化(延遲、錯誤率)。但模型品質的劣化常常沒有即時指標(答案變爛不會讓延遲變高)。所以策略是:基礎指標自動回滾 + 模型品質靠影子評估/離線評測擋在上線前。別指望自動回滾能接住「模型變笨」這種安靜的劣化。
七、系列總結:五個系統,一套心法
走完五篇,我們蓋了五個 AWS 原生 AI 系統。最後用一張總表把它們放在一起看:
情境 核心 AWS 服務 架構母題 成本形狀
────────────────────────────────────────────────────────────────────────────────────────
P1 RAG 知識問答 Bedrock KB + OpenSearch 檢索+生成 固定(向量庫)
客服/內部知識 Serverless + Lambda managed RAG
────────────────────────────────────────────────────────────────────────────────────────
P2 IDP 髒文件結構化 Textract + Comprehend + 事件驅動+狀態機 變動(每頁)
發票/合約/理賠 Bedrock + Step Functions +冪等+人工回路
────────────────────────────────────────────────────────────────────────────────────────
P3 推薦 即時個人化 Kinesis + Feature Store + 串流+特徵一致性 常駐(低延遲)
電商/影音/資訊流 SageMaker Endpoint +兩階段召回排序
────────────────────────────────────────────────────────────────────────────────────────
P4 Agent 自主行動 Bedrock Agents + Lambda ReAct+工具+護欄 變動(token×輪數)
客服/維運/自動化 Action Groups + Guardrails +最小權限+人工關卡
────────────────────────────────────────────────────────────────────────────────────────
P5 MLOps 生產化 CloudWatch + X-Ray + 可觀測+部署+ 變動(監控)
監控/部署/治理 CDK Pipelines + Cost Tag 成本歸因+CI/CD
如果只帶走三句話,我希望是這三句:
能外包給 managed service 的,就別自己 host。 對 90% 的團隊,自己養 GPU、自建向量庫、手刻 ReAct 迴圈不是競爭力,是負債。AWS 原生服務讓你把工程力氣花在「把服務接成解決業務問題的系統」,而不是「維護基礎設施」。
系統的延遲與吞吐需求,決定了它的架構母題與成本形狀。 RAG 要不要即時決定了固定成本、推薦的 100ms 逼出常駐運算、Agent 的多步驟放大了 token 成本。先問需求,再選架構——不是反過來。
MLOps 不是上線後才補的,是從第一天就 code 進 stack 的。 可觀測性、部署策略、成本標籤、CI/CD,全部用 CDK 描述、全部版本控制。「能跑」跟「敢上線」的距離,就是這一整套非功能設計。
而 CDK(CloudFormation)是貫穿這一切的線:五個系統、每一個資源、每一條權限、每一個告警、每一個成本標籤,都是同一種語言寫出來的、可版本控制、可 review、可一鍵重建的程式碼。這,就是「AI System on Native AWS」的全貌。
感謝一路讀到這裡。願你的下一個 AI 系統,從 demo 到上線,都走得穩。
系列導覽
- Part 1:Serverless RAG 智慧客服知識庫
- Part 2:智慧文件處理(IDP)管線
- Part 3:即時個人化推薦系統
- Part 4:自主 AI Agent 工具呼叫系統
- Part 5(本篇):生產化 MLOps 與可觀測性
