Skip to content

Python: add configurable response extraction to agent.as_tool() - #10242

Draft
M-Hietala wants to merge 1 commit into
microsoft:mainfrom
M-Hietala:m-hietala/python_add_configurable_response_extraction_to_agent_as_tool
Draft

M-Hietala wants to merge 1 commit into
microsoft:mainfrom
M-Hietala:m-hietala/python_add_configurable_response_extraction_to_agent_as_tool

Conversation

@M-Hietala

Copy link
Copy Markdown
Member

Motivation & Context

When an agent is used as a tool, its result currently contains the concatenated response text, potentially including intermediate commentary alongside the final answer. This can add unnecessary text to the calling agent's context.

This change lets callers select the final assistant message or provide custom text extraction without writing a separate delegation wrapper.

Description & Review Guide

Some design choices behind this proposed implementation:

  • "last" selects the last assistant message rather than the last message of any role, so a trailing tool result is not mistaken for the answer.
  • An empty or non-text final assistant message returns "", without falling back to earlier commentary. Falling back could present a progress message as the final answer.
  • Custom extractors receive the complete AgentResponse but return a string. This supports advanced filtering while keeping structured artifact forwarding outside this change.
  • Extractors are synchronous because the intended operation is local selection/formatting of an already-completed response. Async extractors could be supported separately if needed.
  • Selection happens after response finalization and pending-input checks. It does not filter streaming observers or allow extraction to hide unresolved approvals or consent requests.

These are proposed semantics, not assumptions of an agreed design. In particular, feedback on the meaning of "last" and combining built-in modes and a callable in one parameter would be helpful.

  • What are the major changes?

    Adds an optional return_mode parameter to BaseAgent.as_tool():

    • "all": preserves the existing response.text behavior and remains the default.
    • "last": returns the last assistant message's text, or "" when no assistant message exists. Empty or non-text final assistant messages do not fall back to earlier commentary.
    • A synchronous Callable[[AgentResponse], str]: extracts text from the complete final response.

    Invalid modes fail before execution. Custom extractors must return a string, and extraction errors propagate through the existing tool execution path.

    Adds documentation and tests covering selection, streaming observers, empty responses, invalid configuration, extraction errors, and pending approvals and consent.

    Before and after:

    from agent_framework import Agent
    from agent_framework.openai import OpenAIChatClient
    
    child = Agent(
        client=OpenAIChatClient(model_id="gpt-4.1"),
        name="researcher",
    )
    
    # Existing behavior remains unchanged.
    research_tool = child.as_tool()
    
    # New: return only the final assistant message.
    research_tool = child.as_tool(return_mode="last")
    
    # New: customize extraction from the complete response.
    research_tool = child.as_tool(
        return_mode=lambda response: "\n".join(
            message.text for message in response.messages[-3:]
        )
    )
  • What is the impact of these changes?

    This is an opt-in, backward-compatible Python API addition. Selection applies to the completed tool result, both with and without a streaming callback. Streaming callbacks continue receiving all updates, and approval and other pending-input checks run before extraction. Child execution, session persistence, and AgentResponse.text semantics are unchanged. The .NET implementation is not modified.

  • What do you want reviewers to focus on?

    The proposed "last" semantics, synchronous callable contract, and suitability of this API addition for the current release line.

Related Issue

Fixes #2796 (Python implementation).

Submitted as a proposed implementation for design review. Explicit maintainer agreement is still pending; the issue also discusses the separate .NET API.

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: GitHub Copilot assisted with design, implementation, tests, documentation, validation, and this PR description.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI balanced review requested due to automatic review settings November 10, 2026 17:46
@M-Hietala
M-Hietala deployed to github-app-auth November 10, 2026 17:46 — with GitHub Actions Active
@M-Hietala
M-Hietala deployed to github-app-auth November 10, 2026 17:46 — with GitHub Actions Active
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Nov 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The extractor validation accepts detectable async callables despite its synchronous contract, allowing child tools to execute before extraction fails and potentially leaving an unawaited coroutine.

1 open issue
What changed in this PR

Adds configurable response extraction to Python’s BaseAgent.as_tool(), addressing the Python portion of #2796 while preserving the default concatenated-text behavior.

Changes:

  • Adds "all", "last", and synchronous callable return modes.
  • Applies extraction after response completion and pending-input checks, without filtering streaming callbacks.
  • Documents the API and adds tests for selection, validation, extraction errors, approvals, and consent.
File Description
python/​packages/​core/​tests/​core/​test_agents.py Tests return modes, edge cases, streaming observers, and pending-input safeguards.
python/​packages/​core/​AGENTS.md Documents extraction semantics and unchanged streaming and session behavior.
python/​packages/​core/​agent_framework/​_agents.py Adds the parameter, validation, response selection, and API documentation.

Review effort: Balanced

Comment on lines +741 to +742
if not callable(return_mode) and return_mode not in ("all", "last"):
raise ValueError('return_mode must be "all", "last", or a synchronous callable returning str.')

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MAF Automated Review — Iteration 1

Result: No findings
Scope: full PR (1 commit(s)): 35e9b3ccb304
Model: gpt-5.6-sol

Overview

The PR adds opt-in response extraction while preserving the existing concatenated-text default. Pending-input checks remain ahead of extraction, session-state merging remains protected by finally, and focused tests cover built-in modes, custom extractors, streaming observers, invalid configuration, and approval/consent behavior. No Critical, High, or Medium defect introduced by the authoritative diff was established.

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
No publishable findings remained after source verification for this scope.

This branch was successfully deployed

1 active deployment
github-app-auth — 35e9b3cc Deployed Nov 10, 2026 by M-Hietala via team_check #9221
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET/Python: Consider adding return_mode parameter to as_tool() / AsAIFunction()

2 participants