Files
5e50e190c4 Python: Add reasoning support for OpenAI Responses Agents (GPT-5, o4-mini, o3) (#12881)
## Summary

Adds reasoning capabilities to Python OpenAI ResponsesAgent, bringing
parity with the C# implementation. This enables fine-grained control
over reasoning effort for O-series models (o1, o3-mini, o4-mini) and
gpt-5 that support the reasoning parameter.

Fixes #12843

## Changes

### Core Implementation
- **`openai_responses_agent.py`**: Added reasoning configuration support
  - Constructor-level reasoning effort setting
  - Per-invocation reasoning effort override capability
  - Proper parameter validation and model compatibility checks
- **`responses_agent_thread_actions.py`**: Extended thread actions to
support reasoning parameters
  - Reasoning effort propagation through thread operations
  - Metadata preservation for reasoning tokens and summaries

### Sample and Tests
- **`responses_agent_reasoning.py`**: Comprehensive demonstration sample
  - Constructor vs per-invocation reasoning configuration
  - Function calling integration with reasoning
  - Reasoning comparison scenarios (low/medium/high effort)
  - Error handling and troubleshooting guidance
- **`test_openai_responses_agent_reasoning.py`**: Full unit test
coverage
  - Parameter validation tests
  - Integration scenarios with function calling
  - Edge cases and error conditions

## Features

✅ **Constructor-Level Reasoning**: Set default reasoning effort when
creating agents
✅ **Per-Invocation Override**: Override reasoning effort per request  
✅ **Priority Hierarchy**: per-invocation > constructor > model default  
✅ **Function Calling Compatible**: Works seamlessly with existing plugin
system
✅ **Azure OpenAI & OpenAI Support**: Compatible with both service
providers
✅ **Model Validation**: Automatic compatibility checks for O-series
models nad GÜT-5
✅ **Metadata Access**: Reasoning tokens and summaries available in
response metadata

## Usage Example

```python
# Constructor-level reasoning
# Constructor-level reasoning configuration
agent = AzureResponsesAgent(
    ai_model_id="gpt-5",
    client=client,
    reasoning={"effort": "low"}  # Default reasoning for all requests
)

# Per-invocation override
response = await agent.invoke(
    "Solve this complex problem step by step",
    reasoning={"effort": "high"}  
)


# Invoke with reasoning callback to capture intermediate thoughts
response = await agent.invoke(
    "Analyze this data step by step",
    reasoning={"effort": "high", "summary": "detailed"},
    on_intermediate_message=handle_reasoning_message
)

# Streaming with reasoning
async for response in agent.invoke_stream(
    "Explain quantum computing in detail",
    reasoning={"effort": "high", "summary": "detailed"},
    on_intermediate_message=handle_reasoning_message
):
    print(response.content, end="", flush=True)

---------

Co-authored-by: Evan Mattson <[email protected]>
Co-authored-by: Evan Mattson <[email protected]>
2025-08-28 23:25:26 +00:00

60 lines
2.3 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""Reasoning content model."""
from html import unescape
from typing import ClassVar, Literal, TypeVar
from xml.etree.ElementTree import Element # nosec
from pydantic import Field
from semantic_kernel.contents.const import REASONING_CONTENT_TAG, ContentTypes
from semantic_kernel.contents.kernel_content import KernelContent
from semantic_kernel.exceptions.content_exceptions import ContentInitializationError
_T = TypeVar("_T", bound="ReasoningContent")
class ReasoningContent(KernelContent):
"""Represents reasoning content.
Exposes a human-readable reasoning ``text``. Any provider-specific fields (for example: ids, encrypted blobs,
statuses, token info) must be carried in ``metadata`` on the base ``KernelContent``.
Attributes:
content_type: Literal identifying this instance as reasoning content.
tag: XML tag name used when serializing to/from XML.
text: The reasoning text to surface to callers.
Methods:
__str__: Return the reasoning text.
to_element: Serialize to an XML Element using ``tag`` and ``text``.
from_element: Deserialize from an XML Element into a ReasoningContent.
to_dict: Serialize to a dict suitable for message payloads.
"""
content_type: Literal[ContentTypes.REASONING_CONTENT] = Field(ContentTypes.REASONING_CONTENT, init=False)
tag: ClassVar[str] = REASONING_CONTENT_TAG
text: str | None = None
def __str__(self) -> str:
"""Return the text of the reasoning content."""
return self.text or ""
def to_element(self) -> Element:
"""Convert the instance to an XML Element."""
element = Element(self.tag)
element.text = self.text
return element
@classmethod
def from_element(cls: type[_T], element: Element) -> _T:
"""Create an instance from an XML Element."""
if element.tag != cls.tag:
raise ContentInitializationError(f"Element tag is not {cls.tag}") # pragma: no cover
return cls(text=unescape(element.text) if element.text else "")
def to_dict(self) -> dict[str, str]:
"""Convert the instance to a dictionary suitable for message serialization."""
return {"type": "reasoning", "text": self.text or ""}