# AWS Bedrock Setup Guide

This guide explains how to set up AWS Bedrock for use with the image analysis Lambda function.

## Prerequisites

1. AWS Account with administrative access
2. AWS CLI installed and configured
3. Access to AWS Console

## Step-by-Step Setup

### 1. Model Access Note

Model access page is retired. Access to serverless foundation models is automatically enabled for your AWS account. Control access strictly via IAM policies and (optionally) AWS Organizations Service Control Policies (SCPs).

### 2. Configure IAM Permissions

1. Go to IAM Console
2. Create a new IAM Policy (or add to existing) for the Lambda role:
   ```json
   {
       "Version": "2012-10-17",
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "bedrock:InvokeModel",
                   "bedrock:InvokeModelWithResponseStream"
               ],
               "Resource": [
                   "arn:aws:bedrock:*:*:model/anthropic.claude-sonnet-4-6"
               ]
           }
       ]
   }
   ```

Optional: To allow listing/querying the model catalog during diagnostics, you can add a scoped statement:
```json
{
  "Effect": "Allow",
  "Action": ["bedrock:ListFoundationModels", "bedrock:GetFoundationModel"],
  "Resource": "*"
}
```

### 3. Region Configuration

1. Ensure you're using a region where Bedrock and Claude Sonnet 4 are available:
   - US East (N. Virginia) - us-east-1
   - US West (Oregon) - us-west-2
   - Asia Pacific (Singapore) - ap-southeast-1
   - Europe (Frankfurt) - eu-central-1
   - Europe (London) - eu-west-2

2. Update your SAM template region accordingly in samconfig.toml

### 3.1 Fallback inference profile (optional)

If **Sonnet 4.6** hits **tokens per day** or **RPM** limits, you can deploy a **second** cross-region inference profile (e.g. **Sonnet 4.5**) and set template parameter **`BedrockFallbackInferenceProfileArn`** to its ID (e.g. `eu.anthropic.claude-sonnet-4-5`). The Lambda calls **primary** (`INFERENCE_PROFILE_ARN`) first; only on throttle-style errors it retries **once** with the fallback.

- Enable both models in Bedrock for your account; confirm **Service Quotas** for **each** profile.
- CloudWatch **`BedrockUsage`** logs include **`model_id`** so you can see which profile served each request.
- Fallback does **not** help if both profiles share the same global quota bucket.

### 4. Cost Management

1. Set up AWS Budget alerts:
   - Go to AWS Billing Console
   - Create a budget for Bedrock usage
   - Set monthly limits and alerts

2. Estimated costs for Claude Sonnet 4.6 (verify in AWS console per region):
   - **Europe (Ireland):** $3.00 per 1M input tokens, $15.00 per 1M output tokens (i.e. $0.003 per 1K input, $0.015 per 1K output).
   - Other regions (e.g. US) may differ; check Bedrock pricing in the console.
   - Higher quality and faster responses than legacy models.

#### 4.1 Invocation count (works before BedrockUsage is deployed)

To see **how many invocations** you had in a period (e.g. the whole month), use a query that matches log lines that appear once per run. Set the **time range** in Logs Insights to the full period:

- In **Logs Insights**: above the query editor, use the **time range** dropdown.
  - Choose **Last 30 days** for a rolling month, or **Custom range** and pick start/end (e.g. 1 Mar 00:00 to 31 Mar 23:59) for a calendar month.
- Query (one line per successful Bedrock call):

```text
fields @timestamp, @message
| filter @message like /Bedrock response:/
| stats count() as invocations
```

Run the query; the **invocations** value is the total for the selected time range. Use that number for monthly totals and cost estimates below.

#### 4.2 Token usage in logs and current cost (after deploy)

The Lambda logs **BedrockUsage** (input_tokens, output_tokens) for each Bedrock call so you can measure cost from CloudWatch Logs.

- **Where**: CloudWatch Log group for the Bedrock analysis Lambda (e.g. `/aws/lambda/<function-name>`).
- **Log line**: Each invocation logs one line starting with `BedrockUsage:` followed by JSON, e.g.  
  `BedrockUsage: {"input_tokens": 12345, "output_tokens": 890}`  
  Deploy the updated Lambda for these lines to appear; older log events will not contain usage.

**CloudWatch Logs Insights – total tokens, request count, and cost**

1. In CloudWatch → Log groups → select the Bedrock analysis Lambda log group.
2. Open **Logs Insights**, select that log group, set the time range (e.g. **Last 1 hour**, **Last 30 days**, or a custom month).
3. Run one of these queries. Each returns **total_input**, **total_output**, and **invocations** (number of requests in the period). The token totals are for *all* requests in the period, not per request; use `total_input / invocations` and `total_output / invocations` for averages.

   **If your log line is plain text** (e.g. `BedrockUsage: {"input_tokens": 3394, "output_tokens": 615}`):

```text
fields @timestamp, @message
| filter @message like /(?i)BedrockUsage:/
| parse @message /"input_tokens":\s*(?<input_tokens>\d+).*"output_tokens":\s*(?<output_tokens>\d+)/
| stats sum(input_tokens) as total_input, sum(output_tokens) as total_output, count(*) as invocations, avg(input_tokens) as avg_input, avg(output_tokens) as avg_output
```

   **If the log event is JSON and the message has escaped quotes** (e.g. `"message": "BedrockUsage: {\"input_tokens\": 3394, \"output_tokens\": 615}"`), use this so the regex matches `\"`:

```text
fields @timestamp, @message
| filter @message like /(?i)BedrockUsage:/
| parse @message /\\"input_tokens\\":\s*(?<input_tokens>\d+).*\\"output_tokens\\":\s*(?<output_tokens>\d+)/
| stats sum(input_tokens) as total_input, sum(output_tokens) as total_output, count(*) as invocations, avg(input_tokens) as avg_input, avg(output_tokens) as avg_output
```

   To see which format you have, run this once and check one `@message` value:

```text
fields @timestamp, @message
| filter @message like /(?i)BedrockUsage:/
| limit 1
```

   If the message shows backslash-quote (`\"input_tokens\"`) use the second query; otherwise use the first.

4. **Cost for the period** (example Europe Ireland, Claude Sonnet 4.6: $3/1M input, $15/1M output):
   - **Cost for this period** = `(total_input / 1_000_000) * 3.00 + (total_output / 1_000_000) * 15.00`.  
     Example: last hour total_input=295,420, total_output=51,748, invocations=88 → cost = 0.886 + 0.776 ≈ **$1.66 for that hour**.
   - **Per-request average** = cost for period ÷ invocations (e.g. $1.66 / 88 ≈ $0.019 per request). Use this to project cost when you scale by request count.
   Check Bedrock pricing in the console for your region.

**Use actual AWS billing as the baseline**

Token-based estimates from Logs Insights are useful for trends and projections, but **actual cost = what AWS charged** (e.g. last month $111.53). Short windows (e.g. 1 hour) can be spikes and are often not representative of average usage. For “current workload” cost and for projecting +users, use last month’s bill and your 30-day invocation count:

- **Current 30-day cost** = actual bill (e.g. $111.53).
- **Current 30-day invocations** = from the invocation-count query over the same 30 days (e.g. “Bedrock response:” or “BedrockUsage” count).
- **Cost per invocation (current)** = `current_30d_cost / invocations_30d`.
- **Projected cost when adding users** (e.g. +3000 users × 10 calls/user/day = 30,000 extra/day = 900,000 extra per 30 days):  
  `projected_30d_cost = current_30d_cost + (900_000 × cost_per_invocation)`.

**Token-based check (Europe Ireland, Claude Sonnet 4.6):** $3/1M input, $15/1M output. If your logs show ~3,357 input and ~588 output tokens per invocation (from a token-sum query over 30 days), then 900,000 extra invocations → extra input 3,021M × $3 = $9,063, extra output 529M × $15 = $7,938 → **~$17,000 extra per 30 days**, not $50k. Use your own 30-day token totals and invocation count for accurate numbers.

**Verification: lock the time period and requests per hour**

Always note the **Logs Insights time range** (e.g. Last 1 hour, Last 24 hours) so the numbers are unambiguous. Example with real data:

| Field | Value | Notes |
|-------|--------|--------|
| **Time range in Insights** | e.g. **Last 1 hour** | Set this explicitly so you know what “period” means. |
| invocations | 89 | Number of Bedrock requests in that period. |
| total_input | 312,255 | Sum of input tokens (all 89 requests). |
| total_output | 54,651 | Sum of output tokens (all 89 requests). |

**If the period = 1 hour:**

- **Requests per hour** = 89.
- **Cost for that hour** (Europe Ireland, $3/1M input, $15/1M output):  
  - Input: 312,255 ÷ 1,000,000 × 3 = **$0.9368**  
  - Output: 54,651 ÷ 1,000,000 × 15 = **$0.8198**  
  - **Total = $1.76** (for that 1 hour).
- **Per request** = $1.76 ÷ 89 = **$0.0198** per invocation.
- **Average tokens per request** = 312,255 ÷ 89 ≈ 3,508 input; 54,651 ÷ 89 ≈ 614 output.

If your Insights range was **not** 1 hour (e.g. 2 hours), then requests per hour = 89 ÷ 2 = 44.5 and cost per hour = $1.76 ÷ 2 = $0.88. Always divide by the **actual period length in hours** to get requests per hour and cost per hour.

**Example: one full day, 300 users**

| Field | Value | Notes |
|-------|--------|--------|
| **Time range** | 1 day | |
| **Users** | 300 | |
| invocations | 289 | Requests that day. |
| total_input | 991,303 | Sum of input tokens (all 289 requests). |
| total_output | 175,977 | Sum of output tokens (all 289 requests). |

**Cost for that day** (Europe Ireland: $3/1M input, $15/1M output):

- Input: 991,303 ÷ 1,000,000 × 3 = **$2.97**
- Output: 175,977 ÷ 1,000,000 × 15 = **$2.64**
- **Total for the day = $5.61**

**Derived rates:**

- **Requests per day** = 289 (for 300 users).
- **Requests per user per day** = 289 ÷ 300 ≈ **0.96** (about 1 call per user per day).
- **Cost per request** = $5.61 ÷ 289 = **$0.0194**.
- **If every day were like this:** 289 × 30 = 8,670 invocations/month → $5.61 × 30 = **$168.30/month** (your actual $111.53 suggests many days are lighter than this).

**Estimate: same usage per user as today**

If new users use Bedrock at the **same rate per user** as today, scale by users only (no extra calls/user/day):

- **Current:** 300 users, **$111.53/month** → **~$0.37 per user per month** (from actual bill ÷ users, not from the sample hour).
- **Add 3,000 users** (3,300 total), same usage per user:
  - **Total projected cost** ≈ 3,300 × $0.37 ≈ **$1,221/month**.
  - **Extra cost** for the 3,000 new users ≈ **~$1,110/month**.

Update the $0.37 if your actual bill or user count changes: `cost_per_user_per_month = last_month_bill / current_user_count`.

**Estimated cost when token data is not yet available**

If BedrockUsage is not in logs yet, use the **invocation count** from the query in §4.1 and a **typical tokens-per-call** guess for this workload (long prompt + 2 images + structured response). Example placeholder: **~10,000 input tokens** and **~600 output tokens** per invocation. Then:

- **Cost per invocation** ≈ (10 × 0.015) + (0.6 × 0.075) = 0.15 + 0.045 = **~$0.195**
- **Monthly estimate** = invocations (from §4.1 query over the month) × 0.195  

Example: 41 invocations in one day → 41 × 30 ≈ 1,230 invocations/month → **~$240/month**. After you deploy and log BedrockUsage, run the token-sum query over 7–30 days and replace the 10k/600 guess with your real averages for accurate numbers.

#### 4.3 Projecting cost for more users (e.g. +3000 users, +10 calls/user/day)

- **Current workload**: From Insights, get `total_invocation_count` (e.g. count log lines with `BedrockUsage`) and `total_input`, `total_output` for the same period.  
  - Example: last 30 days → `invocations_30d`, `input_30d`, `output_30d`.  
  - Average tokens per invocation: `avg_input = input_30d / invocations_30d`, `avg_output = output_30d / invocations_30d`.
- **New workload**:  
  - Extra invocations per day = 3000 users × 10 calls/user/day = **30,000 calls/day**.  
  - Extra per 30 days ≈ 30,000 × 30 = 900,000 invocations (or use exact days).
- **Projected extra tokens (30 days)**  
  - Extra input = 900,000 × avg_input  
  - Extra output = 900,000 × avg_output  
- **Projected extra cost (30 days)**  
  - Extra cost ≈ (extra_input/1000 × 0.015) + (extra_output/1000 × 0.075)  
  - Total projected cost ≈ current 30-day cost + extra cost.

**Insights – invocation count when BedrockUsage is available (for averages)**

```text
fields @timestamp, @message
| filter @message like /BedrockUsage:/
| stats count() as invocations
```

Use the same time range as for token sums so that (total_input, total_output) and invocations are from the same period when computing averages.

### 5. Testing Access

1. Test Bedrock access using AWS CLI:
   ```bash
   aws bedrock list-foundation-models --region your-region
   ```

2. Verify Claude Sonnet 4 is available:
   ```bash
   aws bedrock get-foundation-model \
     --model-identifier anthropic.claude-sonnet-4-6 \
     --region your-region
   ```

## Troubleshooting

### Common Issues

1. **Model Access Error**
   - Ensure model access is enabled in Bedrock console
   - Check IAM permissions
   - Verify region availability

2. **Quota Limits**
   - Default quota: 5 requests per second
   - Request quota increase if needed via AWS Support

3. **Permission Errors**
   - Verify IAM role has correct policies
   - Check resource ARNs match your region

### Support Resources

1. AWS Bedrock Documentation:
   - [Getting Started Guide](https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html)
   - [Claude Model Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html)

2. AWS Support:
   - Open support ticket for quota increases
   - Use AWS re:Post for technical questions

## Security Best Practices

1. **Access Control**
   - Use least privilege principle
   - Regularly rotate access keys
   - Monitor CloudTrail for Bedrock API calls

2. **Data Protection**
   - Enable encryption in transit
   - Don't send sensitive data to the model
   - Review model outputs before storage

3. **Monitoring**
   - Set up CloudWatch alarms for errors
   - Monitor usage and costs
   - Enable AWS Config rules

## Next Steps

After completing setup:

1. Deploy the Lambda function using SAM
2. Test with sample images
3. Monitor initial usage and costs
4. Set up production monitoring and alerts

## Model Advantages

Claude Sonnet 4 offers several improvements over legacy models:

1. **Better Vision Analysis**
   - More accurate image understanding
   - Better context awareness
   - Improved shelf and product recognition

2. **Performance**
   - Faster response times
   - More consistent results
   - Better handling of complex retail scenarios

3. **Cost Efficiency**
   - Better quality-to-cost ratio
   - More accurate first-time responses
   - Reduced need for multiple analysis attempts

## Prompt and parsing (consistency)

The product-on-shelf verification uses a single **match rule**: same brand, category, and packaging form as the reference; variant name and minor label/imagery differences are allowed. This avoids contradictory instructions and improves consistent TRUE/FALSE results.

- **Temperature**: Set to 0.2 (was 0.9) for more deterministic verification.
- **POSITION convention**: Shelves are numbered from top to bottom (Shelf 1 = top). The model is asked to report position as `Shelf N, left|center|right` (e.g. "Shelf 4, left").
- **Structured output**: The model is instructed to end the response with exactly five lines: FINAL_RESULT, CONFIDENCE, FACINGS_COUNT, POSITION, HAS_PI_LABEL. Parsing uses only the **last 10 lines** of the response so that TRUE/FALSE in the body (e.g. HAS_PI_LABEL: TRUE) does not affect the main result; the heuristic fallback also runs only on that block.