Skip to main content
RapidDev - Software Development Agency
build-ai-agent

Building an AI Agent with AutoGen (Now Microsoft Agent Framework)

AutoGen's last release was September 2025; Microsoft Agent Framework, built by the same AutoGen and Semantic Kernel teams, is now the official successor. This guide builds with Agent Framework from the start: define tools with @tool, gate one behind approval_mode='always_require', and resolve it through a session-based approval loop. The example is a procurement agent that checks vendor contracts freely and only approves a purchase order once a human signs off. Budget 45-60 minutes.

What you'll learn

  • Why AutoGen projects should move to Microsoft Agent Framework, and what maps to what
  • Define tools with @tool and mark one approval_mode='always_require' so it never runs unsupervised
  • Run a session-based approval loop with create_session(), user_input_requests, and to_function_approval_response()
  • Recognize when a Workflow with request_info() replaces what AutoGen's Team and GroupChat used to do
Book a free consultation
4.9Clutch rating ⭐
1,000+Happy partners
20+Countries served
200+Team members
Intermediate11 min read45-60 minPython 3.10+, agent-framework + agent-framework-anthropic, an Anthropic API key, any OSLast updated November 2026RapidDev Engineering Team
TL;DR

AutoGen's last release was September 2025; Microsoft Agent Framework, built by the same AutoGen and Semantic Kernel teams, is now the official successor. This guide builds with Agent Framework from the start: define tools with @tool, gate one behind approval_mode='always_require', and resolve it through a session-based approval loop. The example is a procurement agent that checks vendor contracts freely and only approves a purchase order once a human signs off. Budget 45-60 minutes.

Quick facts about this guide
FactValue
ToolMicrosoft Agent Framework (AutoGen)
DifficultyIntermediate
Time required45-60 min
CompatibilityPython 3.10+, agent-framework + agent-framework-anthropic, an Anthropic API key, any OS
Last updatedNovember 2026

What you'll build, and why not AutoGen

If you have an existing AutoGen project: AutoGen pioneered GroupChat and an event-driven agent runtime, and it's a fine piece of research software, but its last release was September 2025. Microsoft Agent Framework is its official successor, built by the same AutoGen and Semantic Kernel teams at Microsoft, and it's the one getting new features and fixes going forward. This guide builds directly with Agent Framework rather than teaching a framework that's no longer actively developed.

You'll build a procurement agent. It can look up a vendor's contract terms and current spend through a look_up_vendor_contract tool, and when a purchase order needs sign-off, it can approve one through approve_purchase_order. The contract lookup runs freely; the purchase-order tool is marked approval_mode='always_require', so the agent's run pauses and returns a pending approval request instead of executing it, no matter how confident the model is.

The core pieces map closely from AutoGen if you already know it: AssistantAgent becomes Agent, FunctionTool becomes the @tool decorator with schemas inferred from type hints, and model clients keep similar names (OpenAIChatCompletionClient exists in both; Agent Framework adds a first-party AnthropicClient and a Responses API client AutoGen never had). The biggest behavioral difference is tool-calling itself: AssistantAgent is single-turn unless you raise max_tool_iterations, while Agent is multi-turn by default and keeps calling tools until it has a real answer. For multi-agent orchestration, AutoGen's Team and GroupChat become a typed, graph-based Workflow, and AutoGen's lack of any built-in pause-for-human-input mechanism becomes Workflow's request_info() and @response_handler, which is a capability AutoGen's Team never had at all.

Prerequisites

  • Python 3.10+ and a terminal
  • An Anthropic API key (ANTHROPIC_API_KEY)
  • pip install agent-framework agent-framework-anthropic --pre
  • Basic comfort with async/await and Python type hints

Step-by-step guide

1

Install Microsoft Agent Framework and set your key

Install the core agent-framework package plus the Anthropic provider package, which is still pre-release so it needs the --pre flag. If you're coming from AutoGen, this replaces autogen-agentchat and autogen-ext; there's no in-place upgrade, it's a new install alongside your old one while you migrate incrementally.

typescript
1pip install agent-framework agent-framework-anthropic --pre
2export ANTHROPIC_API_KEY="sk-ant-..."

Expected result: pip install finishes with no errors and `python -c "import agent_framework"` runs silently.

2

Define the read-only tool: look_up_vendor_contract

A plain Python function with type hints becomes a tool when passed to an agent's tools parameter; the @tool decorator lets you set an explicit name and description instead of relying on the function name and docstring. This replaces AutoGen's FunctionTool wrapper, and it needs no approval mode since it only reads data.

typescript
1from typing import Annotated
2from agent_framework import tool
3
4VENDORS = {
5 "VEN-220": {"name": "Acme Supply Co", "terms": "Net 30", "ytd_spend": 48000},
6}
7
8@tool(approval_mode="never_require")
9def look_up_vendor_contract(
10 vendor_id: Annotated[str, "The vendor ID, e.g. VEN-220"],
11) -> str:
12 """Look up a vendor's contract terms and year-to-date spend."""
13 vendor = VENDORS.get(vendor_id)
14 if not vendor:
15 return f"No vendor found for {vendor_id}"
16 return f"{vendor['name']}: terms={vendor['terms']}, ytd_spend=${vendor['ytd_spend']}"

Expected result: A tool function, callable directly for a quick test, that the agent can call on every turn without pausing.

3

Define the sensitive tool: approve_purchase_order

approve_purchase_order takes the same shape but is marked approval_mode='always_require'. The function body itself doesn't check anything about approval; it just performs the action when the framework actually calls it, which only happens after a human has approved the pending request.

typescript
1@tool(approval_mode="always_require")
2def approve_purchase_order(
3 vendor_id: Annotated[str, "The vendor ID"],
4 amount: Annotated[float, "Purchase order amount in dollars"],
5 reason: Annotated[str, "What the purchase order is for"],
6) -> str:
7 """Approve a purchase order for a vendor. Always requires human approval before it runs."""
8 # In production this posts to your procurement/ERP system
9 return f"Approved PO for {vendor_id}: ${amount:.2f} ({reason})"

Expected result: A second tool, same shape as the first, that will only ever execute after a resumed run carrying an approval.

4

Build the Agent with the Anthropic provider

Agent takes a client, instructions, a name, and the tools list. AnthropicClient reads ANTHROPIC_API_KEY from the environment; pass model explicitly rather than relying on a default, since defaults in the SDK's own examples lag behind current model releases. Agent is multi-turn by default, unlike AutoGen's AssistantAgent, so it keeps calling tools across a single run until it has a full answer.

typescript
1from agent_framework import Agent
2from agent_framework.anthropic import AnthropicClient
3
4agent = Agent(
5 client=AnthropicClient(model="claude-sonnet-5-5"),
6 name="ProcurementAgent",
7 instructions=(
8 "You are a procurement agent. Always call look_up_vendor_contract before making any claim "
9 "about a vendor. Never tell someone a purchase order was approved without actually calling "
10 "approve_purchase_order."
11 ),
12 tools=[look_up_vendor_contract, approve_purchase_order],
13)

Expected result: An agent that answers vendor questions on its own and, for anything touching approve_purchase_order, returns a pending approval instead of a final answer.

5

Run the agent and resolve approvals in a loop

Create a session with agent.create_session() and reuse it for every call in this conversation; approval responses only bind to pending requests recorded in the same session. After each run, check result.user_input_requests. For every pending request, inspect its function_call, get a decision, and build a response with to_function_approval_response(True or False). Keep looping until user_input_requests is empty.

typescript
1import asyncio
2
3async def handle_approvals(query: str, agent, session) -> str:
4 result = await agent.run(query, session=session)
5 while result.user_input_requests:
6 new_inputs = []
7 for request in result.user_input_requests:
8 call = request.function_call
9 print(f"[APPROVAL NEEDED] {call.name}({call.arguments})")
10 approved = True # swap for a real Slack/ticket check
11 new_inputs.append({"role": "user", "contents": [request.to_function_approval_response(approved)]})
12 result = await agent.run(new_inputs, session=session)
13 return result.text
14
15async def main():
16 session = agent.create_session()
17 text = await handle_approvals(
18 "VEN-220 needs a $4,200 PO for a replacement HVAC unit. Check their terms and approve it.",
19 agent,
20 session,
21 )
22 print(text)
23
24asyncio.run(main())

Expected result: The script prints an [APPROVAL NEEDED] line for the purchase order, then a final confirmation once the approval resolves.

6

Migrate multi-agent AutoGen patterns to Workflow

If your AutoGen project used GroupChat or RoundRobinGroupChat to coordinate several agents, the Agent Framework equivalent is a graph-based Workflow built with WorkflowBuilder and add_edge, not a direct drop-in. The win is that a Workflow executor can call ctx.request_info() to pause and wait for a human decision mid-graph, something AutoGen's Team abstraction never supported at all; AutoGen human-in-the-loop always required a custom implementation outside the framework.

typescript
1from agent_framework import WorkflowBuilder, WorkflowContext, Executor, handler, response_handler
2from dataclasses import dataclass
3
4@dataclass
5class POApprovalRequest:
6 vendor_id: str
7 amount: float
8
9class FinanceReviewer(Executor):
10 @handler
11 async def review(self, po: POApprovalRequest, ctx: WorkflowContext) -> None:
12 await ctx.request_info(request_data=po, response_type=str)
13
14 @response_handler
15 async def on_decision(self, original: POApprovalRequest, decision: str, ctx: WorkflowContext) -> None:
16 await ctx.yield_output(f"{'APPROVED' if decision == 'approved' else 'REJECTED'}: {original.vendor_id}")
17
18# workflow = WorkflowBuilder(start_executor=procurement_agent_executor).add_edge(
19# procurement_agent_executor, FinanceReviewer(id="finance-reviewer")
20# ).build()

Expected result: A mental model for porting any AutoGen multi-agent flow: single agents migrate directly, coordination logic gets rebuilt as a Workflow.

7

Harden it: real APIs, error handling, and standing approvals at scale

Swap VENDORS and the fake PO approval for real calls to your ERP/procurement API inside each tool, wrapped in try/except so a failure returns a plain error string instead of crashing the process. For teams running this at volume, replace the manual loop in step 5 with create_harness_agent, which installs ToolApprovalMiddleware automatically, supports standing 'always approve this vendor' rules scoped to a session, and queues multiple pending approvals instead of handling them one at a time.

typescript
1@tool(approval_mode="always_require")
2def approve_purchase_order(
3 vendor_id: Annotated[str, "The vendor ID"],
4 amount: Annotated[float, "Purchase order amount in dollars"],
5 reason: Annotated[str, "What the purchase order is for"],
6) -> str:
7 """Approve a purchase order for a vendor. Always requires human approval before it runs."""
8 try:
9 po = erp_client.create_purchase_order(vendor_id=vendor_id, amount=amount, memo=reason)
10 return f"Approved PO {po.id} for {vendor_id}: ${amount:.2f}"
11 except Exception as e:
12 return f"PO creation failed: {e}"

Expected result: A handler that degrades gracefully on a failed ERP call, and a clear upgrade path to create_harness_agent once one-off approvals turn into dozens a day.

Complete working example

procurement_agent.py
1"""Procurement agent built on Microsoft Agent Framework (AutoGen's successor):
2checks vendor contracts freely, approves purchase orders only after human approval.
3Run: python procurement_agent.py
4(pip install agent-framework agent-framework-anthropic --pre; set ANTHROPIC_API_KEY)
5"""
6import asyncio
7from typing import Annotated
8
9from agent_framework import Agent, tool
10from agent_framework.anthropic import AnthropicClient
11
12VENDORS = {
13 "VEN-220": {"name": "Acme Supply Co", "terms": "Net 30", "ytd_spend": 48000},
14}
15
16
17@tool(approval_mode="never_require")
18def look_up_vendor_contract(
19 vendor_id: Annotated[str, "The vendor ID, e.g. VEN-220"],
20) -> str:
21 """Look up a vendor's contract terms and year-to-date spend."""
22 vendor = VENDORS.get(vendor_id)
23 if not vendor:
24 return f"No vendor found for {vendor_id}"
25 return f"{vendor['name']}: terms={vendor['terms']}, ytd_spend=${vendor['ytd_spend']}"
26
27
28@tool(approval_mode="always_require")
29def approve_purchase_order(
30 vendor_id: Annotated[str, "The vendor ID"],
31 amount: Annotated[float, "Purchase order amount in dollars"],
32 reason: Annotated[str, "What the purchase order is for"],
33) -> str:
34 """Approve a purchase order for a vendor. Always requires human approval before it runs."""
35 # In production this posts to your procurement/ERP system
36 return f"Approved PO for {vendor_id}: ${amount:.2f} ({reason})"
37
38
39agent = Agent(
40 client=AnthropicClient(model="claude-sonnet-5-5"),
41 name="ProcurementAgent",
42 instructions=(
43 "You are a procurement agent. Always call look_up_vendor_contract before making any claim "
44 "about a vendor. Never tell someone a purchase order was approved without actually calling "
45 "approve_purchase_order."
46 ),
47 tools=[look_up_vendor_contract, approve_purchase_order],
48)
49
50
51async def handle_approvals(query: str, agent: Agent, session) -> str:
52 result = await agent.run(query, session=session)
53 while result.user_input_requests:
54 new_inputs = []
55 for request in result.user_input_requests:
56 call = request.function_call
57 print(f"\n[APPROVAL NEEDED] {call.name}({call.arguments})")
58 approved = True # swap for a real Slack/ticket check
59 new_inputs.append(
60 {"role": "user", "contents": [request.to_function_approval_response(approved)]}
61 )
62 result = await agent.run(new_inputs, session=session)
63 return result.text
64
65
66async def main():
67 session = agent.create_session()
68 text = await handle_approvals(
69 "VEN-220 needs a $4,200 PO for a replacement HVAC unit. Check their terms and approve it.",
70 agent,
71 session,
72 )
73 print(f"\nAgent: {text}")
74
75
76if __name__ == "__main__":
77 asyncio.run(main())
78

Common mistakes

Why it's a problem: Trying to upgrade an AssistantAgent in place instead of rewriting it as Agent with @tool-decorated functions

How to avoid: There's no automatic migration. Port one agent at a time: replace AssistantAgent(tools=[FunctionTool(...)]) with Agent(tools=[...]) using plain, type-hinted functions, and re-test before moving to the next one.

Why it's a problem: Assuming approval_mode='never_require' is the same as omitting an approval tool entirely

How to avoid: It's an explicit, visible policy on the tool rather than silence. Set it on every read-only tool so a future edit can't accidentally flip a write tool to unsupervised by leaving the parameter off.

Why it's a problem: Reusing a fresh session on every approval round-trip instead of the one from create_session()

How to avoid: Approval responses only bind to pending requests recorded in the same AgentSession by default. A new session makes every approval response silently ignored.

Why it's a problem: Porting AutoGen's GroupChat speaker-selection logic literally into Workflow edges

How to avoid: Model the actual decision points as a graph instead. Workflow is explicit and typed; trying to replicate GroupChat's dynamic speaker selection one-to-one fights the framework.

Why it's a problem: Setting max_tool_iterations-style caps out of habit and being surprised the agent stops early

How to avoid: Agent is multi-turn by default and keeps calling tools until it has an answer, unlike AssistantAgent. If you need a cap, set it explicitly through the client's function-invocation configuration instead of assuming AutoGen's defaults carried over.

Best practices

  • Mark every tool explicitly with approval_mode, even 'never_require' on read-only ones, so the policy is visible in code review.
  • Reuse one AgentSession across an entire approval round-trip; never start a fresh session mid-conversation.
  • State the tool-use rule explicitly in instructions: never claim an action happened without actually calling the tool that performs it.
  • Migrate single agents (AssistantAgent to Agent) before touching multi-agent coordination (Team/GroupChat to Workflow); they're independent migrations.
  • Move to create_harness_agent once approvals happen often enough that a one-off loop stops scaling; it adds queuing and standing-rule support for free.
  • Log every pending approval and its resolution before you build the next run's input, independent of whether it was approved.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm migrating an AutoGen AssistantAgent-based procurement bot to Microsoft Agent Framework. Here's my new Agent with look_up_vendor_contract and approve_purchase_order (approval_mode='always_require') and a session-based approval loop (code below). Add a third tool, flag_duplicate_po, that runs automatically before approve_purchase_order and blocks it if a similar PO was approved in the last 24 hours. [paste complete_code]

Build AI Agents Prompt

In Claude Code, open procurement_agent.py and ask: 'Add a reject_purchase_order tool with the same approval_mode pattern as approve_purchase_order, and update the instructions to explain when the agent should suggest rejection instead of approval.'

Frequently asked questions

Is AutoGen dead, and should I stop building new AutoGen ai agent projects?

AutoGen isn't deleted and existing projects keep running, but its last release was September 2025 and Microsoft has named Agent Framework its official successor, built by the same core team. For any new project, build on Agent Framework directly; it's the one receiving new capabilities.

What's the single biggest behavior change when migrating from AssistantAgent to Agent?

Tool-calling defaults flip. AssistantAgent runs single-turn unless you raise max_tool_iterations, so it often calls one tool and stops. Agent is multi-turn by default and keeps invoking tools until it has a complete answer, which can surprise code that assumed AutoGen's more conservative default.

Does Microsoft Agent Framework support Anthropic's Claude models directly, not just through AutoGen compatibility?

Yes. Install agent-framework-anthropic and use AnthropicClient (or AnthropicFoundryClient, AnthropicBedrockClient, AnthropicVertexClient for those routes) as a first-class chat client, the same way you'd use OpenAIChatClient. This is a direct Agent Framework integration, not something inherited from AutoGen.

What replaces AutoGen's GroupChat for coordinating multiple agents?

A graph-based Workflow built with WorkflowBuilder, where you define executors and the edges between them explicitly. It's more upfront structure than GroupChat's dynamic speaker selection, but it adds request_info()-based human-in-the-loop pauses and checkpoint/resume support that GroupChat never had.

How much does a conversation like this cost in tokens?

On Claude Sonnet 5.5 ($2/M input, $10/M output), a short procurement exchange with one tool call and a modest system prompt runs about $0.01-0.03 per turn uncached, less once your instructions and tool schemas are stable enough to benefit from prompt caching.

Can someone handle this migration for me instead of doing it myself?

Yes. RapidDev migrates AutoGen projects to Microsoft Agent Framework and builds new agents on it directly, including the approval-loop and Workflow pieces most tutorials skip. A free 30-minute consultation is the fastest way to scope your migration.

RapidDev

Talk to an Expert

Our team has built 1,000+ apps. Get personalized help with your project.

Book a free consultation
Matt Graham

Written by

Matt Graham · CEO & Founder, RapidDev

1,000+ client projects delivered. Columbia University & Harvard Business School alumnus, U.S. Navy veteran. About the author →

Learning is great. Shipping is faster with help.

Our engineers have built 1,000+ apps on your platform and the tools around it. If your project needs to be live sooner than your learning curve allows — book a free consultation.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.