Skip to content

Amazon Bedrock V2 Client with AG2#

Open In Colab Open on GitHub

This notebook demonstrates how to use the Bedrock V2 Client (ModelClientV2 architecture) with AG2. The V2 client returns rich UnifiedResponse objects with typed content blocks, providing better access to multimodal content, tool calls, and provider-specific features.

What is Bedrock V2 Client?#

The Bedrock V2 client (api_type: "bedrock_v2") is the next-generation client architecture that:

  • Returns UnifiedResponse: Rich, provider-agnostic response format with typed content blocks
  • Preserves Rich Content: Text, images, tool calls, and other content types are preserved as typed blocks
  • Direct Property Access: Use response.text, response.get_content_by_type() instead of parsing nested structures
  • Forward Compatible: Handles unknown content types via GenericContent
  • Backward Compatible: Works seamlessly with V1 clients in the same conversation

Key Differences: V1 vs V2#

Feature V1 Client (api_type: "bedrock") V2 Client (api_type: "bedrock_v2")
Response Format ChatCompletion (flattened) UnifiedResponse (rich, typed)
Content Access client.message_retrieval(response) response.text, response.messages
Rich Content Lost or requires parsing Preserved as typed blocks
Tool Calls Flattened to dict ToolCallContent objects
Images Not easily accessible ImageContent objects
Forward Compatible Limited Yes (via GenericContent)

Requirements#

  • Python >= 3.10
  • AG2 installed: pip install ag2
  • boto3 package: pip install boto3
  • AWS credentials configured (via environment variables, IAM role, or AWS credentials file)
  • A Bedrock model that supports the features you need (Tool Use, multimodal, etc.)

Installation#

%pip install ag2 boto3 pydantic --upgrade

Setup: Import Libraries and Configure AWS Credentials#

import os

from dotenv import load_dotenv
from pydantic import BaseModel

from autogen import ConversableAgent, LLMConfig

load_dotenv()

print("Libraries imported successfully!")

Part 1: Basic Bedrock V2 Client Usage#

Let’s start with a simple example using the Bedrock V2 client:

# Configure LLM to use Bedrock V2 client
llm_config_v2 = LLMConfig(
    config_list=[
        {
            "api_type": "bedrock_v2",  # <-- Key: use V2 client architecture
            "model": "qwen.qwen3-coder-480b-a35b-v1:0",
            "aws_region": os.getenv("AWS_REGION", "eu-north-1"),
            "aws_access_key": os.getenv("AWS_ACCESS_KEY"),
            "aws_secret_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
            "aws_profile_name": os.getenv("AWS_PROFILE"),
        }
    ],
)

# Compare with V1 client configuration
llm_config_v1 = LLMConfig(
    config_list=[
        {
            "api_type": "bedrock",  # <-- V1 client architecture
            "model": "qwen.qwen3-coder-480b-a35b-v1:0",
            "aws_region": os.getenv("AWS_REGION", "eu-north-1"),
            "aws_access_key": os.getenv("AWS_ACCESS_KEY"),
            "aws_secret_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
            "aws_profile_name": os.getenv("AWS_PROFILE"),
        }
    ],
    temperature=0.3,
)

print("Bedrock V2 and V1 configurations created!")

Part 2: Direct Client Usage - Accessing Rich Responses#

Let’s see how to use the Bedrock V2 client directly to access rich response content:

from autogen.llm_clients.bedrock_v2 import BedrockV2Client
from autogen.llm_clients.models import UnifiedResponse

# Create Bedrock V2 client directly
client = BedrockV2Client(
    aws_region=os.getenv("AWS_REGION", "eu-north-1"),
    aws_access_key=os.getenv("AWS_ACCESS_KEY"),
    aws_secret_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
)

# Make a request
response = client.create({
    "model": "qwen.qwen3-coder-480b-a35b-v1:0",
    "messages": [{"role": "user", "content": "Explain quantum computing in 2 sentences."}],
})

# Verify it's a UnifiedResponse
print(f"Response type: {type(response)}")
print(f"Is UnifiedResponse: {isinstance(response, UnifiedResponse)}")
print(f"\nProvider: {response.provider}")
print(f"Model: {response.model}")
print(f"\nText content: {response.text}")
print(f"\nUsage: {response.usage}")
print(f"Cost: ${response.cost:.6f}" if response.cost else "Cost: N/A")

Part 3: Accessing Content Blocks#

The V2 client preserves all content as typed blocks. Let’s explore the different content types:

# Access individual messages and content blocks
for i, message in enumerate(response.messages):
    print(f"\nMessage {i + 1}:")
    print(f"  Role: {message.role}")
    print(f"  Content blocks: {len(message.content)}")

    for j, block in enumerate(message.content):
        print(f"\n  Block {j + 1}:")
        print(f"    Type: {block.type}")
        print(f"    Class: {type(block).__name__}")

        # Access text content
        if hasattr(block, "text"):
            print(f"    Text: {block.text[:100]}..." if len(block.text) > 100 else f"    Text: {block.text}")

        # Access tool calls
        if hasattr(block, "name"):
            print(f"    Tool: {block.name}")
            print(
                f"    Arguments: {block.arguments[:100]}..."
                if len(block.arguments) > 100
                else f"    Arguments: {block.arguments}"
            )

# Use helper methods
print("\n" + "=" * 60)
print("Using helper methods:")
print(f"All text: {response.text}")
print(f"Tool calls: {len(response.get_content_by_type('tool_call'))}")
print(f"Text blocks: {len(response.get_content_by_type('text'))}")

Part 4: Structured Outputs with Bedrock V2#

Bedrock V2 client supports structured outputs via response_format. Let’s define a Pydantic model and use it:

# Define structured output model
class Step(BaseModel):
    """Represents a single step in solving a problem."""

    explanation: str
    output: str

class ProblemSolution(BaseModel):
    """Complete structured response for a problem solution."""

    problem: str
    steps: list[Step]
    final_answer: str
    confidence: float | None = None

    def format(self) -> str:
        """Format the structured output for human-readable display."""
        steps_output = "\n".join(
            f"Step {i + 1}: {step.explanation}\n  Output: {step.output}" for i, step in enumerate(self.steps)
        )
        confidence_str = f" (Confidence: {self.confidence})" if self.confidence else ""
        return f"Problem: {self.problem}\n\n{steps_output}\n\nFinal Answer: {self.final_answer}{confidence_str}"

print("Pydantic models defined:")
print(f"- Step: {Step.model_json_schema()}")
print(f"- ProblemSolution: {ProblemSolution.model_json_schema()}")
# Configure Bedrock V2 with structured outputs

llm_config_v2_structured = LLMConfig(
    config_list={
        "api_type": "bedrock_v2",
        "model": "qwen.qwen3-coder-480b-a35b-v1:0",
        "aws_region": os.getenv("AWS_REGION", "eu-north-1"),
        "aws_access_key": os.getenv("AWS_ACCESS_KEY"),
        "aws_secret_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
        "total_max_attempts": 8,
        "mode": "adaptive",  # Retries with client-side throttling
        "response_format": ProblemSolution,
    },
)

# Create agent with structured outputs
math_agent = ConversableAgent(
    name="math_assistant",
    llm_config=llm_config_v2_structured,
    system_message="You are a helpful math assistant that solves problems step by step. Always show your reasoning process clearly.",
    max_consecutive_auto_reply=1,
    human_input_mode="NEVER",
)

print("Agent created with Bedrock V2 and structured outputs!")
# Test the agent with a math problem
print("=== Solving Math Problem with Structured Output ===")

result = math_agent.run(
    message="Solve the equation: 3x + 7 = 22. Show all steps.",
    max_turns=1,
).process()

print("\nResponse received!")

Part 5: V1 vs V2 Client Comparison#

Let’s create agents with both V1 and V2 clients to see the difference:

# Create agents with different client versions
agent_v2 = ConversableAgent(
    name="agent_v2",
    llm_config=llm_config_v2,
    system_message="You are a helpful assistant using V2 client architecture.",
    max_consecutive_auto_reply=1,
    human_input_mode="NEVER",
)

agent_v1 = ConversableAgent(
    name="agent_v1",
    llm_config=llm_config_v1,
    system_message="You are a helpful assistant using V1 client architecture.",
    max_consecutive_auto_reply=1,
    human_input_mode="NEVER",
)

print("Agents created with V1 and V2 clients!")
# Test both agents with the same question
question = "What are the three main benefits of renewable energy?"

print("=== V2 Client Response ===")
result_v2 = agent_v2.run(message=question, max_turns=1).process()

print("\n=== V1 Client Response ===")
result_v1 = agent_v1.run(message=question, max_turns=1).process()

print("\nBoth clients work seamlessly with the same interface!")

Part 6: Group Chat with Mixed V1/V2 Bedrock Clients#

Now let’s create a group chat where agents use different Bedrock client versions. This demonstrates backward compatibility:

from autogen import GroupChat, GroupChatManager

# Planner agent - uses V2 client
planner = ConversableAgent(
    name="planner_agent",
    llm_config=llm_config_v2,
    system_message="Create detailed project plans. Break down tasks into clear steps.",
    description="Creates project plans",
)

# Reviewer agent - uses V1 client (demonstrates compatibility)
reviewer = ConversableAgent(
    name="reviewer_agent",
    llm_config=llm_config_v1,
    system_message="Review plans and provide constructive feedback. Keep reviews concise.",
    description="Reviews plans",
)

# Coordinator agent - uses V2 client
coordinator = ConversableAgent(
    name="coordinator_agent",
    llm_config=llm_config_v2,
    system_message="Coordinate between planner and reviewer. Say DONE! when the plan is finalized.",
    description="Coordinates the planning process",
)

# Setup group chat
groupchat = GroupChat(
    agents=[coordinator, planner, reviewer],
    speaker_selection_method="auto",
    messages=[],
)

# Create manager with V2 client
manager = GroupChatManager(
    name="group_manager",
    groupchat=groupchat,
    llm_config=llm_config_v2,
    is_termination_msg=lambda x: "DONE!" in (x.get("content", "") or "").upper(),
)

print("Group chat created with mixed V1/V2 Bedrock clients!")
# Start the conversation
print("=== Starting Group Chat ===")
chat_result = coordinator.initiate_chat(
    recipient=manager,
    message="Let's create a plan for organizing a tech conference.",
)

print("\n=== Chat History ===")
for msg in chat_result.chat_history:
    print(
        f"\n[{msg.get('role', 'unknown')}]: {msg.get('content', '')[:200]}..."
        if len(msg.get("content", "")) > 200
        else f"\n[{msg.get('role', 'unknown')}]: {msg.get('content', '')}"
    )

Part 7: Advanced Group Chat with Structured Outputs#

Let’s create a more sophisticated group chat where the orchestrator uses structured outputs for routing decisions:

# Define structured output models for orchestration
class TaskDetails(BaseModel):
    """Details about the task being processed."""

    task_type: str
    description: str
    priority: str | None = None
    requirements: list[str] = []

class RoutingDecision(BaseModel):
    """Structured routing decision from the orchestrator."""

    request_analysis: str
    task_details: TaskDetails
    selected_agent: str
    routing_reason: str
    expected_outcome: str
    next_steps: list[str] = []

    def format(self) -> str:
        """Format the structured output for human-readable display."""
        output = "🎯 Routing Decision\n"
        output += f"{'=' * 60}\n\n"
        output += f"Request Analysis:\n{self.request_analysis}\n\n"
        output += f"Task Type: {self.task_details.task_type}\n"
        output += f"Description: {self.task_details.description}\n\n"
        output += "Routing Decision:\n"
        output += f"  β†’ Selected Agent: {self.selected_agent}\n"
        output += f"  β†’ Reason: {self.routing_reason}\n"
        output += f"  β†’ Expected Outcome: {self.expected_outcome}\n"
        if self.next_steps:
            output += "\nNext Steps:\n"
            for i, step in enumerate(self.next_steps, 1):
                output += f"  {i}. {step}\n"
        return output

print("Structured output models defined for orchestration!")
# Configure orchestrator with structured outputs
orchestrator_llm_config = LLMConfig(
    config_list=[
        {
            "api_type": "bedrock_v2",  # V2 client with structured outputs
            "model": "aqwen.qwen3-coder-480b-a35b-v1:0",
            "aws_region": os.getenv("AWS_REGION", "eu-north-1"),
            "aws_access_key": os.getenv("AWS_ACCESS_KEY"),
            "aws_secret_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
            "response_format": RoutingDecision,  # Structured output for routing
        }
    ],
    temperature=0.3,
)

# Regular config for other agents
regular_llm_config = LLMConfig(
    config_list=[
        {
            "api_type": "bedrock_v2",  # V2 client without structured outputs
            "model": "aqwen.qwen3-coder-480b-a35b-v1:0",
            "aws_region": os.getenv("AWS_REGION", "eu-north-1"),
            "aws_access_key": os.getenv("AWS_ACCESS_KEY"),
            "aws_secret_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
        }
    ],
    temperature=0.3,
)

print("LLM configurations created!")
from autogen import UserProxyAgent
from autogen.agentchat import initiate_group_chat
from autogen.agentchat.group.patterns.auto import AutoPattern

# Create orchestrator agent with structured outputs
orchestrator = ConversableAgent(
    name="pipeline_orchestrator",
    system_message="""🎯 You are the Pipeline Orchestrator. Your role is to:
    β€’ Analyze user requests and determine the workflow path
    β€’ Route tasks to appropriate specialized agents
    β€’ Monitor pipeline progress and coordinate handoffs
    β€’ Report final results to the user

    You MUST provide structured routing decisions that include:
    - Analysis of the user's request
    - Task type and details
    - Selected agent and reasoning
    - Expected outcome and next steps

    Workflow Decision Logic:
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ 1. Research task? β†’ Route to researcher                  β”‚
    β”‚ 2. Writing task? β†’ Route to writer                       β”‚
    β”‚ 3. Analysis task? β†’ Route to analyst                     β”‚
    β”‚ 4. Planning task? β†’ Route to planner                     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    Always provide clear routing decisions with reasoning.""",
    llm_config=orchestrator_llm_config,
)

# Create specialized agents
researcher = ConversableAgent(
    name="researcher",
    system_message="""πŸ” You are a Researcher. Your role is to:
    β€’ Conduct thorough research on given topics
    β€’ Gather relevant information from multiple sources
    β€’ Provide well-sourced findings
    β€’ Summarize key points clearly
    Always cite your sources and provide comprehensive research.""",
    llm_config=regular_llm_config,
)

writer = ConversableAgent(
    name="writer",
    system_message="""✍️ You are a Writer. Your role is to:
    β€’ Create well-structured written content
    β€’ Follow style guidelines and best practices
    β€’ Ensure clarity and coherence
    β€’ Adapt tone and style to the audience
    Write engaging, clear, and professional content.""",
    llm_config=regular_llm_config,
)

analyst = ConversableAgent(
    name="analyst",
    system_message="""πŸ“Š You are an Analyst. Your role is to:
    β€’ Analyze data and information systematically
    β€’ Identify patterns and trends
    β€’ Provide insights and recommendations
    β€’ Support conclusions with evidence
    Provide thorough, data-driven analysis.""",
    llm_config=regular_llm_config,
)

planner_agent = ConversableAgent(
    name="planner",
    system_message="""πŸ“‹ You are a Planner. Your role is to:
    β€’ Create detailed plans and roadmaps
    β€’ Break down complex tasks into steps
    β€’ Identify dependencies and timelines
    β€’ Anticipate potential issues
    Create comprehensive, actionable plans.""",
    llm_config=regular_llm_config,
)

# Create user proxy
user = UserProxyAgent(
    name="user",
    human_input_mode="TERMINATE",
    code_execution_config={"work_dir": "coding", "use_docker": False},
)

print("All agents created!")
# Create AutoPattern for groupchat
pattern = AutoPattern(
    initial_agent=orchestrator,
    agents=[
        orchestrator,
        researcher,
        writer,
        analyst,
        planner_agent,
    ],
    user_agent=user,
    group_manager_args={"llm_config": orchestrator_llm_config},
)

print("AutoPattern created with Bedrock V2 agents!")
# Run the group chat
print("=== Starting Group Chat with Bedrock V2 and Structured Outputs ===")

result, context, last_agent = initiate_group_chat(
    pattern=pattern,
    messages="I need help creating a marketing strategy for a new coffee shop. Research the market, analyze competitors, and create a comprehensive plan.",
    max_rounds=8,
)

print("\n=== Final Result ===")
print(result)

Part 8: Accessing Rich Content from V2 Responses#

Let’s demonstrate how to access rich content from V2 client responses in a custom workflow:

# Example: Custom workflow that processes V2 responses
def process_v2_response(response: UnifiedResponse):
    """Process a UnifiedResponse and extract all relevant information."""
    print(f"\n{'=' * 60}")
    print(f"Processing Response from {response.provider.upper()}")
    print(f"{'=' * 60}")

    print(f"\nπŸ“ Model: {response.model}")
    print(f"πŸ†” ID: {response.id}")
    print(f"βœ… Status: {response.status}")
    print(f"🏁 Finish Reason: {response.finish_reason}")

    print(f"\nπŸ’¬ Messages ({len(response.messages)}):")
    for i, msg in enumerate(response.messages, 1):
        print(f"\n  Message {i}:")
        print(f"    Role: {msg.role}")
        print(f"    Content Blocks: {len(msg.content)}")

        # Count content types
        text_blocks = [b for b in msg.content if b.type == "text"]
        tool_blocks = [b for b in msg.content if b.type == "tool_call"]
        image_blocks = [b for b in msg.content if b.type == "image"]

        if text_blocks:
            print(f"    πŸ“„ Text blocks: {len(text_blocks)}")
            print(
                f"       Text: {text_blocks[0].text[:100]}..."
                if len(text_blocks[0].text) > 100
                else f"       Text: {text_blocks[0].text}"
            )

        if tool_blocks:
            print(f"    πŸ”§ Tool calls: {len(tool_blocks)}")
            for tool in tool_blocks:
                print(f"       - {tool.name}")

        if image_blocks:
            print(f"    πŸ–ΌοΈ  Images: {len(image_blocks)}")

    print("\nπŸ“Š Usage:")
    print(f"    Prompt tokens: {response.usage.get('prompt_tokens', 0)}")
    print(f"    Completion tokens: {response.usage.get('completion_tokens', 0)}")
    print(f"    Total tokens: {response.usage.get('total_tokens', 0)}")

    if response.cost:
        print(f"    πŸ’° Cost: ${response.cost:.6f}")

    print("\nπŸ” Provider Metadata:")
    for key, value in response.provider_metadata.items():
        print(f"    {key}: {value}")

# Test the function
test_response = client.create({
    "model": "aqwen.qwen3-coder-480b-a35b-v1:0",
    "messages": [{"role": "user", "content": "List 3 benefits of cloud computing."}],
})

process_v2_response(test_response)

Summary#

In this notebook, we’ve learned:

  1. βœ… How to configure and use Bedrock V2 client (api_type: "bedrock_v2")
  2. βœ… How to access rich UnifiedResponse objects with typed content blocks
  3. βœ… How to use structured outputs with Bedrock V2 client
  4. βœ… How V1 and V2 Bedrock clients work together seamlessly
  5. βœ… How to create group chats with mixed client versions
  6. βœ… How to process and extract information from V2 responses
  7. βœ… Advanced patterns like orchestration with structured outputs

Key Takeaways#

  • V2 Client Benefits: Rich content preservation, direct property access, forward compatibility
  • Backward Compatible: V1 and V2 clients can work together in the same conversation
  • Structured Outputs: Combine V2 architecture with structured outputs for powerful workflows
  • Group Chats: Use V2 clients in multi-agent scenarios for better content handling

Next Steps#

  • Experiment with different Bedrock models using V2 client
  • Try multimodal content (images) with Bedrock V2
  • Create custom workflows that leverage rich content blocks
  • Combine V2 clients with other AG2 features like tools and function calling

References#