Amazon Bedrock V2 Client with AG2#
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 boto3package: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#
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:
- β
How to configure and use Bedrock V2 client (
api_type: "bedrock_v2") - β
How to access rich
UnifiedResponseobjects with typed content blocks - β How to use structured outputs with Bedrock V2 client
- β How V1 and V2 Bedrock clients work together seamlessly
- β How to create group chats with mixed client versions
- β How to process and extract information from V2 responses
- β 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