The Bedrock Converse API toolConfig

How to declare tools as JSON Schema in the Converse API's toolConfig, read a toolUse block, and return a toolResult that Bedrock will accept.

The Amazon Bedrock Converse API (converse and converse_stream on the bedrock-runtime client) gives every supported model one request and response shape. That matters most for tool use: before Converse, each model family expressed tool calls differently, so switching models meant rewriting your parsing code. With Converse you declare tools once in toolConfig and the same code works across the models that support tool use.

Declaring a tool

toolConfig has two keys. tools is a list, each entry a toolSpec with three fields:

  • name — the identifier the model emits back to you. Keep it a stable, snake_case verb phrase.
  • description — natural language telling the model what the tool does and when to use it. This is prompt text, not documentation; it is the single biggest influence on whether the model picks the right tool.
  • inputSchema — an object with one key, json, whose value is a JSON Schema object describing the arguments. Use type, properties, required, enum and per-property description.

The optional toolChoice key steers selection: {"auto": {}} lets the model decide, {"any": {}} forces it to call some tool, and {"tool": {"name": "..."}} forces one specific tool. Support for the forcing variants differs by model, so check the model's documentation before relying on them.

Reading the response

Check stopReason first. "tool_use" means the model wants a tool; "end_turn" means it finished; "max_tokens" means you truncated it. The assistant message in output.message.content is a list of blocks, and a tool call arrives as a toolUse block containing toolUseId, name and input (already parsed into a dictionary for you). A single turn may contain several toolUse blocks plus text — handle all of them.

Returning the result

Two rules the API enforces. First, append the assistant message exactly as returned to your messages list — do not rebuild it or drop blocks. Second, the next message must have role user and contain one toolResult block per toolUse, each carrying the matching toolUseId. A missing or mismatched ID is a validation error, not a soft failure. Inside a toolResult, content accepts json, text, image or document blocks, and status is "success" or "error".

Streaming and other gotchas

With converse_stream, tool arguments arrive as fragments of a JSON string in contentBlockDelta events. You must concatenate them and parse only once the block stops — never parse a partial fragment. Also note that toolConfig is rejected by models that do not support tool use, so model choice and tool design are the same decision. If you need content filtering, guardrailConfig is a separate parameter on the same call and applies independently of your tools.

Example

Example · json
{
  "toolConfig": {
    "toolChoice": { "auto": {} },
    "tools": [
      {
        "toolSpec": {
          "name": "search_policy_documents",
          "description": "Search the company travel-policy corpus and return the three most relevant passages. Use this whenever the user asks what is allowed, reimbursable or required for business travel.",
          "inputSchema": {
            "json": {
              "type": "object",
              "properties": {
                "query": {
                  "type": "string",
                  "description": "Natural-language search phrase"
                },
                "region": {
                  "type": "string",
                  "enum": ["emea", "amer", "apac"],
                  "description": "Policy region. Omit only if the user has not said."
                }
              },
              "required": ["query"]
            }
          }
        }
      }
    ]
  },

  "_response_from_bedrock": {
    "stopReason": "tool_use",
    "output": {
      "message": {
        "role": "assistant",
        "content": [
          { "text": "Let me check the EMEA policy." },
          {
            "toolUse": {
              "toolUseId": "tooluse_9Kx1a2bQRr-8vZ",
              "name": "search_policy_documents",
              "input": { "query": "business class long haul", "region": "emea" }
            }
          }
        ]
      }
    }
  },

  "_your_next_message": {
    "role": "user",
    "content": [
      {
        "toolResult": {
          "toolUseId": "tooluse_9Kx1a2bQRr-8vZ",
          "content": [
            { "json": { "passages": ["Business class is permitted on flights over 6 hours."] } }
          ],
          "status": "success"
        }
      }
    ]
  }
}

When to use it

  • A team swaps the model behind an assistant from one Bedrock provider to another without touching the tool-parsing code, because Converse normalises the toolUse and toolResult shape.
  • A claims assistant returns a structured JSON toolResult containing policy limits so the model can reason over typed fields instead of re-parsing prose.
  • A streaming chat UI buffers contentBlockDelta fragments of a tool's arguments and only parses the JSON once the content block is complete.

Discussion

  • Be the first to comment on this lesson.