DEV Community

Cover image for Bedrock Agent Action Groups: Connect Your Agent to APIs with Terraform πŸ”Œ
Suhas Mallesh
Suhas Mallesh

Posted on

Bedrock Agent Action Groups: Connect Your Agent to APIs with Terraform πŸ”Œ

A Bedrock Agent without tools is just a chatbot. Action groups give your agent the ability to call APIs, query databases, and take real actions. Here's how to wire up Lambda functions with OpenAPI schemas using Terraform.

In the previous post, we deployed a Bedrock Agent that can reason and hold multi-turn conversations. But without tools, it can only generate text. Ask it to check inventory, create a ticket, or look up a customer, and it can only describe what it would do.

Action groups change that. An action group connects your agent to a Lambda function through an OpenAPI schema that describes the available operations. The agent reads the schema, decides which operation to call based on the user's request, extracts the required parameters from the conversation, and invokes the Lambda function. The agent becomes an actor, not just a talker. 🎯

πŸ—οΈ How Action Groups Work

User: "What's the exchange rate from USD to EUR?"
    ↓
Agent reads OpenAPI schema β†’ finds getExchangeRate operation
    ↓
Agent extracts parameters: currency_from=USD, currency_to=EUR
    ↓
Agent invokes Lambda function with parameters
    ↓
Lambda calls external API, returns result
    ↓
Agent formats response: "The current rate is 1 USD = 0.92 EUR"
Enter fullscreen mode Exit fullscreen mode

The agent handles the reasoning. The OpenAPI schema describes what's possible. The Lambda function does the work. You define all three in Terraform.

πŸ”§ Two Schema Options

Bedrock supports two ways to define what your action group can do:

Approach Best For Definition
OpenAPI schema REST APIs with multiple endpoints Full OpenAPI 3.0 spec with paths, methods, parameters
Function schema Simple actions with clear parameters Inline function definitions in Terraform

OpenAPI schemas are more explicit and map directly to API operations. Function schemas are simpler for straightforward actions. This post covers both.

πŸ”§ Terraform: Action Group with OpenAPI Schema

The OpenAPI Schema

{
  "openapi": "3.0.0",
  "info": {
    "title": "Exchange Rate API",
    "version": "1.0.0"
  },
  "paths": {
    "/exchange-rate": {
      "get": {
        "operationId": "getExchangeRate",
        "description": "Get the current exchange rate between two currencies. Use this when the user asks about currency conversion or exchange rates.",
        "parameters": [
          {
            "name": "currency_from",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "The source currency code (e.g., USD, EUR, GBP)"
          },
          {
            "name": "currency_to",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "The target currency code (e.g., USD, EUR, GBP)"
          }
        ],
        "responses": {
          "200": {
            "description": "Exchange rate result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rate": { "type": "number" },
                    "from": { "type": "string" },
                    "to": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The description fields are critical. The agent uses these descriptions to decide when to call the operation and what parameters to extract. Vague descriptions lead to poor tool selection. Be specific about when the operation should be used.

Lambda Function

# lambda/exchange_rate/index.py

import json
import urllib.request

def handler(event, context):
    # Bedrock passes the operation and parameters
    api_path = event.get("apiPath")
    parameters = {p["name"]: p["value"] for p in event.get("parameters", [])}

    currency_from = parameters.get("currency_from", "USD")
    currency_to = parameters.get("currency_to", "EUR")

    # Call external API
    url = f"https://api.frankfurter.dev/v1/latest?base={currency_from}&symbols={currency_to}"
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as resp:
        data = json.loads(resp.read())

    rate = data["rates"].get(currency_to, "N/A")

    # Return in Bedrock's expected format
    return {
        "messageVersion": "1.0",
        "response": {
            "actionGroup": event["actionGroup"],
            "apiPath": api_path,
            "httpMethod": event.get("httpMethod", "GET"),
            "httpStatusCode": 200,
            "responseBody": {
                "application/json": {
                    "body": json.dumps({
                        "rate": rate,
                        "from": currency_from,
                        "to": currency_to
                    })
                }
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

The response format matters. Bedrock expects a specific structure with messageVersion, response.actionGroup, and response.responseBody. Get this wrong and the agent receives empty results.

Terraform: Wire It All Together

# action_groups/lambda.tf

data "archive_file" "exchange_rate" {
  type        = "zip"
  source_dir  = "${path.module}/lambda/exchange_rate"
  output_path = "${path.module}/lambda/exchange_rate.zip"
}

resource "aws_lambda_function" "exchange_rate" {
  function_name    = "${var.environment}-exchange-rate"
  handler          = "index.handler"
  runtime          = "python3.12"
  timeout          = 30
  memory_size      = 128
  role             = aws_iam_role.action_lambda.arn
  filename         = data.archive_file.exchange_rate.output_path
  source_code_hash = data.archive_file.exchange_rate.output_base64sha256
}

# Allow Bedrock to invoke the Lambda
resource "aws_lambda_permission" "bedrock_invoke" {
  statement_id  = "AllowBedrockInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.exchange_rate.function_name
  principal     = "bedrock.amazonaws.com"
  source_arn    = aws_bedrockagent_agent.this.agent_arn
}
Enter fullscreen mode Exit fullscreen mode
# action_groups/action_group.tf

resource "aws_bedrockagent_agent_action_group" "exchange_rate" {
  action_group_name = "ExchangeRateAPI"
  agent_id          = aws_bedrockagent_agent.this.agent_id
  agent_version     = "DRAFT"
  description       = "Get current exchange rates between currencies"

  action_group_executor {
    lambda = aws_lambda_function.exchange_rate.arn
  }

  api_schema {
    payload = file("${path.module}/schemas/exchange_rate.json")
  }

  skip_resource_in_use_check = true
}
Enter fullscreen mode Exit fullscreen mode

After adding the action group, the agent must be re-prepared. If prepare_agent = true on the agent resource, Terraform handles this automatically for agent changes. For action group changes, add a null_resource to trigger re-preparation:

resource "null_resource" "prepare_after_action_group" {
  triggers = {
    action_group = aws_bedrockagent_agent_action_group.exchange_rate.id
  }

  provisioner "local-exec" {
    command = "aws bedrock-agent prepare-agent --agent-id ${aws_bedrockagent_agent.this.agent_id} --region ${var.region}"
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”§ Alternative: Function Schema (Simpler)

For straightforward actions, skip the OpenAPI file and define functions inline:

resource "aws_bedrockagent_agent_action_group" "weather" {
  action_group_name = "WeatherLookup"
  agent_id          = aws_bedrockagent_agent.this.agent_id
  agent_version     = "DRAFT"
  description       = "Get current weather for a location"

  action_group_executor {
    lambda = aws_lambda_function.weather.arn
  }

  function_schema {
    member_functions {
      functions {
        name        = "getWeather"
        description = "Get current weather conditions for a city"

        parameters {
          map_block_key = "city"
          type          = "string"
          description   = "City name (e.g., Seattle, London, Tokyo)"
          required      = true
        }

        parameters {
          map_block_key = "units"
          type          = "string"
          description   = "Temperature units: celsius or fahrenheit"
          required      = false
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

No external schema file needed. Terraform defines everything inline. Good for one or two functions; use OpenAPI schemas when you have multiple endpoints.

πŸ“ Multiple Action Groups

Agents can have multiple action groups, each with its own Lambda and schema. The agent decides which action group to use based on the user's request:

# Each action group = one capability domain
# ExchangeRateAPI  β†’ currency operations
# WeatherLookup    β†’ weather queries
# TicketManager    β†’ support ticket CRUD
Enter fullscreen mode Exit fullscreen mode

Keep action groups focused on a single domain. An agent with 3 focused action groups performs better than one with 15 mixed operations because the model has clearer context for tool selection.

⚠️ Gotchas and Tips

Lambda response format. The most common error is returning a plain JSON response from Lambda instead of Bedrock's expected format with messageVersion and responseBody. The agent silently gets no result.

Description quality drives tool selection. The agent uses OpenAPI descriptions to decide which operation to invoke. Spend time writing clear, specific descriptions. Include when the operation should and should not be used.

Re-preparation after changes. Adding or modifying action groups requires re-preparing the agent. The null_resource pattern handles this in Terraform.

Tool name format. If using Claude models, tool names follow the format httpVerb__actionGroupName__apiName. Avoid double underscores in your action group or API names.

Return control option. Instead of Lambda execution, you can set custom_control = "RETURN_CONTROL" to have the agent return the operation and parameters to your application. Your app executes the action and sends the result back. Useful when the action requires client-side execution.

⏭️ What's Next

This is Post 2 of the AWS AI Agents with Terraform series.

  • Post 1: Deploy First Bedrock Agent πŸ€–
  • Post 2: Action Groups - Connect to APIs (you are here) πŸ”Œ
  • Post 3: Multi-Agent Orchestration
  • Post 4: Agent + Knowledge Base Grounding

Your agent can now take action. It reads the schema, reasons about what to call, extracts parameters from conversation, and invokes your Lambda. The model thinks; the Lambda acts. πŸ”Œ

Found this helpful? Follow for the full AI Agents with Terraform series! πŸ’¬

Top comments (0)