Skip to content

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

Description

Summary

When using agent.as_tool(), the sub-agent returns concatenated TextContent from all messages (filters out tool call/result), not just the final answer. Developers currently have no way to control this (e.g., no flexibility to either return last n messages, or some custom filtering useful for context engineering).

Suggestion: Consider adding an optional return_mode parameter to as_tool().

The Problem

Currently, as_tool() returns response.text which concatenates all TextContent from all messages (source).

In a typical multi-step sub-agent scenario:

Message Content Returned?
1 "Let me search for that..." ✓ YES
2 FunctionCallContent (tool call) ✗ No
3 FunctionResultContent (2000 tokens) ✗ No
4 "I found some info, let me dig deeper..." ✓ YES
5 FunctionCallContent (tool call) ✗ No
6 FunctionResultContent (3000 tokens) ✗ No
7 "FINAL ANSWER: The solution is..." ✓ YES

What gets returned: "Let me search...I found some info...FINAL ANSWER: ..."

What developer likely wants (in some cases): "FINAL ANSWER: The solution is..."

In testing with 10 intermediate "thinking" messages, the coordinator receives ~10x more text than necessary.

Some developer scenarios where this is problematic:

  1. Context bloat: Coordinator's context fills up faster, undermining the isolation pattern's benefit for 'sub-agent' calls
  2. Signal vs noise: Intermediate "thinking" text adds no value to coordinator's decision-making
  3. Cost: More tokens processed in coordinator, increasing costs

Proposed Solution

def as_tool(
    self,
    *,
    # ... existing params ...
    return_mode: Literal["all", "last"] | Callable[[AgentRunResponse], str] = "all",
) -> AIFunction[BaseModel, str]:
  • "all" (default): Current behavior (backward compatible)
  • "last": Return only the last message's text
  • Callable: Custom extraction logic with full access to AgentRunResponse

Examples:

# Return only last message
researcher.as_tool(return_mode="last")

# Return last N messages
researcher.as_tool(return_mode=lambda r: "\n".join(m.text for m in r.messages[-3:]))

# Include tool calls (for debugging/audit)
researcher.as_tool(return_mode=lambda r: format_with_tool_calls(r.messages))

The callable receives the full AgentRunResponse, giving access to response.messages with raw .contents (including FunctionCallContent, FunctionResultContent) for advanced use cases.

Implementation Sketch

# In as_tool(), replace `.text` calls with `_apply_return_mode(response)`:
# - Line 464: non-streaming path
# - Line 476: streaming path
def _apply_return_mode(response: AgentRunResponse) -> str:
    if callable(return_mode):
        return return_mode(response)
    elif return_mode == "last":
        return response.messages[-1].text if response.messages else ""
    return response.text  # "all" - current behavior

Default "all" preserves current behavior with no breaking changes.

Applies to Both Python and .NET

This issue affects both language implementations:

Language Method Returns Source
Python as_tool() response.text _agents.py:464
.NET AsAIFunction() response.Text AgentExtensions.cs:77

Both use ConcatText() to concatenate TextContent from all messages, so both would benefit from a returnMode parameter.

Activity

  1. changed the title [-]Python: [.Net] Consider adding `return_mode` parameter to `as_tool()` / `AsAIFunction()`[/-] [+].NET: Python: [.Net] Consider adding `return_mode` parameter to `as_tool()` / `AsAIFunction()`[/+] on Dec 11, 2025
  2. changed the title [-].NET: Python: [.Net] Consider adding `return_mode` parameter to `as_tool()` / `AsAIFunction()`[/-] [+].NET/Python: Consider adding `return_mode` parameter to `as_tool()` / `AsAIFunction()`[/+] on Dec 11, 2025
  3. MichaelSurgeon commented on Dec 18, 2025

    @MichaelSurgeon

    I wonder if this could be expanded for returning artifact information to? currently artifacts are lost as part of as_tool() we have a use case where we return metadata within an artifact for a file like chapter_name etc which is lost on return.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetagentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: Python

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions