Introduction
As part of the Intelligent Product Recommendation (IPR) team at SAP, we have been using Microsoft AutoGen to implement agentic workflows — specifically the reflection pattern — on top of SAP AI Core. If you have read the earlier post Microsoft AutoGen v0.6.1 with SAP AI Core, you already know how we integrated AutoGen using AzureOpenAIChatCompletionClient and the gen_ai_hub_sdk to obtain tokens and deployment URLs.
Microsoft has now released the Microsoft Agent Framework (MAF). MAF is designed and maintained by the same teams and combines AutoGen's simple multi-agent abstractions with Semantic Kernel's enterprise features: session-based state management, type safety, middleware, telemetry, and graph-based workflows. In short, MAF is the next generation of AutoGen.
This post documents exactly what changed when we migrated our production codebase from AutoGen to MAF, and why the SAP community building agentic solutions on AI Core using AutoGen should consider making the same move.
Why Migrate from AutoGen to MAF?
Here is a quick comparison of the key differences:
|
AutoGen |
Microsoft Agent Framework (MAF) |
|
autogen_agentchat, autogen_core packages |
agent_framework, agent_framework.orchestrations packages |
|
AssistantAgent with model_client=, system_message= |
Agent with client=, instructions= |
|
RoundRobinGroupChat for multi-agent |
GroupChatBuilder with pluggable selection and termination |
|
Manual tool wrapper classes + manual schema registration |
Plain @tool-decorated functions, schema auto-generated from type hints |
|
No built-in context window management |
ContextWindowCompactionStrategy, SlidingWindowStrategy |
|
No session state across turns |
AgentSession for multi-turn conversation state |
|
No middleware layer |
AgentMiddleware for logging, retry, rate-limiting |
|
autogen_core.models types (UserMessage, AssistantMessage …) |
Plain dicts / ChatResponse from agent_framework |
Framework-Level Concept Mapping
Before looking at code, here is the one-to-one concept mapping:
|
AutoGen Concept |
MAF Equivalent |
|
AssistantAgent |
Agent |
|
model_client= |
client= |
|
system_message= |
instructions= |
|
RoundRobinGroupChat |
GroupChatBuilder |
|
TextMentionTermination |
Custom termination predicate (lambda / function) |
|
FunctionTool + wrapper class |
@tool decorator on a plain async function |
|
TaskResult.messages[-1].content |
AgentResponse.text |
|
team.run(task=question) |
workflow.run(question) |
|
autogen_core.models.UserMessage |
dict with role/content keys |
|
ModelInfo(supports_functions=True) |
dict {“supports_functions”: True} |
Key Code Changes
1. Agent Constructor
The AssistantAgent constructor is replaced by Agent with renamed parameters:
Before (AutoGen):
from autogen_agentchat.agents import AssistantAgent
weather_agent = AssistantAgent(
name="weather_agent",
description="Fetches weather information",
model_client=model_client,
tools=[get_weather],
system_message="You are a weather assistant."
)
After (MAF):
from agent_framework import Agent
weather_agent = Agent(
client=model_client, # model_client= -> client=
name="weather_agent",
instructions="You are a weather assistant.", # system_message= -> instructions=
tools=[get_weather] # description= dropped (not supported in MAF)
)
2. Tool Definition — Wrapper Class to @tool Decorator
AutoGen required a wrapper class that held state and manually registered the JSON schema on the model client. MAF replaces this with a plain @tool-decorated async function — no wrapper class, no manual schema registration.
Before (AutoGen):
from autogen_agentchat.tools import FunctionTool
class WeatherToolWrapper:
def __init__(self, units: str = "metric"):
self.units = units
async def get_weather(self, city: str) -> str:
return f"The weather in {city} is 73 degrees and Sunny ({self.units})."
weather_wrapper = WeatherToolWrapper(units="imperial")
tools = [FunctionTool(weather_wrapper.get_weather, description="Get the weather for a city.")]
self._register_tool_on_client(weather_wrapper) # manual schema mutation
After (MAF):
from agent_framework import tool
@tool
async def get_weather(city: str) -> str:
"""Get the current weather for a given city."""
return f"The weather in {city} is 73 degrees and Sunny."
tools = [get_weather]
# No wrapper class needed. Schema is auto-generated from type hints and docstring.
3. Multi-Agent Group Chat — RoundRobinGroupChat to GroupChatBuilder
The RoundRobinGroupChat with TextMentionTermination is replaced by GroupChatBuilder, which accepts explicit selection and termination functions.
Before (AutoGen):
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.conditions import TextMentionTermination
weather_agent = AssistantAgent(
name="weather_agent", model_client=model_client,
tools=[get_weather],
system_message="You are a weather assistant. Fetch weather data using the tool."
)
reviewer_agent = AssistantAgent(
name="reviewer_agent", model_client=model_client,
system_message="Review the weather response. If complete, reply DONE."
)
team = RoundRobinGroupChat(
[weather_agent, reviewer_agent],
max_turns=4,
termination_condition=TextMentionTermination("DONE")
)
result = await team.run(task="What is the weather in New York and London?")
final_text = result.messages[-1].content
After (MAF):
from agent_framework import Agent
from agent_framework_orchestrations import GroupChatBuilder, GroupChatState
from agent_framework import AgentResponse
weather_agent = Agent(
client=model_client, name="weather_agent",
instructions="You are a weather assistant. Fetch weather data using the tool.",
tools=[get_weather]
)
reviewer_agent = Agent(
client=model_client, name="reviewer_agent",
instructions="Review the weather response. If complete, reply DONE."
)
def _round_robin_selection(state: GroupChatState) -> str:
names = list(state.participants.keys())
return names[state.current_round % len(names)]
def _review_approved(messages) -> bool:
return any(
"DONE" in m.text
for m in messages
if getattr(m, "author_name", "") == "reviewer_agent"
)
workflow = GroupChatBuilder(
participants=[weather_agent, reviewer_agent],
selection_func=_round_robin_selection,
termination_condition=_review_approved,
max_rounds=4,
intermediate_output_from=[weather_agent],
).build()
run_result = await workflow.run("What is the weather in New York and London?")
final_text = ""
for event in run_result:
if event.type == "intermediate":
response = event.data
if isinstance(response, AgentResponse):
candidate = " ".join(m.text for m in response.messages if m.text).strip()
if candidate:
final_text = candidate
4. Single-Agent run() Call
Before (AutoGen):
response = await weather_agent.run(task="What is the weather in Paris?")
return response.messages[-1].content
After (MAF):
response = await weather_agent.run("What is the weather in Paris?")
return response.text
5. Custom LLM Client Adapter (SAP AI Core Integration)
Because we use a custom AICoreLLMClient (backed by gen_ai_hub_sdk) instead of a direct Azure OpenAI client, the adapter had to be updated. AutoGen expected a create() method returning a CreateResult object. MAF expects a get_response() method returning a ChatResponse.
Before (AutoGen):
async def create(self, messages, tools=None, **kwargs) -> CreateResult:
resp = await asyncio.to_thread(self.generate_response, messages)
return self._build_create_result(resp)
def capabilities(self) -> ModelInfo:
return ModelInfo(supports_functions=True, supports_vision=self.support_images)
After (MAF):
async def get_response(self, messages, *, stream=False, options=None, ...) -> ChatResponse:
from agent_framework import ChatResponse, Message
# extract instructions and tools from options, then run tool-call loop
for iteration in range(max_tool_iterations):
llm_resp = await asyncio.to_thread(self.generate_response, loop_messages)
if not llm_resp.has_tool_calls:
return ChatResponse(messages=[Message("assistant", [content_text])], ...)
# execute tools, append results, continue loop
...
def capabilities(self) -> dict:
return {"supports_functions": True, "supports_vision": self.support_images}
6. Dependency Changes
Update your requirements.txt or pyproject.toml as follows.
Remove:
autogen-core
autogen-agentchat
Add:
agent-framework-core
Future Works — MAF Features that could be used to enhance Agentic Systems
MAF (agent_framework==1.9.0) ships with several capabilities we have identified as high-value but have not yet wired into the codebase. Here is the prioritised roadmap:
Feature 1 — create_harness_agent (Low Risk, High Value)
Replace the bare Agent(…) constructor with create_harness_agent(). This single call automatically enables two-phase context window compaction and per-LLM-call history persistence with zero behavioural change to the existing API.
Feature 2 — AgentSession for Multi-Turn State (Low Risk)
AgentSession maintains conversation history across multiple run() calls on the same agent. Without it, every run() is stateless — the agent has no memory of previous turns. With AgentSession, prior context is preserved across calls within the same request.
Feature 3 — TodoProvider for Partial-Result Recovery (Medium Risk)
TodoProvider gives the agent five built-in tools (todos_add, todos_complete, todos_get_remaining, etc.) to checkpoint progress within a long task. If the request times out or the process restarts, a resumed call continues from the last open todo rather than starting from scratch.
Feature 4 — BackgroundAgentsProvider for Parallel Sub-Tasks (Medium-High Risk)
BackgroundAgentsProvider lets a parent orchestrator agent dispatch multiple sub-tasks to background sub-agents in parallel (non-blocking). For example, a coordinator agent could fetch weather for 10 cities simultaneously rather than sequentially — reducing total latency from 10x to roughly 1x (limited by the slowest sub-task).
Feature 5 — AgentMiddleware for Cross-Cutting Concerns (Medium Risk)
AgentMiddleware intercepts every agent.run() call, enabling request/response logging, automatic retry on failure, and token budget enforcement — without any changes to the agent code itself.
Feature 6 — LocalEvaluator for CI/CD Quality Gates (Low Risk)
LocalEvaluator and evaluate_agent provide a built-in evaluation framework. Adding a keyword_check(“degrees”) check ensures every weather response contains temperature data; tool_called_check(“get_weather”) verifies the agent always calls the tool instead of hallucinating. These can be added to the existing test suite as a CI gate with no changes to production code.
Conclusion
Migrating from AutoGen to MAF required targeted changes to three core areas:
- Agent construction — AssistantAgent replaced by Agent with renamed parameters
- Tool registration — wrapper class and manual schema replaced by @tool decorator
- Multi-agent orchestration — RoundRobinGroupChat replaced by GroupChatBuilder
The migration also gave us a cleaner LLM client adapter (get_response instead of create), removed all AutoGen type dependencies from message handling, and set the codebase up for the richer MAF features described in the Future Works section above.
If your team is already using AutoGen on SAP AI Core, the migration is straightforward — the concept mapping is one-to-one and the packages are drop-in replacements. MAF gives you a production-grade foundation (context management, middleware, evaluation) that AutoGen simply did not have.
Elevate with AI !!
References:
Microsoft Agentic Framework: https://learn.microsoft.com/en-us/agent-framework/overview/?pivots=programming-language-python
Migration Guide: https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-autogen/



