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
@@ -883,10 +883,14 @@ class EntityDiscovery:
try:
if obj_type == "agent":
# For agents, check chat_options.tools first
chat_options = getattr(obj, "chat_options", None)
if chat_options and hasattr(chat_options, "tools"):
for tool in chat_options.tools:
# For agents, check default_options.get("tools")
chat_options = getattr(obj, "default_options", None)
chat_options_tools = None
if chat_options:
chat_options_tools = chat_options.get("tools")
if chat_options_tools:
for tool in chat_options_tools:
if hasattr(tool, "__name__"):
tools.append(tool.__name__)
elif hasattr(tool, "name"):
@@ -37,17 +37,27 @@ def extract_agent_metadata(entity_object: Any) -> dict[str, Any]:
}
# Try to get instructions
if hasattr(entity_object, "chat_options") and hasattr(entity_object.chat_options, "instructions"):
metadata["instructions"] = entity_object.chat_options.instructions
if hasattr(entity_object, "default_options"):
chat_opts = entity_object.default_options
if isinstance(chat_opts, dict):
if "instructions" in chat_opts:
metadata["instructions"] = chat_opts.get("instructions")
elif hasattr(chat_opts, "instructions"):
metadata["instructions"] = chat_opts.instructions
# Try to get model - check both chat_options and chat_client
# Try to get model - check both default_options and chat_client
if hasattr(entity_object, "default_options"):
chat_opts = entity_object.default_options
if isinstance(chat_opts, dict):
if chat_opts.get("model_id"):
metadata["model"] = chat_opts.get("model_id")
elif hasattr(chat_opts, "model_id") and chat_opts.model_id:
metadata["model"] = chat_opts.model_id
if (
hasattr(entity_object, "chat_options")
and hasattr(entity_object.chat_options, "model_id")
and entity_object.chat_options.model_id
metadata["model"] is None
and hasattr(entity_object, "chat_client")
and hasattr(entity_object.chat_client, "model_id")
):
metadata["model"] = entity_object.chat_options.model_id
elif hasattr(entity_object, "chat_client") and hasattr(entity_object.chat_client, "model_id"):
metadata["model"] = entity_object.chat_client.model_id
# Try to get chat client type
+13 -5
View File
@@ -13,8 +13,9 @@ These follow the patterns established in other agent_framework packages
to avoid pytest plugin conflicts when running tests across packages.
"""
import sys
from collections.abc import AsyncIterable, MutableSequence
from typing import Any
from typing import Any, Generic
from agent_framework import (
AgentRunResponse,
@@ -24,7 +25,6 @@ from agent_framework import (
BaseChatClient,
ChatAgent,
ChatMessage,
ChatOptions,
ChatResponse,
ChatResponseUpdate,
ConcurrentBuilder,
@@ -35,8 +35,14 @@ from agent_framework import (
TextContent,
use_chat_middleware,
)
from agent_framework._clients import TOptions_co
from agent_framework._workflows._agent_executor import AgentExecutorResponse
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
# Import real workflow event classes - NOT mocks!
from agent_framework._workflows._events import (
ExecutorCompletedEvent,
@@ -91,7 +97,7 @@ class MockChatClient:
@use_chat_middleware
class MockBaseChatClient(BaseChatClient):
class MockBaseChatClient(BaseChatClient[TOptions_co], Generic[TOptions_co]):
"""Full BaseChatClient mock with middleware support.
Use this when testing features that require the full BaseChatClient interface.
@@ -106,11 +112,12 @@ class MockBaseChatClient(BaseChatClient):
self.call_count: int = 0
self.received_messages: list[list[ChatMessage]] = []
@override
async def _inner_get_response(
self,
*,
messages: MutableSequence[ChatMessage],
chat_options: ChatOptions,
options: dict[str, Any],
**kwargs: Any,
) -> ChatResponse:
self.call_count += 1
@@ -119,11 +126,12 @@ class MockBaseChatClient(BaseChatClient):
return self.run_responses.pop(0)
return ChatResponse(messages=ChatMessage(role="assistant", text="Mock response from ChatAgent"))
@override
async def _inner_get_streaming_response(
self,
*,
messages: MutableSequence[ChatMessage],
chat_options: ChatOptions,
options: dict[str, Any],
**kwargs: Any,
) -> AsyncIterable[ChatResponseUpdate]:
self.call_count += 1