AI System on Native AWS - Part 1 - Serverless RAG 智慧客服知識庫

大部分人做企業內部問答機器人:租一台 GPU、裝 LangChain、自己接一個 Pinecone、再寫一堆膠水程式碼,三個月後發現光是「文件更新後要重新 embedding」這件事就沒人想維護。 AWS 原生的作法不一樣:文件丟進 S3,Bedrock Knowledge Bases 自動幫你切塊、嵌入、同步進向量庫;問答時呼叫一個 RetrieveAndGenerate API,連檢索帶生成、還附引用來源,一次搞定。 沒有一台伺服器需要你開機、沒有一個模型需要你自己 host,計費按呼叫次數走,資料永遠不離開你的 AWS 帳號。 這一系列會帶你走過五種「最常見、最能直接落地」的 AWS 原生 AI 系統;這篇 Part 1,我們從所有 AI 應用的起點——RAG 問答——開始。


關於這個系列

「在雲上做 AI」有兩條路。一條是把 AWS 當成一台大型的 Linux 機房:自己開 EC2、自己裝 vLLM、自己管 Kubernetes、自己接開源向量庫。另一條是用 AWS 原生的 managed AI 服務(Bedrock、SageMaker、Textract、Comprehend、Personalize…),把「模型 host、擴縮、容錯」全部外包給雲廠商,你只寫「把這些服務接起來」的黏合邏輯。

這個系列走的是第二條路。原因很簡單:對 90% 的團隊來說,自己 host 模型不是核心競爭力,而是負債。我們會用五篇,各自完整拆解一個最常見的 AWS 原生 AI 系統,每一篇都包含:情境與痛點 → 系統目的 → 系統設計與架構 → CDK(CloudFormation)實作 → 技術選型考量 → 成本 → 延伸與坑

  • Part 1(本篇):Serverless RAG 智慧客服知識庫 —— Bedrock Knowledge Bases + OpenSearch Serverless
  • Part 2:智慧文件處理(IDP)管線 —— Textract + Comprehend + Bedrock + Step Functions
  • Part 3:即時個人化推薦系統 —— Kinesis + Feature Store + SageMaker Endpoint
  • Part 4:自主 AI Agent 工具呼叫系統 —— Bedrock Agents + Lambda Action Groups + Guardrails
  • Part 5:生產化 MLOps 與可觀測性 —— SageMaker 部署策略、模型呼叫日誌、成本治理、CDK CI/CD

全系列的基礎設施都用 AWS CDK(TypeScript) 描述,底層產出的就是 CloudFormation。為什麼是 CDK 不是手刻 CloudFormation YAML?這個問題我們在本篇最後會回答。


一、情境與痛點:為什麼 RAG 是所有 AI 系統的起點

先講一個具體場景。

你是一家 SaaS 公司,累積了三年的產品文件、API 手冊、客服工單、內部 Wiki——加起來幾千份 PDF 跟 Markdown。現在有兩群人每天都在問同樣的問題:

  • 客戶:「你們的 webhook 重試機制是幾次?」「企業版支援 SSO 嗎?」——這些答案文件裡都有,但客戶找不到,只好開工單。
  • 新進客服/工程師:「這個錯誤碼是什麼意思?」「退款流程的 SOP 在哪?」——答案散在十個不同系統裡。

你想做一個問答機器人,讓大家用自然語言問,系統從你自己的文件裡找答案,並且附上出處(這樣才敢信)。這就是 RAG(Retrieval-Augmented Generation,檢索增強生成)。

為什麼不直接把文件塞給 LLM、或直接用 ChatGPT?三個致命問題:

  • 上下文塞不下:幾千份文件動輒上千萬 token,任何模型的 context window 都放不下。
  • 會幻覺:純 LLM 沒看過你的內部文件,只會一本正經地編一個看起來很合理的答案。
  • 資料外流:把內部合約、客戶資料貼到外部 ChatGPT,法遵直接否決。

RAG 的解法是:先檢索、再生成。把「找出最相關的幾段文字」跟「用這幾段文字生成答案」拆成兩步。

使用者問題
    │
    ▼
┌──────────────┐    把問題轉成向量,去向量庫找最像的 K 段文字
│   檢索 Retrieve │ ─────────────────────────────────────────▶ 相關文件片段 ×K
└──────────────┘
    │
    ▼
┌──────────────┐    把「問題 + 檢索到的片段」一起丟給 LLM
│   生成 Generate │ ─────────────────────────────────────────▶ 有根據、附引用的答案
└──────────────┘

RAG 之所以是「所有 AI 系統的起點」,是因為它是最小、最實用、投報率最高的一個 LLM 應用:不需要訓練模型、不需要標資料、當天就能看到價值。也因此,它幾乎是每個團隊踏進 AWS AI 的第一步。


二、系統目的:功能與非功能需求

在動手前,先把「這個系統到底要做到什麼」講清楚。這一步決定了後面所有的架構取捨。

功能需求(Functional):

  • 支援上傳 PDF / Word / Markdown / HTML,系統自動切塊、嵌入、建索引。
  • 文件更新後能增量同步,不用整批重建。
  • 問答時回傳答案 + 引用來源(哪份文件、哪一段)。
  • 支援多輪對話(記得上一個問題的脈絡)。
  • 依使用者身分做文件層級的權限過濾(客戶只能看到公開文件)。

非功能需求(Non-functional):

面向目標為什麼
延遲P95 < 3.5s(檢索 + 生成)客服對話體感能接受的上限
準確性答案必須可溯源,禁止無引用回答沒有出處的答案不敢給客戶
資料主權文件與向量永不離開自己的 AWS 帳號法遵、資安紅線
維運無伺服器需長期開機,免管 GPU團隊沒有 MLOps 人力
成本隨用量計費,零流量近乎零成本初期流量不確定
擴縮能從 10 QPS 平滑到 500 QPS上線後流量會爆發

這張表就是我們選 Serverless + Bedrock 全託管路線的理由:團隊沒有 MLOps 人力、要資料主權、要隨用量計費——這三條同時成立時,自己 host 模型幾乎必輸。


三、系統設計與架構

整個系統拆成兩條資料流:離線的知識注入(Ingestion)線上的問答(Query)

3.1 整體架構

                         【 Ingestion:離線知識注入 】
┌──────────┐   上傳    ┌──────────────┐   觸發同步   ┌───────────────────────┐
│  文件來源  │ ───────▶ │   S3 Bucket   │ ──────────▶ │  Bedrock Knowledge Base │
│ PDF/MD/… │          │  (docs/)      │             │  ┌──────────────────┐  │
└──────────┘          └──────────────┘             │  │ 1. Chunking 切塊  │  │
                                                    │  │ 2. Titan Embed   │  │
                                                    │  │    嵌入向量        │  │
                                                    │  └────────┬─────────┘  │
                                                    └───────────┼────────────┘
                                                                ▼
                                                    ┌───────────────────────┐
                                                    │ OpenSearch Serverless   │
                                                    │  (向量索引 + metadata)   │
                                                    └───────────────────────┘

                         【 Query:線上問答 】
┌──────────┐  HTTPS   ┌──────────────┐   invoke   ┌──────────────┐
│  前端/App │ ───────▶ │ API Gateway   │ ─────────▶ │    Lambda     │
│(Cognito) │          │  (REST/JWT)   │           │ (Orchestrator)│
└──────────┘          └──────────────┘           └───────┬──────┘
      ▲                                                   │ RetrieveAndGenerate
      │  答案 + 引用                                        ▼
      │                                          ┌────────────────────────┐
      └──────────────────────────────────────── │  Bedrock KB + Claude     │
                                                 │  檢索 K 段 → 生成帶引用答案 │
                                                 └────────────────────────┘

核心洞察:Bedrock Knowledge Bases 把 RAG 裡最麻煩的那半(切塊策略、embedding 呼叫、向量庫同步、retrieval 排序)全部包成一個 managed service。你不用自己寫 chunking、不用自己呼叫 embedding model、不用自己維護「文件改了要 reindex」的排程。你只要:(1) 把文件丟 S3;(2) 呼叫一次 StartIngestionJob 或開啟自動同步;(3) 問答時呼叫 RetrieveAndGenerate

3.2 線上問答的資料流(一次請求發生什麼)

  1. 前端帶著 Cognito 發的 JWT 打 API Gateway。
  2. API Gateway 用 Cognito Authorizer 驗證 token,通過才轉給 Lambda。
  3. Lambda 呼叫 Bedrock 的 RetrieveAndGenerate,帶上使用者問題、Knowledge Base ID、要用的生成模型(Claude)。
  4. Bedrock 內部:把問題向量化 → 去 OpenSearch Serverless 找最相關的 K 段 → 把片段 + 問題組成 prompt → 呼叫 Claude 生成 → 回傳答案與 citations(每段答案對應到哪個文件片段)。
  5. Lambda 把答案與引用整理成前端要的 JSON 回傳。

關鍵是:檢索與生成這兩步,在 RetrieveAndGenerate 裡是一次 API 呼叫完成的。你也可以拆成 Retrieve(只檢索)+ 自己組 prompt 呼叫 InvokeModel(自己生成),換取更多控制權——這個取捨我們在第五節談。


四、CDK(CloudFormation)實作

以下用 AWS CDK v2(TypeScript)描述整套基礎設施。CDK 合成出來的就是 CloudFormation 模板,cdk deploy 底層走的是 CloudFormation 的 change set。

4.1 專案結構

rag-chatbot/
├── bin/app.ts                      # CDK app 進入點
├── lib/
│   ├── rag-stack.ts                # 主 stack
│   ├── constructs/
│   │   ├── vector-store.ts         # OpenSearch Serverless collection
│   │   ├── knowledge-base.ts       # Bedrock Knowledge Base + Data Source
│   │   └── query-api.ts            # API Gateway + Lambda + Cognito
│   └── lambda/
│       └── query/index.ts          # 問答 orchestrator
├── cdk.json
└── package.json

4.2 向量庫:OpenSearch Serverless Collection

Bedrock Knowledge Base 需要一個向量庫。這裡用 OpenSearch Serverless(向量檢索用途),它需要三種 policy:加密(encryption)、網路(network)、資料存取(data access)。

 1// lib/constructs/vector-store.ts
 2import { Construct } from 'constructs';
 3import * as oss from 'aws-cdk-lib/aws-opensearchserverless';
 4
 5export interface VectorStoreProps {
 6  collectionName: string;
 7  /** 稍後會建立的 KB 執行角色 ARN,要放進 data access policy */
 8  kbRoleArn: string;
 9}
10
11export class VectorStore extends Construct {
12  public readonly collection: oss.CfnCollection;
13
14  constructor(scope: Construct, id: string, props: VectorStoreProps) {
15    super(scope, id);
16
17    // 1) 加密 policy:用 AWS 託管金鑰加密整個 collection
18    const encryptionPolicy = new oss.CfnSecurityPolicy(this, 'EncryptionPolicy', {
19      name: `${props.collectionName}-enc`,
20      type: 'encryption',
21      policy: JSON.stringify({
22        Rules: [{ ResourceType: 'collection', Resource: [`collection/${props.collectionName}`] }],
23        AWSOwnedKey: true,
24      }),
25    });
26
27    // 2) 網路 policy:此範例用 public endpoint(正式環境建議改 VPC endpoint)
28    const networkPolicy = new oss.CfnSecurityPolicy(this, 'NetworkPolicy', {
29      name: `${props.collectionName}-net`,
30      type: 'network',
31      policy: JSON.stringify([{
32        Rules: [
33          { ResourceType: 'collection', Resource: [`collection/${props.collectionName}`] },
34          { ResourceType: 'dashboard', Resource: [`collection/${props.collectionName}`] },
35        ],
36        AllowFromPublic: true,
37      }]),
38    });
39
40    // 3) collection 本體:type = VECTORSEARCH
41    this.collection = new oss.CfnCollection(this, 'Collection', {
42      name: props.collectionName,
43      type: 'VECTORSEARCH',
44      description: 'Vector store for RAG knowledge base',
45    });
46    this.collection.addDependency(encryptionPolicy);
47    this.collection.addDependency(networkPolicy);
48
49    // 4) 資料存取 policy:允許 KB 角色讀寫索引
50    const dataAccessPolicy = new oss.CfnAccessPolicy(this, 'DataAccessPolicy', {
51      name: `${props.collectionName}-data`,
52      type: 'data',
53      policy: JSON.stringify([{
54        Rules: [
55          {
56            ResourceType: 'index',
57            Resource: [`index/${props.collectionName}/*`],
58            Permission: ['aoss:CreateIndex', 'aoss:ReadDocument', 'aoss:WriteDocument',
59                         'aoss:UpdateIndex', 'aoss:DescribeIndex'],
60          },
61          {
62            ResourceType: 'collection',
63            Resource: [`collection/${props.collectionName}`],
64            Permission: ['aoss:CreateCollectionItems', 'aoss:DescribeCollectionItems'],
65          },
66        ],
67        Principal: [props.kbRoleArn],
68      }]),
69    });
70    this.collection.addDependency(dataAccessPolicy);
71  }
72}

注意 OpenSearch Serverless 的 data access policy 是它自己一套權限系統,跟 IAM 分開。很多人第一次踩的坑就是 IAM 給了權限、KB 卻還是連不進 collection——因為忘了在 aoss data access policy 裡把 KB 角色列為 Principal。

4.3 Bedrock Knowledge Base 與 Data Source

Knowledge Base 用 L1 construct(CfnKnowledgeBase)。它綁定:一個 embedding 模型(Titan Embeddings)、一個向量庫(上面的 collection)、一個 data source(S3)。

 1// lib/constructs/knowledge-base.ts
 2import { Construct } from 'constructs';
 3import * as bedrock from 'aws-cdk-lib/aws-bedrock';
 4import * as iam from 'aws-cdk-lib/aws-iam';
 5import * as s3 from 'aws-cdk-lib/aws-s3';
 6import { Stack } from 'aws-cdk-lib';
 7
 8export interface KnowledgeBaseProps {
 9  docsBucket: s3.Bucket;
10  collectionArn: string;
11  kbRole: iam.Role;
12  embeddingModelArn: string;   // Titan Embeddings v2
13}
14
15export class KnowledgeBase extends Construct {
16  public readonly knowledgeBaseId: string;
17
18  constructor(scope: Construct, id: string, props: KnowledgeBaseProps) {
19    super(scope, id);
20    const region = Stack.of(this).region;
21
22    const kb = new bedrock.CfnKnowledgeBase(this, 'KB', {
23      name: 'support-knowledge-base',
24      roleArn: props.kbRole.roleArn,
25      knowledgeBaseConfiguration: {
26        type: 'VECTOR',
27        vectorKnowledgeBaseConfiguration: {
28          embeddingModelArn: props.embeddingModelArn,
29        },
30      },
31      storageConfiguration: {
32        type: 'OPENSEARCH_SERVERLESS',
33        opensearchServerlessConfiguration: {
34          collectionArn: props.collectionArn,
35          vectorIndexName: 'support-index',
36          fieldMapping: {
37            vectorField: 'vector',
38            textField: 'text',
39            metadataField: 'metadata',
40          },
41        },
42      },
43    });
44
45    // Data Source:S3 + 固定大小切塊(300 token,overlap 20%)
46    new bedrock.CfnDataSource(this, 'S3DataSource', {
47      knowledgeBaseId: kb.attrKnowledgeBaseId,
48      name: 's3-docs',
49      dataSourceConfiguration: {
50        type: 'S3',
51        s3Configuration: { bucketArn: props.docsBucket.bucketArn },
52      },
53      vectorIngestionConfiguration: {
54        chunkingConfiguration: {
55          chunkingStrategy: 'FIXED_SIZE',
56          fixedSizeChunkingConfiguration: { maxTokens: 300, overlapPercentage: 20 },
57        },
58      },
59    });
60
61    this.knowledgeBaseId = kb.attrKnowledgeBaseId;
62  }
63}

Knowledge Base 的執行角色需要能:讀 S3、呼叫 embedding 模型、寫 OpenSearch Serverless。

 1// rag-stack.ts 片段:KB 執行角色
 2const kbRole = new iam.Role(this, 'KbRole', {
 3  assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'),
 4});
 5docsBucket.grantRead(kbRole);
 6kbRole.addToPolicy(new iam.PolicyStatement({
 7  actions: ['bedrock:InvokeModel'],
 8  resources: [`arn:aws:bedrock:${this.region}::foundation-model/amazon.titan-embed-text-v2:0`],
 9}));
10kbRole.addToPolicy(new iam.PolicyStatement({
11  actions: ['aoss:APIAccessAll'],
12  resources: [vectorStore.collection.attrArn],
13}));

4.4 問答 API:Lambda + API Gateway + Cognito

問答 Lambda 只做一件事:呼叫 RetrieveAndGenerate,整理回傳。

 1// lib/lambda/query/index.ts
 2import { BedrockAgentRuntimeClient, RetrieveAndGenerateCommand }
 3  from '@aws-sdk/client-bedrock-agent-runtime';
 4
 5const client = new BedrockAgentRuntimeClient({});
 6const KB_ID = process.env.KNOWLEDGE_BASE_ID!;
 7const MODEL_ARN = process.env.MODEL_ARN!;   // Claude 的推論 profile ARN
 8
 9export const handler = async (event: any) => {
10  const { question, sessionId } = JSON.parse(event.body ?? '{}');
11
12  const res = await client.send(new RetrieveAndGenerateCommand({
13    input: { text: question },
14    ...(sessionId ? { sessionId } : {}),
15    retrieveAndGenerateConfiguration: {
16      type: 'KNOWLEDGE_BASE',
17      knowledgeBaseConfiguration: {
18        knowledgeBaseId: KB_ID,
19        modelArn: MODEL_ARN,
20        retrievalConfiguration: {
21          vectorSearchConfiguration: { numberOfResults: 5 },  // 取 Top-5 片段
22        },
23      },
24    },
25  }));
26
27  // 整理引用來源:每一段 citation 對應到哪個 S3 檔案
28  const citations = (res.citations ?? []).flatMap(c =>
29    (c.retrievedReferences ?? []).map(r => ({
30      source: r.location?.s3Location?.uri,
31      snippet: r.content?.text?.slice(0, 200),
32    })));
33
34  return {
35    statusCode: 200,
36    headers: { 'content-type': 'application/json' },
37    body: JSON.stringify({
38      answer: res.output?.text,
39      sessionId: res.sessionId,       // 回傳 sessionId 以支援多輪對話
40      citations,
41    }),
42  };
43};

把 Lambda 掛到 API Gateway,並用 Cognito User Pool 保護:

 1// lib/constructs/query-api.ts 片段
 2import * as apigw from 'aws-cdk-lib/aws-apigateway';
 3import * as cognito from 'aws-cdk-lib/aws-cognito';
 4import * as lambdaNode from 'aws-cdk-lib/aws-lambda-nodejs';
 5import { Duration } from 'aws-cdk-lib';
 6
 7const userPool = new cognito.UserPool(this, 'UserPool', { selfSignUpEnabled: false });
 8
 9const queryFn = new lambdaNode.NodejsFunction(this, 'QueryFn', {
10  entry: 'lib/lambda/query/index.ts',
11  timeout: Duration.seconds(30),
12  memorySize: 512,
13  environment: { KNOWLEDGE_BASE_ID: kbId, MODEL_ARN: modelArn },
14});
15// 只給 Lambda 呼叫這個 KB 的權限
16queryFn.addToRolePolicy(new iam.PolicyStatement({
17  actions: ['bedrock:RetrieveAndGenerate', 'bedrock:Retrieve', 'bedrock:InvokeModel'],
18  resources: ['*'],   // 正式環境請收斂到特定 KB 與 model ARN
19}));
20
21const api = new apigw.RestApi(this, 'RagApi', {
22  defaultCorsPreflightOptions: { allowOrigins: apigw.Cors.ALL_ORIGINS },
23});
24const authorizer = new apigw.CognitoUserPoolsAuthorizer(this, 'Auth', {
25  cognitoUserPools: [userPool],
26});
27api.root.addResource('ask').addMethod('POST',
28  new apigw.LambdaIntegration(queryFn),
29  { authorizer, authorizationType: apigw.AuthorizationType.COGNITO });

4.5 部署

1npm install
2npx cdk bootstrap           # 首次:建立 CDK 需要的 S3/IAM 資源
3npx cdk synth               # 合成 CloudFormation 模板,先看一眼
4npx cdk deploy              # 底層走 CloudFormation change set 部署
5# 部署後:上傳文件到 docs bucket,再啟動一次 ingestion job
6aws bedrock-agent start-ingestion-job \
7  --knowledge-base-id <KB_ID> --data-source-id <DS_ID>

cdk synth 到系統上線,你沒有碰過任何一台伺服器、沒有 host 任何一個模型。這就是「原生 AWS」的意義。


五、技術選型考量:為什麼選 X 不選 Y

RAG 系統每一個元件都有替代方案。以下是幾個關鍵決策,以及「什麼時候該翻盤選 Y」。

5.1 Bedrock Knowledge Bases vs 自建 LangChain 管線

選擇                  選 Bedrock KB 的理由              自建 LangChain 的理由
──────────────────────────────────────────────────────────────────────────
切塊/嵌入/同步        全託管,不用自己寫、自己排程          你需要非常客製的切塊策略
向量庫維護            KB 幫你管索引 lifecycle              你要多租戶 / 特殊索引結構
上手速度             幾天內上線                          你已有成熟的 RAG pipeline
控制權               中等(切塊策略有限選項)              完全掌握每一步

翻盤條件:當你需要 (1) 語意切塊 / 版面感知切塊等 KB 不支援的策略、(2) 檢索後自訂 rerank / 多階段檢索、(3) 跨多個異質資料源做複雜 join——這時自建管線(可搭 Lambda + 開源套件)才划算。但對 8 成場景,KB 的預設就夠好。

5.2 向量庫:OpenSearch Serverless vs Aurora pgvector vs Pinecone

選擇                    優勢                          代價 / 翻盤條件
──────────────────────────────────────────────────────────────────────
OpenSearch Serverless   Bedrock KB 原生支援,免管節點     最低 OCU 有固定成本(見成本節)
Aurora pgvector         已有 Postgres,省一套系統          需自己管實例、擴縮
Pinecone(第三方)      向量檢索體驗最佳                  資料出 AWS 帳號,法遵可能不允許

翻盤條件:資料量小、已經在用 Aurora、且流量低到不想付 OpenSearch 的最低月費 → 選 Aurora pgvector(Bedrock KB 也支援)。資料主權沒問題、追求極致檢索體驗 → 才考慮 Pinecone。

5.3 生成模型:Claude vs Titan vs Llama(都在 Bedrock 上)

  • Claude(Anthropic):遵循指令、拒絕幻覺、引用邏輯最穩,是 RAG 生成的預設首選。
  • Titan Text:AWS 自家,便宜,適合簡單摘要,複雜推理稍弱。
  • Llama(Meta):開源權重、可自行微調,若你要 fine-tune 或有特殊授權需求時考慮。

RAG 的瓶頸通常不在生成而在檢索品質,所以生成模型先用 Claude 求穩,之後再依成本壓力往下調。

5.4 RetrieveAndGenerate vs Retrieve + 自己 InvokeModel

  • 一次搞定的 RetrieveAndGenerate:少寫程式碼、少一次網路往返、引用格式 Bedrock 幫你處理好。
  • 拆開的 Retrieve + InvokeModel:你能在中間插入 rerank、過濾、改寫 prompt、加 few-shot、做 guardrail 檢查——控制權大很多。

先用合體版上線,等你要優化檢索品質或 prompt 時,再拆開。

5.5 為什麼用 CDK 而不是手刻 CloudFormation YAML

選擇            CDK(TypeScript)                手刻 CloudFormation YAML
─────────────────────────────────────────────────────────────────────
抽象化          用迴圈/函式/型別產生資源            純宣告,重複的東西要複製貼上
IAM 權限        grantRead() 一行搞定最小權限        自己拼 policy JSON,易錯
重用            Construct 可跨專案打包重用           巢狀 stack,體驗較差
型別檢查        編譯期就抓到打錯的屬性               部署到一半才報錯

CDK 產出的仍然是 CloudFormation(可用 cdk synth 檢視),等於你拿到「有型別、能寫邏輯」的 CloudFormation。對於 IAM 權限特別多的 AI 系統,grant*() 這類方法自動產生最小權限 policy,省下大量手拼 JSON 的錯誤。


六、成本估算

Serverless 的美好是「零流量近乎零成本」,但 RAG 有一個容易被忽略的固定成本:向量庫。

以「每月 10 萬次問答、文件庫 5 GB」估算(us-east-1 概略單價,實際以帳單為準):

項目用量概略月費
OpenSearch Serverless最低 2 OCU(檢索)+ 索引,約 $0.24/OCU·hr~$350(固定成本大戶)
Bedrock 嵌入(Titan v2)首次 5 GB ≈ 數百萬 token,增量少~$5 一次性 + 少量
Bedrock 生成(Claude)10 萬次 × 平均 2K in / 400 out token~$60–200(視模型等級)
Lambda10 萬次呼叫、每次 <1s< $1
API Gateway10 萬次請求~$0.35
S3 儲存5 GB~$0.12
合計~$420–560 / 月

成本洞察:這套系統的成本結構是「一大塊固定 + 一小塊變動」。固定成本幾乎全來自 OpenSearch Serverless 的最低 OCU。這帶來一個重要的架構結論——

  • 流量很低(< 1 萬次/月)時:OpenSearch 的固定月費會讓「每次問答的均攤成本」高得離譜。此時改用 Aurora Serverless v2 + pgvector(可縮到很低)或甚至把向量存進 DynamoDB 自己算相似度,反而划算。
  • 流量中高時:固定成本被攤平,OpenSearch Serverless 的免維運優勢就贏了。

這正是第五節「向量庫翻盤條件」的成本版註解。


七、延伸與常見的坑

上線後你一定會遇到的優化題:

  • 檢索品質不夠好:答案答非所問,通常是切塊策略問題。改用語意切塊、加大 overlap、或引入 hybrid search(向量 + 關鍵字混合)。這時就是把 RetrieveAndGenerate 拆成 Retrieve + 自訂 rerank 的時機。
  • 幻覺仍然出現:在 prompt 裡強制「只能根據提供的片段回答,找不到就說不知道」,並掛上 Bedrock Guardrails(Part 4 會深入)過濾幻覺與敏感內容。
  • 權限過濾:客戶不該看到內部文件。在 ingestion 時給每個 chunk 打 metadata(如 visibility: public),檢索時用 metadata filter 限制範圍。
  • 評估沒有基準:你怎麼知道改了切塊策略是變好還變壞?建立一組「問題 → 標準答案」的評測集,用 LLM-as-judge 打分,每次改動都跑一遍。這是把 RAG 從「感覺不錯」推進到「可量化改善」的關鍵。
  • 多輪對話的 session 成本:sessionId 會讓 Bedrock 帶入歷史,token 用量隨對話增長,記得設對話長度上限。

最容易踩的三個坑:

  1. 忘了 aoss data access policy:IAM 對了、KB 還是連不進向量庫——因為 OpenSearch Serverless 有獨立的一套資料存取權限。
  2. 模型要先在 Bedrock console 開啟存取:Bedrock 的 foundation model 預設是「未啟用」,要先在 Model access 頁面申請開通,CDK 才 invoke 得到。
  3. ingestion job 不會自動跑:上傳文件到 S3 後,除非設定自動同步,否則要手動 start-ingestion-job,不然向量庫是空的。

小結

RAG 是所有 AI 系統的起點,也是最能展現「AWS 原生」威力的地方:你把整個 RAG 裡最麻煩的檢索基礎設施,外包給 Bedrock Knowledge Bases + OpenSearch Serverless,自己只寫幾十行 Lambda 與 CDK。沒有 GPU、沒有模型 host、資料留在自己帳號、隨用量計費。

下一篇,我們處理另一個超高頻的 AI 系統:當「知識」不是乾淨的 Markdown,而是幾百萬張掃描的 PDF、發票、合約時,怎麼用 Textract + Comprehend + Bedrock + Step Functions 蓋一條智慧文件處理(IDP)管線。


系列導覽

  • Part 1(本篇):Serverless RAG 智慧客服知識庫
  • Part 2:智慧文件處理(IDP)管線 —— Textract + Comprehend + Bedrock + Step Functions
  • Part 3:即時個人化推薦系統 —— Kinesis + Feature Store + SageMaker Endpoint
  • Part 4:自主 AI Agent 工具呼叫系統 —— Bedrock Agents + Lambda Action Groups + Guardrails
  • Part 5:生產化 MLOps 與可觀測性 —— 部署策略、模型日誌、成本治理、CDK CI/CD
Yen

Yen

Yen