{
  "source_file": "0930",
  "source_platform": "Ghost",
  "author": "Peng Qian / Data Leads Future",
  "title_listed": "Make Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "工程实践",
  "is_paid_flag": "否",
  "url": "https://www.dataleadsfuture.com/make-microsoft-agent-frameworks-structured-output-work-with-qwen-and-deepseek-models/",
  "publish_date": "2025-12",
  "corpus_stage": "",
  "notes": "",
  "id": "8e4bbcef762e",
  "collected_at": "2026-10-02T12:27:32",
  "title": "Make Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models",
  "body_markdown": "Agentic AI\nMake Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models\nUpdated to the latest version of Microsoft Agent Framework\nPeng Qian\n11 Dec 2025\n—\n8 min read\nShare\nMake Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models. Image by DALL-E-3\nUpdate:\nAfter version\npython-1.0.0b260114\n, MAF made big changes to the\nresponse_format\nrelated APIs, but the official docs haven’t been updated yet. This article is based on version\npython-1.0.0b260127\nand brings you the latest structured output solution.\nIntroduction\nToday, we’ll add some extra features to the Microsoft Agent Framework so that Qwen and DeepSeek can also utilize structured output.\nThe main reason is that Autogen has stayed on version v0.75 for a long time, which makes it necessary to switch to Microsoft Agent Framework soon.\nEvery time we switch the agent framework, we have to make it work with some common LLMs. This time is no exception. Luckily, Microsoft Agent Framework is pretty easy to use. We just need to adapt the structured output feature, and we can use it right away.\nAs usual, I’ll put the source code at the end of the article for you to use.\nBackground On Structured Output\nHow does Agent Framework do structured output?\nIn Microsoft Agent Framework, we set the\nresponse_format\nparameter to a Pydantic\nBaseModel\ndata class to tell the LLM to produce structured output, like this:\nfrom pydantic import BaseModel\n\nclass PersonInfo(BaseModel):\n    \"\"\"Information about a person.\"\"\"\n    name: str | None = None\n    age: int | None = None\n    occupation: str | None = None\n\nresponse = await agent.run(\n    \"Please provide information about John Smith, who is a 35-year-old software engineer.\",\n    options={\n        \"response_format\": PersonInfo\n    },\n)\nThere are two places to set the\nresponse_format\nparameter:\nSet it during the\nChatAgent\ninitialization in\ndefault_options\nparameter. This becomes a global parameter for the agent, and all later communications with OpenAI-compatible models use it.\nSet it when calling\nrun\nor\nrun_stream\nin\noptions\nparameter. This works only for that single API call.\nThe\nresponse_format\nset in\nrun\nor\nrun_stream\nis higher priority than the setting in the\nChatAgent\ncreation. That means the\nresponse_format\nin\nrun\nwill override what was set when creating the\nChatAgent\n.\nThe conversion process of the response_format parameter in Microsoft Agent Framework. Image by Author\nBy default, we use\nOpenAIChatClient\nto call OpenAI’s API. Before the API call, a\n_prepare_options\nmethod converts the\nBaseModel\ninto\n{\"type\": \"json_schema\", \"json_schema\": <base model schema>}\nand passes it to the LLM.\nSo that’s how Agent Framework makes the LLM do structured output. Our extension will go into the\n_prepare_options\nmethod of\nOpenAIChatClient\n.\nDo Qwen and DeepSeek support json_schema settings?\nAccording to the official docs, both Qwen and DeepSeek support structured output. But they only support setting the OpenAI client’s\nresponse_format\nto\n{\"type\": \"json_object\"}\nand require the keyword\njson\nin the prompt to enable structured output. They do not support OpenAI’s API way of setting\nresponse_format\nto\njson_schema\n.\nIf we don’t extend the Microsoft Agent Framework and force\nresponse_format\nto be a\nBaseModel\nclass, we’ll see errors like this:\nError code: 400 - {'error': {'message': \"<400> InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in some form, to use 'response_format' of type 'json_object'.\", 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_parameter_error'}}\nSo for Qwen and DeepSeek, without modifying the Microsoft Agent Framework, we can’t use the structured output feature.\nHow to make Qwen and DeepSeek output using json_schema\nEven though Qwen and DeepSeek don’t support\n{\"type\": \"json_schema\"}\n, we can still inject\njson_schema\ninto the system prompt so the LLM outputs according to our data class.\nThe trick is: before calling the OpenAI API, convert the\nBaseModel\nto its\njson_schema\n, attach it to the system prompt, and send it along.\nIf you want to know exactly how I made Qwen output according to a Pydantic BaseModel’s rules, read my popular article where I explain multiple methods for this:\nBuild AutoGen Agents with Qwen3: Structured Output & Thinking Mode\nSave yourself 40 hours of trial and error\nData Leads Future\nPeng Qian\nHow I Extended It\nNow, let’s see exactly how to extend Microsoft Agent Framework so Qwen and DeepSeek can do structured output.\nI know you want the answer fast, so here’s the modified code you can use right now:\nfrom typing import override, MutableSequence, Any\nfrom textwrap import dedent\nfrom copy import deepcopy\n\nfrom pydantic import BaseModel\nfrom agent_framework.openai import OpenAIChatClient\nfrom agent_framework import ChatMessage, ChatOptions, TextContent\n\nclass OpenAILikeChatClient(OpenAIChatClient):\n    @override\n    def _prepare_options(self, messages: MutableSequence[ChatMessage], options: dict[str, Any]) -> dict[str, Any]:\n        chat_options_copy = deepcopy(options)\n        response_format = chat_options_copy.get(\"response_format\")\n\n        if (\n            response_format\n            and isinstance(response_format, type)\n            and issubclass(response_format, BaseModel)\n        ):\n            structured_output_prompt = self._build_structured_prompt(response_format)\n            if old_instructions := chat_options_copy.get(\"instructions\"):\n                chat_options_copy[\"instructions\"] = f\"{old_instructions}\\n\\n{structured_output_prompt}\"\n            else:\n                messages = [ChatMessage(role=Role.SYSTEM, text=structured_output_prompt), *messages]\n            \n            chat_options_copy[\"response_format\"] = {\"type\": \"json_object\"}\n\n        return super()._prepare_options(messages, chat_options_copy)\n\n    @staticmethod\n    def _build_structured_prompt(response_format: type[BaseModel]) -> str:\n        json_schema = response_format.model_json_schema()\n        structured_output_prompt = dedent(f\"\"\"\n        <output-format>\\n\n        Your output must adhere to the following JSON schema format,\n        without any Markdown syntax, and without any preface or explanation:\\n\n        {json_schema}\\n\n        </output-format>\n        \"\"\")\n\n        return structured_output_prompt\nAs I said before, both\nrun\nand\nrun_stream\ncall\nOpenAIChatClient\n’s\n_prepare_options\nmethod, so it’s the best place to extend.\nI marked each part of the code with numbers in the comments so I can explain in order:\nThe\nchat_options\nobject is the parameters you pass to the method. We need to\ndeepcopy\nit to a new object because we’re going to change\nresponse_format\nto\n{\"type\": \"json_object\"}\nto work with DeepSeek. Agent Framework still needs the original\nBaseModel\nto convert the returned JSON string back to a data class.\nThen we take the\njson_schema\nfrom the\nBaseModel\n, turn it into part of the system prompt, and wrap it with\nxml\ntags.\nThe original\n_prepare_options\nchecks if\nmessages\nis empty. We’ll only handle the case where\nmessages\nis not empty, meaning the user sends at least a user message.\nIf the first message in\nmessages\nis a system message, we attach the structured output prompt to the system message, replacing the old system message.\nIf the first message is a user message, we create a new system message with just the structured output prompt and put it at the front of the\nmessages\nlist.\nWith this change, Microsoft Agent Framework now supports structured output for Qwen and DeepSeek. Next, let’s test some common cases to make sure it works.\nTesting the Extension\nPrepare an MLflow server to observe\nBefore testing, we need a monitoring tool to check the messages Agent Framework sends to the LLM API.\nAgent Framework supports logging platforms based on\nopentelemetry\n, but it doesn’t log system messages by default, so that won’t work for our case today.\nAgent Framework's OpenTelemetry output doesn't log the system message used when calling the LLM. Image by Author\nIn a previous article, I showed how I use MLflow to see the messages sent to OpenAI’s API:\nMonitoring Qwen 3 Agents with MLflow 3.x: End-to-End Tracing Tutorial\nEnhance your multi-agent application’s observability, explainability and Traceability\nData Leads Future\nPeng Qian\nSo today we’ll still use MLflow’s\nopenai.autolog\nAPI, because it can record system messages sent to the LLM.\nYou just need to start a\nserver\nlike this:\nmlflow server --host 0.0.0.0 --port 5000\nThen in the test code, add a call to\nopenai.autolog\n:\nmlflow.set_tracking_uri(os.environ.get(\"MLFLOW_TRACKING_URI\"))\nmlflow.set_experiment(\"Default\")\nmlflow.openai.autolog()\nTest single-turn conversation\nFirst, let’s follow the official docs to test normal structured output.\nSet up a data class, then set it in the\nrun\nmethod:\nclass PersonInfo(BaseModel):\n    \"\"\"Information about a person.\"\"\"\n    name: str | None = None\n    age: int | None = None\n    occupation: str | None = None\n\n\nasync def main():\n    response = await agent.run(\n        \"Please provide information about John Smith, who is a 35-year-old software engineer.\",\n        options={\n            \"response_format\": PersonInfo\n        },\n    )\n    print(response.text)\nCheck on MLflow:\nThe json_schema prompt has already been appended to the system prompt. Image by Author\nWe can see the data class has been turned into a\njson_schema\nprompt, attached to the system prompt. Also, we can get the structured object directly through\nresponse.value\n.\nTest multi-turn conversation\nNow let’s test Microsoft Agent Framework’s multi-turn example.\nFirst, set a\nresponse_format\nat\ncreate_agent\n, without setting it in\nrun\n:\nclass OutText(BaseModel):\n    output: str\n\nagent = client.create_agent(\n    instructions=\"You are a good assistant.\",\n    name=\"assistant\",\n    default_options={\n        \"response_format\": OutText\n    },\n)\n\nasync def main():\n    result1 = await agent.run(\n        \"How many kilometers is the highway from Wuhan to Beijing?\",\n        thread=thread,\n    )\n    print(result1.text)\nThen use\nrun_stream\nfor the second turn and set another\nresponse_format\n:\nclass ETA(BaseModel):\n    hours: int\n\nfinal_response = await AgentRunResponse.from_agent_response_generator(\n    agent.run_stream(\n        \"How long would it take to drive there at 120 km/h?\",\n        thread=thread,\n        options={\n            \"response_format\": ETA\n        },\n    ),\n    output_format_type=ETA\n)\nprint(final_response.value)\nCheck on MLflow:\nThe first round of conversation used the default response_format parameter. Image by Author\nThe second round of conversation switched to the response_format parameter passed into the run_stream method. Image by Author\nNo problems at all. The\nresponse_format\nin\nrun_stream\noverrides the one set in\ncreate_agent\nas expected.\nConclusion\nWith Autogen no longer updated, we’ve started moving to Microsoft Agent Framework.\nDuring this migration, we extended the Microsoft Agent Framework so Qwen and DeepSeek can use structured output.\nI hope Qwen and DeepSeek’s APIs will one day support setting\nresponse_format\nto\n{\"type\": \"json_schema\"}\ndirectly, so we wouldn’t have to adapt the framework every time we switch.\nStructured output is just about adding a\njson_schema\ndescription in the system prompt so the LLM outputs content as we define. So even if you’re not using Microsoft Agent Framework, you can modify things in a similar way.\nThat’s it for today’s journey. If you find this tutorial useful, please share it with your friends.\nHere’s the source code for today’s tutorial:\nagentic-ai-playground/10_Agent_Framework_Qwen3_DeepSeek at main · qtalen/agentic-ai-playground\nContribute to qtalen/agentic-ai-playground development by creating an account on GitHub.\nGitHub\nqtalen\nFollow Data Leads Future\nOne practical story every month, sharing my hard-learned experiences in the enterprise AI space.\nSubscribe\nEmail sent! Check your inbox to complete your signup.\nYou can unsubscribe anytime.",
  "metadata": {
    "title": "Make Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models",
    "description": "Learn how to extend the Microsoft Agent Framework so Qwen and DeepSeek models can use structured output, with step-by-step tips and code examples.",
    "keywords": null,
    "author": null,
    "og:site_name": "Data Leads Future",
    "og:type": "article",
    "og:title": "Make Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models",
    "og:description": "Updated to the latest version of Microsoft Agent Framework",
    "og:url": "https://www.dataleadsfuture.com/make-microsoft-agent-frameworks-structured-output-work-with-qwen-and-deepseek-models/",
    "og:image": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/size/w1200/2025/12/Agent_Framework_cover.webp",
    "og:image:width": "1200",
    "og:image:height": "686",
    "twitter:card": "summary_large_image",
    "twitter:title": "Make Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models",
    "twitter:description": "Updated to the latest version of Microsoft Agent Framework",
    "twitter:url": "https://www.dataleadsfuture.com/make-microsoft-agent-frameworks-structured-output-work-with-qwen-and-deepseek-models/",
    "twitter:image": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/size/w1200/2025/12/Agent_Framework_cover.webp",
    "twitter:label1": "Written by",
    "twitter:data1": "Peng Qian",
    "twitter:label2": "Filed under",
    "twitter:data2": "Agentic AI, Generative AI, Microsoft Agent Framework",
    "twitter:site": "@qtalen",
    "twitter:creator": "@qtalen",
    "article:published_time": "2025-12-11T12:05:57.000Z",
    "article:modified_time": "2026-04-22T08:20:29.000Z",
    "article:tag": "Microsoft Agent Framework"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/8e4bbcef762e/code/body-000.txt",
      "chars": 369,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-001.txt",
      "chars": 271,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-002.txt",
      "chars": 1717,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-003.txt",
      "chars": 40,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-004.txt",
      "chars": 119,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-005.txt",
      "chars": 405,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-006.txt",
      "chars": 377,
      "language": "text"
    },
    {
      "file": "attachments/8e4bbcef762e/code/body-007.txt",
      "chars": 343,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/size/w1200/2025/12/Agent_Framework_cover.webp",
      "bytes": 127894,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/000.webp",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/size/w1200/2025/12/Agent_Framework_cover.webp",
      "alt": "Make Microsoft Agent Framework’s Structured Output Work With Qwen and DeepSeek Models. ",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-4.png",
      "bytes": 69459,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/005.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-4.png",
      "alt": "The conversion process of the response_format parameter in Microsoft Agent Framework. ",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/icon/color_192_192-49.png",
      "bytes": 17721,
      "reused": false,
      "local_path": "attachments/8e4bbcef762e/images/body-fdfc56fdffe043c90cb4.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/icon/color_192_192-49.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/thumbnail/structured_output-1.webp",
      "bytes": 81226,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/007.webp",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/thumbnail/structured_output-1.webp",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-5.png",
      "bytes": 115472,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/008.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-5.png",
      "alt": "Agent Framework's OpenTelemetry output doesn't log the system message used when calling the LLM.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/icon/color_192_192-50.png",
      "bytes": 17721,
      "reused": false,
      "local_path": "attachments/8e4bbcef762e/images/body-e0075129a304df249095.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/icon/color_192_192-50.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/thumbnail/Tracing-with-MLflow-3.x-1.webp",
      "bytes": 99038,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/011.webp",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/thumbnail/Tracing-with-MLflow-3.x-1.webp",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-6.png",
      "bytes": 97491,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/012.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-6.png",
      "alt": "The json_schema prompt has already been appended to the system prompt.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-7.png",
      "bytes": 71164,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/014.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-7.png",
      "alt": "The first round of conversation used the default response_format parameter.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-8.png",
      "bytes": 89215,
      "reused": true,
      "local_path": "attachments/8e4bbcef762e/images/016.png",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/2025/12/image-8.png",
      "alt": "The second round of conversation switched to the response_format parameter passed into the run_stream method.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/icon/pinned-octocat-093da3e6fa40-22.svg",
      "bytes": 1009,
      "reused": false,
      "local_path": "attachments/8e4bbcef762e/images/body-39a02096cbb45698665b.svg",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/icon/pinned-octocat-093da3e6fa40-22.svg",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/thumbnail/agentic-ai-playground-20",
      "bytes": 100018,
      "reused": false,
      "local_path": "attachments/8e4bbcef762e/images/body-7982507f89e90ec70db3.img",
      "src": "https://storage.ghost.io/c/33/67/33678c00-2c15-4961-93e9-497b427e2006/content/images/thumbnail/agentic-ai-playground-20",
      "alt": "",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://www.dataleadsfuture.com/tag/agentic-ai/",
      "label": "Agentic AI",
      "type": "a"
    },
    {
      "href": "https://www.dataleadsfuture.com/author/peng/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://www.dataleadsfuture.com/build-autogen-agents-with-qwen3-structured-output-thinking-mode/",
      "label": "Build AutoGen Agents with Qwen3: Structured Output & Thinking Mode Save yourself 40 hours of trial and error Data Leads ",
      "type": "a"
    },
    {
      "href": "https://www.dataleadsfuture.com/monitoring-qwen-3-agents-with-mlflow-3-x-end-to-end-tracking-tutorial/",
      "label": "Monitoring Qwen 3 Agents with MLflow 3.x: End-to-End Tracing Tutorial Enhance your multi-agent application’s observabili",
      "type": "a"
    },
    {
      "href": "https://github.com/qtalen/agentic-ai-playground/tree/main/10_Agent_Framework_Qwen3_DeepSeek",
      "label": "agentic-ai-playground/10_Agent_Framework_Qwen3_DeepSeek at main · qtalen/agentic-ai-playground Contribute to qtalen/agen",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:47:48Z",
  "source_url": "https://www.dataleadsfuture.com/make-microsoft-agent-frameworks-structured-output-work-with-qwen-and-deepseek-models/",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 11940,
  "expected_body_images": 12,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/8e4bbcef762e/pages/source.html",
  "local_html": "attachments/8e4bbcef762e/pages/article.html",
  "body_base_path": "attachments/8e4bbcef762e/pages/",
  "body_markdown_path": "attachments/8e4bbcef762e/pages/article.md"
}