An exploration of Amazon Bedrock Agents and custom action groups, using a real-world weather data use case for Disney World parks. Three tools providing surface observations, hourly forecasts, and upper-atmosphere radiosonde data were built and wired into a Bedrock Agent via two distinct integration architectures — direct Lambda action groups and Azure Logic App action groups — resulting in a three-cloud data flow in a single agent invocation.
The experiment specifically compared two ways of providing tools to a Bedrock Agent. Both were wired to the same GCP Cloud Run weather backend — the only variable was how the action group was implemented.
aws_lambda/lambda_atmospheric.pyactionGroup, apiPath, httpMethod, messageVersionapi_response envelope — nested action_response → responseBodyapiPath + httpMethod inside handlerazure_logicapps//invoke endpointstatusCode / body — no Bedrock envelope needed (action group handles wrapping)sig query param)Bedrock Agents use OpenAPI 3.0 schemas to understand what an action group does and what parameters it accepts. Each tool was defined as a separate spec and registered as its own action group. The agent selects the right tool based on semantic understanding of the user's question.
The most implementation-specific learning from this project: Bedrock Agents don't call Lambda functions with a plain HTTP event. They send and expect a structured envelope that includes agent metadata alongside the action payload. Getting this wrong produces silent failures.
actionGroup, apiPath, httpMethod, messageVersion
Route on apiPath == "/atmospheric_conditions" and httpMethod == "GET"
Unknown paths return error body with HTTP 200 — Bedrock always sees 200 at transport layer
72210 (atmospheric) or KISM (surface)
GET weather-endpoint-936515207074.us-east1.run.app — GCP Cloud Run
response.raise_for_status() — propagates HTTP errors as exceptions
Response body: json.dumps(weather_data) — must be stringified JSON, not dict
responseBody → application/json → body structure
Echo actionGroup, apiPath, httpMethod from inbound event
Top-level key must be "response" — not "actionResponse" or any other variant
messageVersion echoed from event['messageVersion']
The Logic Apps experiment was motivated by a specific question: can a Bedrock Agent's action groups be served from non-AWS infrastructure? If the answer is yes, it means the agent reasoning layer (Bedrock) can be decoupled from the tool execution layer, and tools can run wherever the data lives — without routing everything through Lambda first. The Logic Apps backends already existed for another Azure workflow, so reusing them as Bedrock action groups was a low-effort test of the cross-cloud pattern.
The finding was mixed: technically it works — Bedrock calls the Logic App's HTTP trigger URL as specified in the OpenAPI server block, and the Logic App's response is passed back to the agent. But the SAS URL authentication is awkward to manage (the sig token expires), and the two-hop latency (Bedrock → Azure → GCP) adds observable delay compared to the direct Lambda path.
Testing tool selection behavior was a core goal. A Bedrock Agent with one weather tool can't demonstrate anything interesting about agent reasoning. With three tools serving different data types at different stations, the agent must decide which tool to invoke based on the user's question. "Is it raining?" → observations. "Will it storm this afternoon?" → forecast. "Are conditions favorable for afternoon thunderstorms developing?" → atmospheric soundings.
The upper-atmosphere sounding tool (station 72210) was the most interesting to test because its value proposition is not obvious from a consumer perspective — NWS radiosonde data isn't something most people know exists. Whether the agent would correctly identify when to use it (when asked about convective instability, CAPE index, or storm development potential) vs defaulting to the surface forecast was a useful signal for evaluating the agent's tool reasoning quality.
The single largest friction point in getting the Lambda action group working correctly was the response format. A standard Lambda function returns {"statusCode": 200, "body": "..."}. Bedrock Agents require a completely different structure: the responseBody must be nested inside an action_response object inside a top-level response key, and the body itself must be a JSON string (not a parsed object). An early version returned the weather data as a Python dict inside the body — Bedrock silently discarded it.
The messageVersion echo requirement was also non-obvious: the top-level response must include "messageVersion": event['messageVersion'] or the agent invocation fails with an opaque validation error. None of these requirements are enforced at Lambda test time — they only surface when the actual Bedrock agent invokes the function.
Three decisions from this prototype carried forward into the production Park Agent Chat (Azure Functions-based):
The production system migrated from AWS Bedrock to Azure AI (Azure Functions + Azure OpenAI), but the action group design principles — narrow tools, OpenAPI specs, one purpose per tool — were directly informed by what worked and didn't work in this prototype.