Python: [BREAKING]: Introducing Options as TypedDict and Generic (#3140)

* WIP typeddict for options

* updated all clients and ChatAgents

* updated everything

* added ADR

* fix mypy

* proper typevar imports

* fixed import

* fixed other imports

* slight update in the sample

* updated from feedback

* fixes

* fixed missing covariants and test fixes

* fixed typing

* updated anthropic thinking config

* ruff fixes

* fixed int tests

* fix tests and mypy

* updated integration tests

* updated docstring and test fix

* improved options handling in obser

* mypy fix

* updated a host of integration tests

* fix tests

* bedrock fix
This commit is contained in:
Eduard van Valkenburg
2026-01-13 17:41:05 +01:00
committed by GitHub
Unverified
parent 5faa2851bb
commit 3e97425245
111 changed files with 6141 additions and 4715 deletions
@@ -3,7 +3,7 @@
import asyncio
from agent_framework import HostedMCPTool, HostedWebSearchTool, TextReasoningContent, UsageContent
from agent_framework.anthropic import AnthropicClient
from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient
"""
Anthropic Chat Agent Example
@@ -15,9 +15,9 @@ This sample demonstrates using Anthropic with:
"""
async def streaming_example() -> None:
async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
agent = AnthropicClient().create_agent(
agent = AnthropicClient[AnthropicChatOptions]().create_agent(
name="DocsAgent",
instructions="You are a helpful agent for both Microsoft docs questions and general questions.",
tools=[
@@ -27,10 +27,12 @@ async def streaming_example() -> None:
),
HostedWebSearchTool(),
],
# anthropic needs a value for the max_tokens parameter
# we set it to 1024, but you can override like this:
max_tokens=20000,
additional_chat_options={"thinking": {"type": "enabled", "budget_tokens": 10000}},
default_options={
# anthropic needs a value for the max_tokens parameter
# we set it to 1024, but you can override like this:
"max_tokens": 20000,
"thinking": {"type": "enabled", "budget_tokens": 10000},
},
)
query = "Can you compare Python decorators with C# attributes?"
@@ -48,11 +50,5 @@ async def streaming_example() -> None:
print("\n")
async def main() -> None:
print("=== Anthropic Example ===")
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())
@@ -38,10 +38,12 @@ async def main() -> None:
),
HostedWebSearchTool(),
],
# anthropic needs a value for the max_tokens parameter
# we set it to 1024, but you can override like this:
max_tokens=20000,
additional_chat_options={"thinking": {"type": "enabled", "budget_tokens": 10000}},
default_options={
# anthropic needs a value for the max_tokens parameter
# we set it to 1024, but you can override like this:
"max_tokens": 20000,
"thinking": {"type": "enabled", "budget_tokens": 10000},
},
)
query = "Can you compare Python decorators with C# attributes?"
@@ -5,7 +5,7 @@ import logging
from pathlib import Path
from agent_framework import HostedCodeInterpreterTool, HostedFileContent
from agent_framework.anthropic import AnthropicClient
from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient
logger = logging.getLogger(__name__)
"""
@@ -22,7 +22,7 @@ This sample demonstrates using Anthropic with:
async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
client = AnthropicClient(additional_beta_flags=["skills-2025-10-02"])
client = AnthropicClient[AnthropicChatOptions](additional_beta_flags=["skills-2025-10-02"])
# List Anthropic-managed Skills
skills = await client.anthropic_client.beta.skills.list(source="anthropic", betas=["skills-2025-10-02"])
@@ -35,8 +35,8 @@ async def main() -> None:
name="DocsAgent",
instructions="You are a helpful agent for creating powerpoint presentations.",
tools=HostedCodeInterpreterTool(),
max_tokens=20000,
additional_chat_options={
default_options={
"max_tokens": 20000,
"thinking": {"type": "enabled", "budget_tokens": 10000},
"container": {"skills": [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]},
},
@@ -44,7 +44,7 @@ async def main() -> None:
result = await agent.run(
query,
# These additional options are required for image generation
additional_chat_options={
options={
"extra_headers": {"x-ms-oai-image-generation-deployment": "gpt-image-1-mini"},
},
)
@@ -46,7 +46,7 @@ async def main() -> None:
result = await agent.run(
query,
# Specify type to use as response
additional_chat_options={
options={
"response_format": {
"type": "json_schema",
"json_schema": {
@@ -2,13 +2,13 @@
import asyncio
import random
import sys
from collections.abc import AsyncIterable, MutableSequence
from typing import Any, ClassVar
from typing import Any, ClassVar, Generic
from agent_framework import (
BaseChatClient,
ChatMessage,
ChatOptions,
ChatResponse,
ChatResponseUpdate,
Role,
@@ -16,6 +16,12 @@ from agent_framework import (
use_chat_middleware,
use_function_invocation,
)
from agent_framework._clients import TOptions_co
if sys.version_info >= (3, 12):
from typing import override # type: ignore # pragma: no cover
else:
from typing_extensions import override # type: ignore[import] # pragma: no cover
"""
Custom Chat Client Implementation Example
@@ -27,7 +33,7 @@ showing integration with ChatAgent and both streaming and non-streaming response
@use_function_invocation
@use_chat_middleware
class EchoingChatClient(BaseChatClient):
class EchoingChatClient(BaseChatClient[TOptions_co], Generic[TOptions_co]):
"""A custom chat client that echoes messages back with modifications.
This demonstrates how to implement a custom chat client by extending BaseChatClient
@@ -46,11 +52,12 @@ class EchoingChatClient(BaseChatClient):
super().__init__(**kwargs)
self.prefix = prefix
@override
async def _inner_get_response(
self,
*,
messages: MutableSequence[ChatMessage],
chat_options: ChatOptions,
options: dict[str, Any],
**kwargs: Any,
) -> ChatResponse:
"""Echo back the user's message with a prefix."""
@@ -77,16 +84,17 @@ class EchoingChatClient(BaseChatClient):
response_id=f"echo-resp-{random.randint(1000, 9999)}",
)
@override
async def _inner_get_streaming_response(
self,
*,
messages: MutableSequence[ChatMessage],
chat_options: ChatOptions,
options: dict[str, Any],
**kwargs: Any,
) -> AsyncIterable[ChatResponseUpdate]:
"""Stream back the echoed message character by character."""
# Get the complete response first
response = await self._inner_get_response(messages=messages, chat_options=chat_options, **kwargs)
response = await self._inner_get_response(messages=messages, options=options, **kwargs)
if response.messages:
response_text = response.messages[0].text or ""
@@ -24,7 +24,7 @@ async def reasoning_example() -> None:
agent = OllamaChatClient().create_agent(
name="TimeAgent",
instructions="You are a helpful agent answer in one sentence.",
additional_chat_options={"think": True}, # Enable Reasoning on agent level
default_options={"think": True}, # Enable Reasoning on agent level
)
query = "Hey what is 3+4? Can you explain how you got to that answer?"
print(f"User: {query}")
@@ -3,7 +3,7 @@
import asyncio
import json
from agent_framework.openai import OpenAIChatClient
from agent_framework.openai import OpenAIChatClient, OpenAIChatOptions
"""
OpenAI Chat Client Runtime JSON Schema Example
@@ -32,7 +32,7 @@ runtime_schema = {
async def non_streaming_example() -> None:
print("=== Non-streaming runtime JSON schema example ===")
agent = OpenAIChatClient().create_agent(
agent = OpenAIChatClient[OpenAIChatOptions]().create_agent(
name="RuntimeSchemaAgent",
instructions="Return only JSON that matches the provided schema. Do not add commentary.",
)
@@ -42,7 +42,7 @@ async def non_streaming_example() -> None:
response = await agent.run(
query,
additional_chat_options={
options={
"response_format": {
"type": "json_schema",
"json_schema": {
@@ -76,7 +76,7 @@ async def streaming_example() -> None:
chunks: list[str] = []
async for chunk in agent.run_stream(
query,
additional_chat_options={
options={
"response_format": {
"type": "json_schema",
"json_schema": {
@@ -2,7 +2,7 @@
import asyncio
from agent_framework.openai import OpenAIResponsesClient
from agent_framework.openai import OpenAIResponsesClient, OpenAIResponsesOptions
"""
OpenAI Responses Client Reasoning Example
@@ -10,19 +10,20 @@ OpenAI Responses Client Reasoning Example
This sample demonstrates advanced reasoning capabilities using OpenAI's gpt-5 models,
showing step-by-step reasoning process visualization and complex problem-solving.
This uses the additional_chat_options parameter to enable reasoning with high effort and detailed summaries.
You can also set these options at the run level, since they are api and/or provider specific, you will need to lookup
the correct values for your provider, since these are passed through as-is.
This uses the default_options parameter to enable reasoning with high effort and detailed summaries.
You can also set these options at the run level using the options parameter.
Since these are api and/or provider specific, you will need to lookup
the correct values for your provider, as they are passed through as-is.
In this case they are here: https://platform.openai.com/docs/api-reference/responses/create#responses-create-reasoning
"""
agent = OpenAIResponsesClient(model_id="gpt-5").create_agent(
agent = OpenAIResponsesClient[OpenAIResponsesOptions](model_id="gpt-5").create_agent(
name="MathHelper",
instructions="You are a personal math tutor. When asked a math question, "
"reason over how best to approach the problem and share your thought process.",
additional_chat_options={"reasoning": {"effort": "high", "summary": "detailed"}},
default_options={"reasoning": {"effort": "high", "summary": "detailed"}},
)
@@ -42,7 +42,7 @@ async def non_streaming_example() -> None:
response = await agent.run(
query,
additional_chat_options={
options={
"response_format": {
"type": "json_schema",
"json_schema": {
@@ -76,7 +76,7 @@ async def streaming_example() -> None:
chunks: list[str] = []
async for chunk in agent.run_stream(
query,
additional_chat_options={
options={
"response_format": {
"type": "json_schema",
"json_schema": {