Skip to content

Python: feat(foundry): add experimental to_prompt_agent converter#5959

Open
eavanvalkenburg wants to merge 8 commits into
microsoft:mainfrom
eavanvalkenburg:feat/foundry-to-prompt-agent
Open

Python: feat(foundry): add experimental to_prompt_agent converter#5959
eavanvalkenburg wants to merge 8 commits into
microsoft:mainfrom
eavanvalkenburg:feat/foundry-to-prompt-agent

Conversation

@eavanvalkenburg
Copy link
Copy Markdown
Member

Motivation and Context

Developers building agents with Agent Framework's FoundryChatClient have no way today to take that agent and publish it as a Foundry prompt agent without rewriting the definition by hand in azure-ai-projects SDK types — model, instructions, tools, all restated. The same Python tool definitions also can't be reused between the Responses API path (FoundryChatClient local execution) and a prompt agent definition because the two surfaces have different shapes.

This PR adds the missing converter so the same Agent object can either run locally via agent.run(...) or be published as a hosted prompt agent via to_prompt_agent(agent), with the model lifted from the bound FoundryChatClient.

Description

  • Adds to_prompt_agent(agent) -> PromptAgentDefinition in a new agent_framework_foundry._to_prompt_agent module and re-exports it from both agent_framework_foundry and the agent_framework.foundry lazy-loading namespace (including the .pyi stub).
  • Marked experimental with the new ExperimentalFeature.TO_PROMPT_AGENT tag (alphabetically slotted into ExperimentalFeature).
  • agent.client must be a FoundryChatClient (or subclass) — otherwise TypeError. The model is read from the bound client.
  • Tool conversion:
    • Foundry SDK tool instances (anything from FoundryChatClient.get_*_tool() or a literal azure.ai.projects.models.*Tool) are passed through unchanged.
    • AF FunctionTool instances (including @tool-decorated callables) become Foundry FunctionTool declarations. Prompt agents are server-side, so the deployed agent receives the schema but cannot execute the local Python — wiring server-side execution is the caller's responsibility, which the docstring and README call out.
    • Local Agent Framework MCP tools cannot be expressed in a PromptAgentDefinition; the converter raises ValueError and points at FoundryChatClient.get_mcp_tool(...) for hosted MCP servers. The converter walks both agent.default_options["tools"] and agent.mcp_tools to catch local MCP that normalize_tools() split off.
    • Dict-shaped tools with a type discriminator are rehydrated through the SDK Tool model; missing type raises ValueError.
  • Adds a single-file portable-agent sample under samples/02-agents/providers/foundry/foundry_portable_agent.py that drives the same Agent through both agent.run(...) and to_prompt_agent(agent).
  • README gets a "Publishing an agent as a Foundry prompt agent" section with a runnable code block.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Adds `to_prompt_agent(agent)`, an experimental converter
(`ExperimentalFeature.TO_PROMPT_AGENT`) that turns an Agent Framework
`Agent` into a Foundry `PromptAgentDefinition` ready to publish via
`AIProjectClient.agents.create_version(...)`.

Behaviour:

* `agent.client` must be a `FoundryChatClient` (or subclass); otherwise
  `TypeError` is raised. The model deployment name is lifted from the
  bound client so the same Agent definition used for local runs can be
  published as a hosted prompt agent without restating the model.
* Foundry SDK tool instances (from `FoundryChatClient.get_*_tool()`) are
  passed through unchanged. AF `FunctionTool`s (and `@tool`-decorated
  callables) are emitted as Foundry `FunctionTool` declarations.
* Local AF MCP tools cannot be expressed in a `PromptAgentDefinition`;
  the converter raises `ValueError` and points at
  `FoundryChatClient.get_mcp_tool()` for hosted MCP servers.
* The converter walks both `agent.default_options["tools"]` and
  `agent.mcp_tools` because `normalize_tools()` splits local MCP off
  into its own list.

Re-exported through the `agent_framework.foundry` lazy-loading namespace
(updates both `__init__.py` and the `__init__.pyi` type stub).

Adds a portable-agent sample showing the same `Agent` driven through
both `agent.run(...)` and `to_prompt_agent(agent)`, and a README section
covering the new converter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 19, 2026 18:31
eavanvalkenburg and others added 3 commits May 19, 2026 20:36
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an experimental to_prompt_agent(agent) -> PromptAgentDefinition converter to make Agent Framework Foundry agents portable between local execution (agent.run) and publishing as a hosted Foundry prompt agent.

Changes:

  • Introduces agent_framework_foundry._to_prompt_agent.to_prompt_agent with tool conversion rules (SDK tools pass-through, AF function tools -> declarations, local MCP rejected, dict tools rehydrated).
  • Re-exports to_prompt_agent via agent_framework_foundry and agent_framework.foundry (incl. .pyi) and registers ExperimentalFeature.TO_PROMPT_AGENT.
  • Adds unit tests, README guidance, and a portable-agent sample.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
python/samples/02-agents/providers/foundry/foundry_portable_agent.py Adds an end-to-end sample running locally and publishing via to_prompt_agent.
python/packages/foundry/tests/foundry/test_to_prompt_agent.py Adds coverage for client validation, model requirements, and tool conversion behaviors.
python/packages/foundry/agent_framework_foundry/_to_prompt_agent.py Implements the converter and tool-shape conversion/validation logic.
python/packages/foundry/agent_framework_foundry/init.py Re-exports to_prompt_agent from the package root.
python/packages/foundry/README.md Documents how to publish an agent as a Foundry prompt agent (experimental).
python/packages/core/agent_framework/foundry/init.pyi Exposes to_prompt_agent in the typed public surface.
python/packages/core/agent_framework/foundry/init.py Adds lazy import mapping for to_prompt_agent.
python/packages/core/agent_framework/_feature_stage.py Adds ExperimentalFeature.TO_PROMPT_AGENT.

Comment thread python/packages/foundry/agent_framework_foundry/_to_prompt_agent.py Outdated
Comment thread python/packages/foundry/agent_framework_foundry/_to_prompt_agent.py Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@moonbox3 moonbox3 added documentation Improvements or additions to documentation python labels May 19, 2026
eavanvalkenburg and others added 2 commits May 19, 2026 20:42
…ompt agents

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Construct `PromptAgentDefinition` `Tool` from a dict via `**tool_item`
  unpacking rather than the positional Mapping constructor \u2014 cleaner and
  matches the typical Pydantic / Azure SDK pattern.
* Drop the redundant `isinstance(mcp_tool, MCPTool)` guard in
  `_convert_tools`; the parameter is already typed `Iterable[MCPTool]` so
  the second `raise` was unreachable. The remaining single `raise`
  fires for every entry as intended.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot changed the title feat(foundry): add experimental to_prompt_agent converter Python: feat(foundry): add experimental to_prompt_agent converter May 19, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 85%

✓ Correctness

The converter logic is sound: it correctly accesses agent.default_options["tools"] for non-MCP tools, agent.mcp_tools for local MCP tools, and agent.client.model for the deployment name. The isinstance checks in _convert_tools are ordered correctly (ProjectsTool before FunctionTool before Mapping). FunctionTool.parameters() is a valid method returning a dict (confirmed at _tools.py:782). The only remaining concern (already flagged in the prior review) is the ProjectsTool(dict(tool_item)) positional-dict construction on line 179, which works with the Azure SDK's autorest-generated _model_base.Model but is non-obvious and may break if the SDK changes its internal base class. No new correctness issues found beyond what was already flaged.

✓ Security Reliability

The converter module is well-structured with proper validation: client type checks, model presence validation, tool type discrimination with clear error messages, and explicit rejection of local MCP tools. No new security or reliability issues found beyond those already flagged in the existing review thread (ProjectsTool positional construction, unreachable mcp_tools branch, sample cosmetics).

✓ Test Coverage

Test coverage is generally thorough, covering the main success paths, error conditions, and the experimental decorator. The primary gap is the absence of a test for an Agent created without instructions (a common real-world scenario where agents are purely tool-based). The converter explicitly calls agent.default_options.get("instructions") which returns None in that case, and this path should be verified. Additionally, a test combining valid tools alongside a local MCP tool would strengthen coverage of the error path to ensure valid tools don't get lost before the MCP rejection fires.

✓ Design Approach

I found one design issue: the converter currently publishes the client’s base model instead of the agent’s effective model. In this repo, Agent(default_options={"model": ...}) is the authoritative override for local execution, so to_prompt_agent() can produce a prompt agent that runs on a different model than the same Agent uses locally.


Automated review by eavanvalkenburg's agents

Comment thread python/packages/foundry/agent_framework_foundry/_to_prompt_agent.py
Comment thread python/packages/foundry/tests/foundry/test_to_prompt_agent.py
* Read the model from `agent.default_options.get("model")` first,
  falling back to `agent.client.model`. This mirrors the order
  `Agent.__init__` uses (`_agents.py:740`) when assembling
  default_options, so the model the agent runs with is the same model
  the converter publishes \u2014 e.g. when the caller passes
  `default_options={"model": "..."}` to override the bound client.
* Updated the missing-model error message to point at both the client
  and the default_options paths.
* Added tests:
  * tool-only agent with no `instructions` produces a definition
    where `instructions` is `None` and is omitted from the dict
    payload (`Agent.__init__` strips None values from default_options
    before storing them).
  * `default_options['model']` wins over the bound client's model.
  * Fallback to client.model when default_options has no model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants