🧭 Navigation:
Home | Vision LLM Theory | Local Testing | Deployment UI | Deployment | CDK Stacks | Runtime | S3 Files | 🔵 Lambda Analyzers | Prompting System
This document explains how Lambda analyzers work in BADGERS—their anatomy, required layers, environment variables, and code patterns.
BADGERS uses four types of Lambda functions:
| Type | Purpose | Example |
|---|---|---|
| 🔍 Vision Analyzers | Analyze images using Bedrock vision models | full_text_analyzer, table_analyzer, etc. |
| 🐳 Container Lambdas | Processing requiring large deps or custom pipelines | image_enhancer, remediation_analyzer |
| 🔧 Utilities | Transform or prepare data | pdf_to_images_converter |
| 🔗 Correlators | Correlate outputs across analyzers per page | correlation_analyzer |
| Layer | Purpose |
|---|---|
| 🧠 Foundation Layer | Core framework, boto3, Pillow, pdf2image, shared utilities |
| Layer | Purpose |
|---|---|
| 📄 Poppler Layer | pdf2image requires Poppler binaries for PDF rendering |
| Variable | Required | Default | Description |
|---|---|---|---|
CONFIG_BUCKET |
✅ | - | S3 bucket containing analyzer configs |
OUTPUT_BUCKET |
✅ | - | S3 bucket for saving results |
ANALYZER_NAME |
✅ | - | Analyzer identifier (e.g., full_text_analyzer) |
LOGGING_LEVEL |
❌ | INFO |
Log verbosity |
MAX_TOKENS |
❌ | 8000 |
Max response tokens from Bedrock |
TEMPERATURE |
❌ | 0.1 |
Model temperature (lower = more deterministic) |
AWS_REGION |
❌ | us-west-2 |
Region for Bedrock calls |
DYNAMIC_TOKENS_ENABLED |
❌ | false |
Enable complexity-based dynamic token estimation |
| Parameter | Required | Description |
|---|---|---|
session_id |
✅ | Runtime session ID for tracing and S3 output |
image_path |
✅* | S3 URL or file path (*or image_data) |
image_data |
✅* | Base64-encoded image (*or image_path) |
aws_profile |
❌ | Optional AWS profile for local testing |
audit_mode |
❌ | Enable confidence scoring and human review flags |
dynamic_tokens_enabled |
❌ | Enable dynamic max_tokens based on image complexity (default: false) |
| Variable | Required | Description |
|---|---|---|
OUTPUT_BUCKET |
✅ | S3 bucket for storing converted images |
Every vision analyzer follows the same pattern:
def lambda_handler(event, context):
# 1️⃣ Log Gateway context (for AgentCore tracing)
# 2️⃣ Detect config source (S3 vs local)
# 3️⃣ Parse input and extract session_id
# 4️⃣ Get image data (S3 or base64)
# 5️⃣ Load configuration
# 6️⃣ Initialize analyzer with foundation
# 7️⃣ Run analysis
# 8️⃣ Save result to S3
# 9️⃣ Return responseif hasattr(context, "client_context") and context.client_context:
gateway_id = context.client_context.custom.get("bedrockAgentCoreGatewayId", "unknown")
tool_name = context.client_context.custom.get("bedrockAgentCoreToolName", "unknown")
logger.info("Gateway invocation - Gateway: %s, Tool: %s", gateway_id, tool_name)When invoked via AgentCore Gateway, the Lambda receives metadata about which gateway and tool triggered it.
config_bucket = os.environ.get("CONFIG_BUCKET")
if os.environ.get("AWS_EXECUTION_ENV") and config_bucket:
config_source = "s3" # Running in Lambda with S3 config
else:
config_source = "local" # Local testing with manifest.jsonAnalyzers auto-detect whether to load config from S3 (production) or local filesystem (testing).
session_id = body.get("session_id", "no_session")
logger.info("Processing request for runtime session_id: %s", session_id)AgentCore Runtime passes a session_id that links all tool invocations in a conversation. This enables:
- 📊 Tracing requests across multiple Lambda calls
- 📁 Organizing outputs by session in S3
- 🔍 Debugging multi-step workflows
def _get_image_data(body: dict) -> bytes:
if "image_data" in body:
return base64.b64decode(body["image_data"]) # Direct base64
if "image_path" in body:
if image_path.startswith("s3://"):
# Download from S3
s3.get_object(Bucket=bucket, Key=key)
# Handle .b64 files (pre-encoded from pdf_to_images)
if key.endswith(".b64"):
return base64.b64decode(data.decode("utf-8"))
return bytes(data)Supports three input modes:
- 📤 Direct base64 -
image_datafield - ☁️ S3 path -
s3://bucket/keyformat - 📄 Pre-encoded .b64 - From PDF converter output
from foundation.analyzer_foundation import AnalyzerFoundation
analyzer = AnalyzerFoundation("full_text_analyzer")
result = analyzer.analyze(image_data, aws_profile)The Foundation Layer handles all complexity:
- 📝 Prompt loading and composition
- 🖼️ Image optimization
- 🤖 Bedrock invocation with retries
- 📤 Response processing
from foundation.s3_result_saver import save_result_to_s3
s3_uri = save_result_to_s3(
result=result,
analyzer_name=analyzer_name,
output_bucket=output_bucket,
session_id=session_id,
image_path=body.get("image_path"),
)Results are saved to S3 with path: results/{session_id}/{analyzer_name}/{timestamp}.txt
The PDF converter transforms PDFs into analyzable images:
def lambda_handler(event, context):
# 1️⃣ Get PDF from S3 or local path
pdf_data = _get_pdf_data(pdf_path)
# 2️⃣ Convert to images using pdf2image + Poppler
base64_images = _convert_pdf_to_images(pdf_data, dpi, max_size_mb)
# 3️⃣ Store as .b64 files in S3 temp location
s3_paths = _store_images_to_s3(base64_images, session_id)
# 4️⃣ Return S3 paths for downstream analyzers
return {"images": s3_paths, "page_count": len(s3_paths)}quality = 85
while quality > 20:
img.save(buffer, format="JPEG", quality=quality, optimize=True)
if buffer.tell() <= max_size_bytes:
break
quality -= 10 # Reduce quality until under size limitIteratively compresses images to meet Bedrock's size limits (default 4MB).
Combines results from multiple analyzer invocations:
def _aggregate_by_page(execution_results: list, pdf_name: str) -> dict:
pages = {}
for result in execution_results:
page_num = result.get("page", 0)
tool_name = result.get("tool", "unknown")
if page_num not in pages:
pages[page_num] = {"page": page_num, "analyses": []}
pages[page_num]["analyses"].append({
"tool": tool_name,
"result": result.get("result"),
"success": result.get("success")
})
return {"pdf_name": pdf_name, "pages": sorted(pages.values())}Output structure:
{
"pdf_name": "document.pdf",
"total_pages": 3,
"pages": [
{"page": 1, "analyses": [{"tool": "full_text", "result": "..."}]},
{"page": 2, "analyses": [{"tool": "full_text", "result": "..."}]}
]
}All Lambdas use standard try/except patterns:
try:
# ... processing ...
except Exception as e:
logger.error("Error: %s", e, exc_info=True)
return {
"statusCode": 500,
"body": json.dumps({"result": str(e), "success": False}),
}{
"session_id": "abc123",
"image_path": "s3://bucket/image.png",
"aws_profile": null
}Or with direct base64:
{
"session_id": "abc123",
"image_data": "base64-encoded-image-bytes"
}{
"statusCode": 200,
"body": {
"result": "Extracted text or analysis...",
"success": true,
"session_id": "abc123"
}
}Option 1: Use the Wizard (Recommended)
cd frontend
uv run python main.pyThe Analyzer Creation Wizard is available as the 🧙 Create Analyzer tab in the Local Testing UI.
Option 2: Manual Creation
- Create directory:
deployment/lambdas/code/{analyzer_name}/ - Copy
lambda_handler.pyfrom an existing analyzer - Update
ANALYZER_NAMEreferences - Create manifest in
deployment/s3_files/manifests/{analyzer_name}.json - Create prompts in
deployment/s3_files/prompts/{analyzer_name}/ - Add to CDK stack in
deployment/stacks/
The Foundation Layer handles everything else automatically.