mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] parameter naming and other fixes (#1255)
* parameter naming and other fixes * fix test * fix azure openai responses decorator ordering * fix test * fix mypy * fixes in options handling * fix tests * final fixes * exclude macos tests * fix model param
This commit is contained in:
committed by
GitHub
Unverified
parent
1a81ed202e
commit
76900f0eab
@@ -351,22 +351,28 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
Returns:
|
||||
str: The agent_id to use
|
||||
"""
|
||||
run_options = run_options or {}
|
||||
# If no agent_id is provided, create a temporary agent
|
||||
if self.agent_id is None:
|
||||
if not self.model_id:
|
||||
raise ServiceInitializationError("Model deployment name is required for agent creation.")
|
||||
if "model" not in run_options or not run_options["model"]:
|
||||
raise ServiceInitializationError(
|
||||
"Model deployment name is required for agent creation, "
|
||||
"can also be passed to the get_response methods."
|
||||
)
|
||||
|
||||
agent_name: str = self.agent_name or "UnnamedAgent"
|
||||
args: dict[str, Any] = {"model": self.model_id, "name": agent_name}
|
||||
if run_options:
|
||||
if "tools" in run_options:
|
||||
args["tools"] = run_options["tools"]
|
||||
if "tool_resources" in run_options:
|
||||
args["tool_resources"] = run_options["tool_resources"]
|
||||
if "instructions" in run_options:
|
||||
args["instructions"] = run_options["instructions"]
|
||||
if "response_format" in run_options:
|
||||
args["response_format"] = run_options["response_format"]
|
||||
args: dict[str, Any] = {
|
||||
"model": run_options["model"],
|
||||
"name": agent_name,
|
||||
}
|
||||
if "tools" in run_options:
|
||||
args["tools"] = run_options["tools"]
|
||||
if "tool_resources" in run_options:
|
||||
args["tool_resources"] = run_options["tool_resources"]
|
||||
if "instructions" in run_options:
|
||||
args["instructions"] = run_options["instructions"]
|
||||
if "response_format" in run_options:
|
||||
args["response_format"] = run_options["response_format"]
|
||||
created_agent = await self.project_client.agents.create_agent(**args)
|
||||
self.agent_id = str(created_agent.id)
|
||||
self._should_delete_agent = True
|
||||
@@ -673,7 +679,10 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
|
||||
if chat_options is not None:
|
||||
run_options["max_completion_tokens"] = chat_options.max_tokens
|
||||
run_options["model"] = chat_options.model_id
|
||||
if chat_options.model_id is not None:
|
||||
run_options["model"] = chat_options.model_id
|
||||
else:
|
||||
run_options["model"] = self.model_id
|
||||
run_options["top_p"] = chat_options.top_p
|
||||
run_options["temperature"] = chat_options.temperature
|
||||
run_options["parallel_tool_calls"] = chat_options.allow_multiple_tool_calls
|
||||
|
||||
@@ -285,7 +285,7 @@ async def test_azure_ai_chat_client_get_agent_id_or_create_create_new(
|
||||
azure_ai_settings = AzureAISettings(model_deployment_name=azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"])
|
||||
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, azure_ai_settings=azure_ai_settings)
|
||||
|
||||
agent_id = await chat_client._get_agent_id_or_create() # type: ignore
|
||||
agent_id = await chat_client._get_agent_id_or_create(run_options={"model": azure_ai_settings.model_deployment_name}) # type: ignore
|
||||
|
||||
assert agent_id == "test-agent-id"
|
||||
assert chat_client._should_delete_agent # type: ignore
|
||||
@@ -577,6 +577,7 @@ async def test_azure_ai_chat_client_get_agent_id_or_create_with_run_options(
|
||||
"tools": [{"type": "function", "function": {"name": "test_tool"}}],
|
||||
"instructions": "Test instructions",
|
||||
"response_format": {"type": "json_object"},
|
||||
"model": azure_ai_settings.model_deployment_name,
|
||||
}
|
||||
|
||||
agent_id = await chat_client._get_agent_id_or_create(run_options) # type: ignore
|
||||
@@ -1277,7 +1278,7 @@ async def test_azure_ai_chat_client_get_agent_id_or_create_with_agent_name(
|
||||
# Ensure agent_name is None to test the default
|
||||
chat_client.agent_name = None # type: ignore
|
||||
|
||||
agent_id = await chat_client._get_agent_id_or_create() # type: ignore
|
||||
agent_id = await chat_client._get_agent_id_or_create(run_options={"model": azure_ai_settings.model_deployment_name}) # type: ignore
|
||||
|
||||
assert agent_id == "test-agent-id"
|
||||
# Verify create_agent was called with default "UnnamedAgent"
|
||||
@@ -1294,7 +1295,7 @@ async def test_azure_ai_chat_client_get_agent_id_or_create_with_response_format(
|
||||
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, azure_ai_settings=azure_ai_settings)
|
||||
|
||||
# Test with response_format in run_options
|
||||
run_options = {"response_format": {"type": "json_object"}}
|
||||
run_options = {"response_format": {"type": "json_object"}, "model": azure_ai_settings.model_deployment_name}
|
||||
|
||||
agent_id = await chat_client._get_agent_id_or_create(run_options) # type: ignore
|
||||
|
||||
@@ -1313,7 +1314,10 @@ async def test_azure_ai_chat_client_get_agent_id_or_create_with_tool_resources(
|
||||
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, azure_ai_settings=azure_ai_settings)
|
||||
|
||||
# Test with tool_resources in run_options
|
||||
run_options = {"tool_resources": {"vector_store_ids": ["vs-123"]}}
|
||||
run_options = {
|
||||
"tool_resources": {"vector_store_ids": ["vs-123"]},
|
||||
"model": azure_ai_settings.model_deployment_name,
|
||||
}
|
||||
|
||||
agent_id = await chat_client._get_agent_id_or_create(run_options) # type: ignore
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ class ChatAgent(BaseAgent):
|
||||
from agent_framework.clients import OpenAIChatClient
|
||||
|
||||
# Create a basic chat agent
|
||||
client = OpenAIChatClient(model="gpt-4")
|
||||
client = OpenAIChatClient(model_id="gpt-4")
|
||||
agent = ChatAgent(chat_client=client, name="assistant", description="A helpful assistant")
|
||||
|
||||
# Run the agent with a simple message
|
||||
@@ -514,6 +514,26 @@ class ChatAgent(BaseAgent):
|
||||
# Use streaming responses
|
||||
async for update in agent.run_stream("What's the weather in Paris?"):
|
||||
print(update.text, end="")
|
||||
|
||||
With additional provider specific options:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
agent = ChatAgent(
|
||||
chat_client=client,
|
||||
name="reasoning-agent",
|
||||
instructions="You are a reasoning assistant.",
|
||||
model_id="gpt-5",
|
||||
temperature=0.7,
|
||||
max_tokens=500,
|
||||
additional_chat_options={
|
||||
"reasoning": {"effort": "high", "summary": "concise"}
|
||||
}, # OpenAI Responses specific.
|
||||
)
|
||||
|
||||
# Use streaming responses
|
||||
async for update in agent.run_stream("How do you prove the pythagorean theorem?"):
|
||||
print(update.text, end="")
|
||||
"""
|
||||
|
||||
AGENT_SYSTEM_NAME: ClassVar[str] = "microsoft.agent_framework"
|
||||
@@ -534,7 +554,7 @@ class ChatAgent(BaseAgent):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -549,7 +569,7 @@ class ChatAgent(BaseAgent):
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
request_kwargs: dict[str, Any] | None = None,
|
||||
additional_chat_options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a ChatAgent instance.
|
||||
@@ -578,7 +598,7 @@ class ChatAgent(BaseAgent):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -589,8 +609,9 @@ class ChatAgent(BaseAgent):
|
||||
tools: The tools to use for the request.
|
||||
top_p: The nucleus sampling probability to use.
|
||||
user: The user to associate with the request.
|
||||
request_kwargs: A dictionary of other values that will be passed through
|
||||
additional_chat_options: A dictionary of other values that will be passed through
|
||||
to the chat_client ``get_response`` and ``get_streaming_response`` methods.
|
||||
This can be used to pass provider specific parameters.
|
||||
kwargs: Any additional keyword arguments. Will be stored as ``additional_properties``.
|
||||
|
||||
Raises:
|
||||
@@ -626,7 +647,7 @@ class ChatAgent(BaseAgent):
|
||||
self._local_mcp_tools = [tool for tool in normalized_tools if isinstance(tool, MCPTool)]
|
||||
agent_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)]
|
||||
self.chat_options = ChatOptions(
|
||||
model_id=model,
|
||||
model_id=model_id,
|
||||
conversation_id=conversation_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
instructions=instructions,
|
||||
@@ -643,7 +664,7 @@ class ChatAgent(BaseAgent):
|
||||
tools=agent_tools,
|
||||
top_p=top_p,
|
||||
user=user,
|
||||
additional_properties=request_kwargs or {}, # type: ignore
|
||||
additional_properties=additional_chat_options or {}, # type: ignore
|
||||
)
|
||||
self._async_exit_stack = AsyncExitStack()
|
||||
self._update_agent_name()
|
||||
@@ -701,7 +722,7 @@ class ChatAgent(BaseAgent):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -716,7 +737,7 @@ class ChatAgent(BaseAgent):
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
additional_chat_options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AgentRunResponse:
|
||||
"""Run the agent with the given messages and options.
|
||||
@@ -736,7 +757,7 @@ class ChatAgent(BaseAgent):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -747,7 +768,8 @@ class ChatAgent(BaseAgent):
|
||||
tools: The tools to use for the request.
|
||||
top_p: The nucleus sampling probability to use.
|
||||
user: The user to associate with the request.
|
||||
additional_properties: Additional properties to include in the request.
|
||||
additional_chat_options: Additional properties to include in the request.
|
||||
Use this field for provider-specific parameters.
|
||||
kwargs: Additional keyword arguments for the agent.
|
||||
Will only be passed to functions that are called.
|
||||
|
||||
@@ -778,30 +800,27 @@ class ChatAgent(BaseAgent):
|
||||
if not mcp_server.is_connected:
|
||||
await self._async_exit_stack.enter_async_context(mcp_server)
|
||||
final_tools.extend(mcp_server.functions)
|
||||
response = await self.chat_client.get_response(
|
||||
messages=thread_messages,
|
||||
chat_options=run_chat_options
|
||||
& ChatOptions(
|
||||
model_id=model,
|
||||
conversation_id=thread.service_thread_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
metadata=metadata,
|
||||
presence_penalty=presence_penalty,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
stop=stop,
|
||||
store=store,
|
||||
temperature=temperature,
|
||||
tool_choice=tool_choice,
|
||||
tools=final_tools,
|
||||
top_p=top_p,
|
||||
user=user,
|
||||
additional_properties=additional_properties or {},
|
||||
),
|
||||
**kwargs,
|
||||
|
||||
co = run_chat_options & ChatOptions(
|
||||
model_id=model_id,
|
||||
conversation_id=thread.service_thread_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
metadata=metadata,
|
||||
presence_penalty=presence_penalty,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
stop=stop,
|
||||
store=store,
|
||||
temperature=temperature,
|
||||
tool_choice=tool_choice,
|
||||
tools=final_tools,
|
||||
top_p=top_p,
|
||||
user=user,
|
||||
**(additional_chat_options or {}),
|
||||
)
|
||||
response = await self.chat_client.get_response(messages=thread_messages, chat_options=co, **kwargs)
|
||||
|
||||
await self._update_thread_with_type_and_conversation_id(thread, response.conversation_id)
|
||||
|
||||
@@ -832,7 +851,7 @@ class ChatAgent(BaseAgent):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -847,7 +866,7 @@ class ChatAgent(BaseAgent):
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
additional_chat_options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterable[AgentRunResponseUpdate]:
|
||||
"""Stream the agent with the given messages and options.
|
||||
@@ -867,7 +886,7 @@ class ChatAgent(BaseAgent):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -878,7 +897,8 @@ class ChatAgent(BaseAgent):
|
||||
tools: The tools to use for the request.
|
||||
top_p: The nucleus sampling probability to use.
|
||||
user: The user to associate with the request.
|
||||
additional_properties: Additional properties to include in the request.
|
||||
additional_chat_options: Additional properties to include in the request.
|
||||
Use this field for provider-specific parameters.
|
||||
kwargs: Any additional keyword arguments.
|
||||
Will only be passed to functions that are called.
|
||||
|
||||
@@ -890,8 +910,6 @@ class ChatAgent(BaseAgent):
|
||||
thread=thread, input_messages=input_messages
|
||||
)
|
||||
agent_name = self._get_agent_name()
|
||||
response_updates: list[ChatResponseUpdate] = []
|
||||
|
||||
# Resolve final tool list (runtime provided tools + local MCP server tools)
|
||||
final_tools: list[ToolProtocol | MutableMapping[str, Any] | Callable[..., Any]] = []
|
||||
normalized_tools: list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]] = ( # type: ignore[reportUnknownVariableType]
|
||||
@@ -911,29 +929,29 @@ class ChatAgent(BaseAgent):
|
||||
await self._async_exit_stack.enter_async_context(mcp_server)
|
||||
final_tools.extend(mcp_server.functions)
|
||||
|
||||
co = run_chat_options & ChatOptions(
|
||||
conversation_id=thread.service_thread_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
metadata=metadata,
|
||||
model_id=model_id,
|
||||
presence_penalty=presence_penalty,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
stop=stop,
|
||||
store=store,
|
||||
temperature=temperature,
|
||||
tool_choice=tool_choice,
|
||||
tools=final_tools,
|
||||
top_p=top_p,
|
||||
user=user,
|
||||
**(additional_chat_options or {}),
|
||||
)
|
||||
|
||||
response_updates: list[ChatResponseUpdate] = []
|
||||
async for update in self.chat_client.get_streaming_response(
|
||||
messages=thread_messages,
|
||||
chat_options=run_chat_options
|
||||
& ChatOptions(
|
||||
conversation_id=thread.service_thread_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
metadata=metadata,
|
||||
model_id=model,
|
||||
presence_penalty=presence_penalty,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
stop=stop,
|
||||
store=store,
|
||||
temperature=temperature,
|
||||
tool_choice=tool_choice,
|
||||
tools=final_tools,
|
||||
top_p=top_p,
|
||||
user=user,
|
||||
additional_properties=additional_properties or {},
|
||||
),
|
||||
**kwargs,
|
||||
messages=thread_messages, chat_options=co, **kwargs
|
||||
):
|
||||
response_updates.append(update)
|
||||
|
||||
@@ -951,7 +969,7 @@ class ChatAgent(BaseAgent):
|
||||
raw_representation=update,
|
||||
)
|
||||
|
||||
response = ChatResponse.from_chat_response_updates(response_updates)
|
||||
response = ChatResponse.from_chat_response_updates(response_updates, output_format_type=co.response_format)
|
||||
await self._update_thread_with_type_and_conversation_id(thread, response.conversation_id)
|
||||
await self._notify_thread_of_new_messages(thread, input_messages, response.messages)
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class ChatClientProtocol(Protocol):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -129,7 +129,7 @@ class ChatClientProtocol(Protocol):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -160,7 +160,7 @@ class ChatClientProtocol(Protocol):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -188,7 +188,7 @@ class ChatClientProtocol(Protocol):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -240,7 +240,7 @@ def prepare_messages(messages: str | ChatMessage | list[str] | list[ChatMessage]
|
||||
def merge_chat_options(
|
||||
*,
|
||||
base_chat_options: ChatOptions | Any | None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
@@ -265,7 +265,7 @@ def merge_chat_options(
|
||||
|
||||
Keyword Args:
|
||||
base_chat_options: Optional base ChatOptions to merge with direct parameters.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
frequency_penalty: The frequency penalty to use.
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
@@ -292,40 +292,11 @@ def merge_chat_options(
|
||||
if base_chat_options is not None and not isinstance(base_chat_options, ChatOptions):
|
||||
raise TypeError("chat_options must be an instance of ChatOptions")
|
||||
|
||||
if base_chat_options is not None:
|
||||
# Combine tools from both sources
|
||||
base_tools = base_chat_options.tools or []
|
||||
combined_tools = [*base_tools, *(tools or [])] if tools else base_tools
|
||||
if base_chat_options is None:
|
||||
base_chat_options = ChatOptions()
|
||||
|
||||
# Create new chat_options, using direct parameters when provided, otherwise fall back to base
|
||||
return ChatOptions(
|
||||
model_id=model if model is not None else base_chat_options.model_id,
|
||||
frequency_penalty=(
|
||||
frequency_penalty if frequency_penalty is not None else base_chat_options.frequency_penalty
|
||||
),
|
||||
logit_bias=logit_bias if logit_bias is not None else base_chat_options.logit_bias,
|
||||
max_tokens=max_tokens if max_tokens is not None else base_chat_options.max_tokens,
|
||||
metadata=metadata if metadata is not None else base_chat_options.metadata,
|
||||
presence_penalty=(presence_penalty if presence_penalty is not None else base_chat_options.presence_penalty),
|
||||
response_format=(response_format if response_format is not None else base_chat_options.response_format),
|
||||
seed=seed if seed is not None else base_chat_options.seed,
|
||||
stop=stop if stop is not None else base_chat_options.stop,
|
||||
store=store if store is not None else base_chat_options.store,
|
||||
temperature=temperature if temperature is not None else base_chat_options.temperature,
|
||||
top_p=top_p if top_p is not None else base_chat_options.top_p,
|
||||
tool_choice=(
|
||||
tool_choice if (tool_choice is not None and tool_choice != "auto") else base_chat_options.tool_choice # type: ignore[arg-type]
|
||||
),
|
||||
tools=combined_tools or None,
|
||||
user=user if user is not None else base_chat_options.user,
|
||||
additional_properties=(
|
||||
additional_properties if additional_properties is not None else base_chat_options.additional_properties
|
||||
),
|
||||
conversation_id=base_chat_options.conversation_id,
|
||||
)
|
||||
# No base options, create from direct parameters only
|
||||
return ChatOptions(
|
||||
model_id=model,
|
||||
return base_chat_options & ChatOptions(
|
||||
model_id=model_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
@@ -340,7 +311,7 @@ def merge_chat_options(
|
||||
tool_choice=tool_choice,
|
||||
tools=tools,
|
||||
user=user,
|
||||
additional_properties=additional_properties or {},
|
||||
additional_properties=additional_properties,
|
||||
)
|
||||
|
||||
|
||||
@@ -560,7 +531,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -592,7 +563,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -604,17 +575,18 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
top_p: The nucleus sampling probability to use.
|
||||
user: The user to associate with the request.
|
||||
additional_properties: Additional properties to include in the request.
|
||||
Can be used for provider-specific parameters.
|
||||
kwargs: Any additional keyword arguments.
|
||||
May include ``chat_options`` which provides base values that can be overridden by direct parameters.
|
||||
|
||||
Returns:
|
||||
A chat response from the model.
|
||||
A chat response from the model_id.
|
||||
"""
|
||||
# Normalize tools and merge with base chat_options
|
||||
normalized_tools = await self._normalize_tools(tools)
|
||||
chat_options = merge_chat_options(
|
||||
base_chat_options=kwargs.pop("chat_options", None),
|
||||
model=model,
|
||||
model_id=model_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
@@ -654,7 +626,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -686,7 +658,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -698,6 +670,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
top_p: The nucleus sampling probability to use.
|
||||
user: The user to associate with the request.
|
||||
additional_properties: Additional properties to include in the request.
|
||||
Can be used for provider-specific parameters.
|
||||
kwargs: Any additional keyword arguments.
|
||||
May include ``chat_options`` which provides base values that can be overridden by direct parameters.
|
||||
|
||||
@@ -708,7 +681,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
normalized_tools = await self._normalize_tools(tools)
|
||||
chat_options = merge_chat_options(
|
||||
base_chat_options=kwargs.pop("chat_options", None),
|
||||
model=model,
|
||||
model_id=model_id,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
@@ -787,7 +760,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
model: str | None = None,
|
||||
model_id: str | None = None,
|
||||
presence_penalty: float | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
seed: int | None = None,
|
||||
@@ -802,7 +775,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
request_kwargs: dict[str, Any] | None = None,
|
||||
additional_chat_options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> "ChatAgent":
|
||||
"""Create a ChatAgent with this client.
|
||||
@@ -824,7 +797,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
metadata: Additional metadata to include in the request.
|
||||
model: The model to use for the agent.
|
||||
model_id: The model_id to use for the agent.
|
||||
presence_penalty: The presence penalty to use.
|
||||
response_format: The format of the response.
|
||||
seed: The random seed to use.
|
||||
@@ -835,8 +808,9 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
tools: The tools to use for the request.
|
||||
top_p: The nucleus sampling probability to use.
|
||||
user: The user to associate with the request.
|
||||
request_kwargs: A dictionary of other values that will be passed through
|
||||
additional_chat_options: A dictionary of other values that will be passed through
|
||||
to the chat_client ``get_response`` and ``get_streaming_response`` methods.
|
||||
This can be used to pass provider specific parameters.
|
||||
kwargs: Any additional keyword arguments. Will be stored as ``additional_properties``.
|
||||
|
||||
Returns:
|
||||
@@ -848,7 +822,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
from agent_framework.clients import OpenAIChatClient
|
||||
|
||||
# Create a client
|
||||
client = OpenAIChatClient(model="gpt-4")
|
||||
client = OpenAIChatClient(model_id="gpt-4")
|
||||
|
||||
# Create an agent using the convenience method
|
||||
agent = client.create_agent(
|
||||
@@ -873,7 +847,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
metadata=metadata,
|
||||
model=model,
|
||||
model_id=model_id,
|
||||
presence_penalty=presence_penalty,
|
||||
response_format=response_format,
|
||||
seed=seed,
|
||||
@@ -884,6 +858,6 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
tools=tools,
|
||||
top_p=top_p,
|
||||
user=user,
|
||||
request_kwargs=request_kwargs,
|
||||
additional_chat_options=additional_chat_options,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -221,7 +221,7 @@ class ChatContext(SerializationMixin):
|
||||
async def process(self, context: ChatContext, next):
|
||||
print(f"Chat client: {context.chat_client.__class__.__name__}")
|
||||
print(f"Messages: {len(context.messages)}")
|
||||
print(f"Model: {context.chat_options.model}")
|
||||
print(f"Model: {context.chat_options.model_id}")
|
||||
|
||||
# Store metadata
|
||||
context.metadata["input_tokens"] = self.count_tokens(context.messages)
|
||||
|
||||
@@ -163,7 +163,7 @@ class SerializationMixin:
|
||||
combined_exclude.update(self.INJECTABLE)
|
||||
|
||||
# Get all instance attributes
|
||||
result: dict[str, Any] = {"type": self._get_type_identifier()}
|
||||
result: dict[str, Any] = {} if "type" in combined_exclude else {"type": self._get_type_identifier()}
|
||||
for key, value in self.__dict__.items():
|
||||
if key not in combined_exclude and not key.startswith("_"):
|
||||
if exclude_none and value is None:
|
||||
|
||||
@@ -13,14 +13,14 @@ from collections.abc import (
|
||||
Sequence,
|
||||
)
|
||||
from copy import deepcopy
|
||||
from typing import Any, ClassVar, Literal, TypeVar, overload
|
||||
from typing import Any, ClassVar, Literal, TypeVar, cast, overload
|
||||
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from ._logging import get_logger
|
||||
from ._serialization import SerializationMixin
|
||||
from ._tools import ToolProtocol, ai_function
|
||||
from .exceptions import AdditionItemMismatch
|
||||
from .exceptions import AdditionItemMismatch, ContentError
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import Self # pragma: no cover
|
||||
@@ -96,7 +96,10 @@ def _parse_content(content_data: MutableMapping[str, Any]) -> "Contents":
|
||||
content_data: Content data (dict)
|
||||
|
||||
Returns:
|
||||
Content object or raises ValidationError if parsing fails
|
||||
Content object
|
||||
|
||||
Raises:
|
||||
ContentError if parsing fails
|
||||
"""
|
||||
content_type = str(content_data.get("type"))
|
||||
match content_type:
|
||||
@@ -125,7 +128,7 @@ def _parse_content(content_data: MutableMapping[str, Any]) -> "Contents":
|
||||
case "text_reasoning":
|
||||
return TextReasoningContent.from_dict(content_data)
|
||||
case _:
|
||||
raise ValidationError([f"Unknown content type '{content_type}'"], model=Contents) # type: ignore
|
||||
raise ContentError(f"Unknown content type '{content_type}'")
|
||||
|
||||
|
||||
def _parse_content_list(contents_data: Sequence[Any]) -> list["Contents"]:
|
||||
@@ -143,8 +146,8 @@ def _parse_content_list(contents_data: Sequence[Any]) -> list["Contents"]:
|
||||
try:
|
||||
content = _parse_content(content_data)
|
||||
contents.append(content)
|
||||
except ValidationError as ve:
|
||||
logger.warning(f"Skipping unknown content type or invalid content: {ve}")
|
||||
except ContentError as exc:
|
||||
logger.warning(f"Skipping unknown content type or invalid content: {exc}")
|
||||
else:
|
||||
# If it's already a content object, keep it as is
|
||||
contents.append(content_data)
|
||||
@@ -2098,8 +2101,8 @@ def _process_update(
|
||||
try:
|
||||
cont = _parse_content(content)
|
||||
message.contents.append(cont)
|
||||
except ValidationError as ve:
|
||||
logger.warning(f"Skipping unknown content type or invalid content: {ve}")
|
||||
except ContentError as exc:
|
||||
logger.warning(f"Skipping unknown content type or invalid content: {exc}")
|
||||
else:
|
||||
message.contents.append(content)
|
||||
# Incorporate the update's properties into the response.
|
||||
@@ -2816,12 +2819,12 @@ class AgentRunResponseUpdate(SerializationMixin):
|
||||
kwargs: will be combined with additional_properties if provided.
|
||||
|
||||
"""
|
||||
contents = [] if contents is None else _parse_content_list(contents)
|
||||
parsed_contents: list[Contents] = [] if contents is None else _parse_content_list(contents)
|
||||
|
||||
if text is not None:
|
||||
if isinstance(text, str):
|
||||
text = TextContent(text=text)
|
||||
contents.append(text)
|
||||
parsed_contents.append(text)
|
||||
|
||||
# Convert role from dict if needed (for SerializationMixin support)
|
||||
if isinstance(role, MutableMapping):
|
||||
@@ -2829,7 +2832,7 @@ class AgentRunResponseUpdate(SerializationMixin):
|
||||
elif isinstance(role, str):
|
||||
role = Role(value=role)
|
||||
|
||||
self.contents = contents
|
||||
self.contents = parsed_contents
|
||||
self.role = role
|
||||
self.author_name = author_name
|
||||
self.response_id = response_id
|
||||
@@ -3011,11 +3014,11 @@ class ChatOptions(SerializationMixin):
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
additional_properties: MutableMapping[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize ChatOptions.
|
||||
|
||||
Keyword Args:
|
||||
additional_properties: Provider-specific additional properties.
|
||||
model_id: The AI model ID to use.
|
||||
allow_multiple_tool_calls: Whether to allow multiple tool calls.
|
||||
conversation_id: The conversation ID.
|
||||
@@ -3034,6 +3037,8 @@ class ChatOptions(SerializationMixin):
|
||||
tools: List of available tools.
|
||||
top_p: The top-p value (must be between 0.0 and 1.0).
|
||||
user: The user ID.
|
||||
additional_properties: Provider-specific additional properties, can also be passed as kwargs.
|
||||
**kwargs: Additional properties to include in additional_properties.
|
||||
"""
|
||||
# Validate numeric constraints and convert types as needed
|
||||
if frequency_penalty is not None:
|
||||
@@ -3055,7 +3060,12 @@ class ChatOptions(SerializationMixin):
|
||||
if max_tokens is not None and max_tokens <= 0:
|
||||
raise ValueError("max_tokens must be greater than 0")
|
||||
|
||||
self.additional_properties = additional_properties or {}
|
||||
if additional_properties is None:
|
||||
additional_properties = {}
|
||||
if kwargs:
|
||||
additional_properties.update(kwargs)
|
||||
|
||||
self.additional_properties = cast(dict[str, Any], additional_properties)
|
||||
self.model_id = model_id
|
||||
self.allow_multiple_tool_calls = allow_multiple_tool_calls
|
||||
self.conversation_id = conversation_id
|
||||
@@ -3128,48 +3138,11 @@ class ChatOptions(SerializationMixin):
|
||||
case "none":
|
||||
return ToolMode.NONE
|
||||
case _:
|
||||
raise ValidationError(f"Invalid tool choice: {tool_choice}")
|
||||
raise ContentError(f"Invalid tool choice: {tool_choice}")
|
||||
if isinstance(tool_choice, (dict, Mapping)):
|
||||
return ToolMode.from_dict(tool_choice) # type: ignore
|
||||
return tool_choice
|
||||
|
||||
def to_provider_settings(self, *, by_alias: bool = True, exclude: set[str] | None = None) -> dict[str, Any]:
|
||||
"""Convert the ChatOptions to a dictionary suitable for provider requests.
|
||||
|
||||
Keyword Args:
|
||||
by_alias: Use alias names for fields if True.
|
||||
exclude: Additional keys to exclude from the output.
|
||||
|
||||
Returns:
|
||||
Dictionary of settings for provider.
|
||||
"""
|
||||
default_exclude = {"additional_properties", "type"} # 'type' is for serialization, not API calls
|
||||
# No tool choice if no tools are defined
|
||||
if self.tools is None or len(self.tools) == 0:
|
||||
default_exclude.add("tool_choice")
|
||||
# No metadata and logit bias if they are empty
|
||||
# Prevents 400 error
|
||||
if not self.logit_bias:
|
||||
default_exclude.add("logit_bias")
|
||||
if not self.metadata:
|
||||
default_exclude.add("metadata")
|
||||
|
||||
merged_exclude = default_exclude if exclude is None else default_exclude | set(exclude)
|
||||
|
||||
settings = self.to_dict(exclude_none=True, exclude=merged_exclude)
|
||||
if by_alias and self.model_id is not None:
|
||||
settings["model"] = settings.pop("model_id", None)
|
||||
|
||||
# Serialize tool_choice to its string representation for provider settings
|
||||
if "tool_choice" in settings and isinstance(self.tool_choice, ToolMode):
|
||||
settings["tool_choice"] = self.tool_choice.serialize_model()
|
||||
|
||||
settings = {k: v for k, v in settings.items() if v is not None}
|
||||
settings.update(self.additional_properties)
|
||||
for key in merged_exclude:
|
||||
settings.pop(key, None)
|
||||
return settings
|
||||
|
||||
def __and__(self, other: object) -> "ChatOptions":
|
||||
"""Combines two ChatOptions instances.
|
||||
|
||||
@@ -3189,14 +3162,13 @@ class ChatOptions(SerializationMixin):
|
||||
combined.tools = list(self.tools) if self.tools else None
|
||||
combined.logit_bias = dict(self.logit_bias) if self.logit_bias else None
|
||||
combined.metadata = dict(self.metadata) if self.metadata else None
|
||||
combined.additional_properties = dict(self.additional_properties)
|
||||
combined.response_format = response_format
|
||||
|
||||
# Apply scalar and mapping updates from the other options
|
||||
updated_data = other.to_dict(exclude_none=True, exclude={"tools"})
|
||||
logit_bias = updated_data.pop("logit_bias", {})
|
||||
metadata = updated_data.pop("metadata", {})
|
||||
additional_properties = updated_data.pop("additional_properties", {})
|
||||
additional_properties: dict[str, Any] = updated_data.pop("additional_properties", {})
|
||||
|
||||
for key, value in updated_data.items():
|
||||
setattr(combined, key, value)
|
||||
@@ -3205,10 +3177,18 @@ class ChatOptions(SerializationMixin):
|
||||
# Preserve response_format from other if it exists, otherwise keep self's
|
||||
if other.response_format is not None:
|
||||
combined.response_format = other.response_format
|
||||
combined.instructions = "\n".join([combined.instructions or "", other.instructions or ""])
|
||||
combined.logit_bias = {**(combined.logit_bias or {}), **logit_bias}
|
||||
combined.metadata = {**(combined.metadata or {}), **metadata}
|
||||
combined.additional_properties = {**(combined.additional_properties or {}), **additional_properties}
|
||||
if other.instructions:
|
||||
combined.instructions = "\n".join([combined.instructions or "", other.instructions or ""])
|
||||
|
||||
combined.logit_bias = (
|
||||
{**(combined.logit_bias or {}), **logit_bias} if logit_bias or combined.logit_bias else None
|
||||
)
|
||||
combined.metadata = {**(combined.metadata or {}), **metadata} if metadata or combined.metadata else None
|
||||
if combined.additional_properties and additional_properties:
|
||||
combined.additional_properties.update(additional_properties)
|
||||
else:
|
||||
if additional_properties:
|
||||
combined.additional_properties = additional_properties
|
||||
if other_tools:
|
||||
if combined.tools is None:
|
||||
combined.tools = list(other_tools)
|
||||
|
||||
@@ -21,8 +21,8 @@ from ._shared import (
|
||||
TAzureOpenAIResponsesClient = TypeVar("TAzureOpenAIResponsesClient", bound="AzureOpenAIResponsesClient")
|
||||
|
||||
|
||||
@use_observability
|
||||
@use_function_invocation
|
||||
@use_observability
|
||||
@use_chat_middleware
|
||||
class AzureOpenAIResponsesClient(AzureOpenAIConfigMixin, OpenAIBaseResponsesClient):
|
||||
"""Azure Responses completion class."""
|
||||
|
||||
@@ -171,7 +171,6 @@ class AzureOpenAIConfigMixin(OpenAIBase):
|
||||
|
||||
Args:
|
||||
deployment_name: Name of the deployment.
|
||||
ai_model_type: The type of OpenAI model to deploy.
|
||||
endpoint: The specific endpoint URL for the deployment.
|
||||
base_url: The base URL for Azure services.
|
||||
api_version: Azure API version. Defaults to the defined DEFAULT_AZURE_API_VERSION.
|
||||
|
||||
@@ -140,3 +140,9 @@ class MiddlewareException(AgentFrameworkException):
|
||||
"""An error occurred during middleware execution."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ContentError(AgentFrameworkException):
|
||||
"""An error occurred while processing content."""
|
||||
|
||||
pass
|
||||
|
||||
@@ -825,7 +825,7 @@ def _trace_get_response(
|
||||
) -> "ChatResponse":
|
||||
global OBSERVABILITY_SETTINGS
|
||||
if not OBSERVABILITY_SETTINGS.ENABLED:
|
||||
# If model diagnostics are not enabled, just return the completion
|
||||
# If model_id diagnostics are not enabled, just return the completion
|
||||
return await func(
|
||||
self,
|
||||
messages=messages,
|
||||
@@ -836,7 +836,7 @@ def _trace_get_response(
|
||||
if "operation_duration_histogram" not in self.additional_properties:
|
||||
self.additional_properties["operation_duration_histogram"] = _get_duration_histogram()
|
||||
model_id = (
|
||||
kwargs.get("model")
|
||||
kwargs.get("model_id")
|
||||
or (chat_options.model_id if (chat_options := kwargs.get("chat_options")) else None)
|
||||
or getattr(self, "model_id", None)
|
||||
)
|
||||
@@ -848,7 +848,7 @@ def _trace_get_response(
|
||||
attributes = _get_span_attributes(
|
||||
operation_name=OtelAttr.CHAT_COMPLETION_OPERATION,
|
||||
provider_name=provider_name,
|
||||
model_id=model_id,
|
||||
model=model_id,
|
||||
service_url=service_url,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -923,7 +923,7 @@ def _trace_get_streaming_response(
|
||||
self.additional_properties["operation_duration_histogram"] = _get_duration_histogram()
|
||||
|
||||
model_id = (
|
||||
kwargs.get("model")
|
||||
kwargs.get("model_id")
|
||||
or (chat_options.model_id if (chat_options := kwargs.get("chat_options")) else None)
|
||||
or getattr(self, "model_id", None)
|
||||
)
|
||||
@@ -935,7 +935,7 @@ def _trace_get_streaming_response(
|
||||
attributes = _get_span_attributes(
|
||||
operation_name=OtelAttr.CHAT_COMPLETION_OPERATION,
|
||||
provider_name=provider_name,
|
||||
model_id=model_id,
|
||||
model=model_id,
|
||||
service_url=service_url,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -1346,7 +1346,7 @@ def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
|
||||
attributes[SpanAttributes.LLM_SYSTEM] = system_name
|
||||
if provider_name := kwargs.get("provider_name"):
|
||||
attributes[OtelAttr.PROVIDER_NAME] = provider_name
|
||||
attributes[SpanAttributes.LLM_REQUEST_MODEL] = kwargs.get("model_id", "unknown")
|
||||
attributes[SpanAttributes.LLM_REQUEST_MODEL] = kwargs.get("model", "unknown")
|
||||
if service_url := kwargs.get("service_url"):
|
||||
attributes[OtelAttr.ADDRESS] = service_url
|
||||
if conversation_id := kwargs.get("conversation_id", chat_options.conversation_id):
|
||||
|
||||
@@ -154,7 +154,7 @@ class OpenAIBaseChatClient(OpenAIBase, BaseChatClient):
|
||||
|
||||
def _prepare_options(self, messages: MutableSequence[ChatMessage], chat_options: ChatOptions) -> dict[str, Any]:
|
||||
# Preprocess web search tool if it exists
|
||||
options_dict = chat_options.to_provider_settings()
|
||||
options_dict = chat_options.to_dict(exclude={"type"})
|
||||
instructions = options_dict.pop("instructions", None)
|
||||
if instructions:
|
||||
messages = [ChatMessage(role="system", text=instructions), *messages]
|
||||
@@ -172,14 +172,20 @@ class OpenAIBaseChatClient(OpenAIBase, BaseChatClient):
|
||||
options_dict.pop("parallel_tool_calls", None)
|
||||
options_dict.pop("tool_choice", None)
|
||||
|
||||
if "model" not in options_dict:
|
||||
if "model_id" not in options_dict:
|
||||
options_dict["model"] = self.model_id
|
||||
else:
|
||||
options_dict["model"] = options_dict.pop("model_id")
|
||||
if (
|
||||
chat_options.response_format
|
||||
and isinstance(chat_options.response_format, type)
|
||||
and issubclass(chat_options.response_format, BaseModel)
|
||||
):
|
||||
options_dict["response_format"] = type_to_response_format_param(chat_options.response_format)
|
||||
if additional_properties := options_dict.pop("additional_properties", None):
|
||||
for key, value in additional_properties.items():
|
||||
if value is not None:
|
||||
options_dict[key] = value
|
||||
return options_dict
|
||||
|
||||
def _create_chat_response(self, response: ChatCompletion, chat_options: ChatOptions) -> "ChatResponse":
|
||||
|
||||
@@ -300,27 +300,26 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
|
||||
|
||||
def _prepare_options(self, messages: MutableSequence[ChatMessage], chat_options: ChatOptions) -> dict[str, Any]:
|
||||
"""Take ChatOptions and create the specific options for Responses API."""
|
||||
options_dict: dict[str, Any] = {}
|
||||
|
||||
if chat_options.max_tokens is not None:
|
||||
options_dict["max_output_tokens"] = chat_options.max_tokens
|
||||
|
||||
if chat_options.temperature is not None:
|
||||
options_dict["temperature"] = chat_options.temperature
|
||||
|
||||
if chat_options.top_p is not None:
|
||||
options_dict["top_p"] = chat_options.top_p
|
||||
|
||||
if chat_options.user is not None:
|
||||
options_dict["user"] = chat_options.user
|
||||
|
||||
# messages
|
||||
if instructions := options_dict.pop("instructions", None):
|
||||
messages = [ChatMessage(role="system", text=instructions), *messages]
|
||||
request_input = self._prepare_chat_messages_for_request(messages)
|
||||
if not request_input:
|
||||
raise ServiceInvalidRequestError("Messages are required for chat completions")
|
||||
options_dict["input"] = request_input
|
||||
options_dict: dict[str, Any] = chat_options.to_dict(
|
||||
exclude={
|
||||
"type",
|
||||
"response_format", # handled in inner get methods
|
||||
"presence_penalty", # not supported
|
||||
"frequency_penalty", # not supported
|
||||
"logit_bias", # not supported
|
||||
"seed", # not supported
|
||||
"stop", # not supported
|
||||
}
|
||||
)
|
||||
translations = {
|
||||
"model_id": "model",
|
||||
"allow_multiple_tool_calls": "parallel_tool_calls",
|
||||
"conversation_id": "previous_response_id",
|
||||
"max_tokens": "max_output_tokens",
|
||||
}
|
||||
for old_key, new_key in translations.items():
|
||||
if old_key in options_dict and old_key != new_key:
|
||||
options_dict[new_key] = options_dict.pop(old_key)
|
||||
|
||||
# tools
|
||||
if chat_options.tools is None:
|
||||
@@ -328,13 +327,23 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
|
||||
else:
|
||||
options_dict["tools"] = self._tools_to_response_tools(chat_options.tools)
|
||||
|
||||
# other settings
|
||||
options_dict["store"] = chat_options.store is True
|
||||
|
||||
if chat_options.conversation_id:
|
||||
options_dict["previous_response_id"] = chat_options.conversation_id
|
||||
if "model" not in options_dict:
|
||||
# model id
|
||||
if not options_dict.get("model"):
|
||||
options_dict["model"] = self.model_id
|
||||
|
||||
# messages
|
||||
request_input = self._prepare_chat_messages_for_request(messages)
|
||||
if not request_input:
|
||||
raise ServiceInvalidRequestError("Messages are required for chat completions")
|
||||
options_dict["input"] = request_input
|
||||
|
||||
# additional provider specific settings
|
||||
if additional_properties := options_dict.pop("additional_properties", None):
|
||||
for key, value in additional_properties.items():
|
||||
if value is not None:
|
||||
options_dict[key] = value
|
||||
if "store" not in options_dict:
|
||||
options_dict["store"] = False
|
||||
return options_dict
|
||||
|
||||
def _prepare_chat_messages_for_request(self, chat_messages: Sequence[ChatMessage]) -> list[dict[str, Any]]:
|
||||
@@ -630,6 +639,11 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
|
||||
additional_properties=additional_properties,
|
||||
)
|
||||
)
|
||||
if hasattr(item, "summary") and item.summary:
|
||||
for summary in item.summary:
|
||||
contents.append(
|
||||
TextReasoningContent(text=summary.text, raw_representation=summary) # type: ignore[arg-type]
|
||||
)
|
||||
case "code_interpreter_call": # ResponseOutputCodeInterpreterCall
|
||||
if hasattr(item, "outputs") and item.outputs:
|
||||
for code_output in item.outputs:
|
||||
|
||||
@@ -238,7 +238,7 @@ async def test_chat_client_observability(mock_chat_client, span_exporter: InMemo
|
||||
|
||||
messages = [ChatMessage(role=Role.USER, text="Test message")]
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, model="Test")
|
||||
response = await client.get_response(messages=messages, model_id="Test")
|
||||
assert response is not None
|
||||
spans = span_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
@@ -263,7 +263,7 @@ async def test_chat_client_streaming_observability(
|
||||
span_exporter.clear()
|
||||
# Collect all yielded updates
|
||||
updates = []
|
||||
async for update in client.get_streaming_response(messages=messages, model="Test"):
|
||||
async for update in client.get_streaming_response(messages=messages, model_id="Test"):
|
||||
updates.append(update)
|
||||
|
||||
# Verify we got the expected updates, this shouldn't be dependent on otel
|
||||
|
||||
@@ -4,13 +4,12 @@ from collections.abc import AsyncIterable
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic import BaseModel
|
||||
from pytest import fixture, mark, raises
|
||||
|
||||
from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AIFunction,
|
||||
BaseContent,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
@@ -37,7 +36,7 @@ from agent_framework import (
|
||||
UsageDetails,
|
||||
ai_function,
|
||||
)
|
||||
from agent_framework.exceptions import AdditionItemMismatch
|
||||
from agent_framework.exceptions import AdditionItemMismatch, ContentError
|
||||
|
||||
|
||||
@fixture
|
||||
@@ -451,7 +450,8 @@ def test_ai_content_serialization(content_type: type[BaseContent], args: dict):
|
||||
else:
|
||||
# Normal attribute checking for other content types
|
||||
for key, value in args.items():
|
||||
assert getattr(deserialized, key) == value
|
||||
if value:
|
||||
assert getattr(deserialized, key) == value
|
||||
|
||||
# For now, skip the TestModel validation since it still uses Pydantic
|
||||
# This would need to be updated when we migrate more classes
|
||||
@@ -772,53 +772,11 @@ def test_chat_options_init() -> None:
|
||||
assert options.model_id is None
|
||||
|
||||
|
||||
def test_chat_options_init_with_args(ai_function_tool, ai_tool) -> None:
|
||||
options = ChatOptions(
|
||||
model_id="gpt-4",
|
||||
max_tokens=1024,
|
||||
temperature=0.7,
|
||||
top_p=0.9,
|
||||
presence_penalty=0.0,
|
||||
frequency_penalty=0.0,
|
||||
user="user-123",
|
||||
tools=[ai_function_tool, ai_tool],
|
||||
tool_choice="required",
|
||||
additional_properties={"custom": True},
|
||||
logit_bias={"a": 1},
|
||||
metadata={"m": "v"},
|
||||
)
|
||||
assert options.model_id == "gpt-4"
|
||||
assert options.max_tokens == 1024
|
||||
assert options.temperature == 0.7
|
||||
assert options.top_p == 0.9
|
||||
assert options.presence_penalty == 0.0
|
||||
assert options.frequency_penalty == 0.0
|
||||
assert options.user == "user-123"
|
||||
for tool in options.tools:
|
||||
assert isinstance(tool, ToolProtocol)
|
||||
assert tool.name is not None
|
||||
assert tool.description is not None
|
||||
if isinstance(tool, AIFunction):
|
||||
assert tool.parameters() is not None
|
||||
|
||||
settings = options.to_provider_settings()
|
||||
assert settings["model"] == "gpt-4" # uses alias
|
||||
assert settings["tool_choice"] == "required" # serialized via model_serializer
|
||||
assert settings["custom"] is True # from additional_properties
|
||||
assert "additional_properties" not in settings
|
||||
|
||||
|
||||
def test_chat_options_tool_choice_validation_errors():
|
||||
with raises((ValidationError, TypeError)):
|
||||
with raises((ContentError, TypeError)):
|
||||
ChatOptions(tool_choice="invalid-choice")
|
||||
|
||||
|
||||
def test_chat_options_tool_choice_excluded_when_no_tools():
|
||||
options = ChatOptions(tool_choice="auto")
|
||||
settings = options.to_provider_settings()
|
||||
assert "tool_choice" not in settings
|
||||
|
||||
|
||||
def test_chat_options_and(ai_function_tool, ai_tool) -> None:
|
||||
options1 = ChatOptions(model_id="gpt-4o", tools=[ai_function_tool], logit_bias={"x": 1}, metadata={"a": "b"})
|
||||
options2 = ChatOptions(model_id="gpt-4.1", tools=[ai_tool], additional_properties={"p": 1})
|
||||
@@ -1059,69 +1017,6 @@ def test_chat_tool_mode_eq_with_string():
|
||||
assert ToolMode.AUTO == "auto"
|
||||
|
||||
|
||||
def test_chat_options_tool_choice_dict_mapping(ai_tool):
|
||||
opts = ChatOptions(tool_choice={"mode": "required", "required_function_name": "fn"}, tools=[ai_tool])
|
||||
assert isinstance(opts.tool_choice, ToolMode)
|
||||
assert opts.tool_choice.mode == "required"
|
||||
assert opts.tool_choice.required_function_name == "fn"
|
||||
# provider settings serialize to just the mode
|
||||
settings = opts.to_provider_settings()
|
||||
assert settings["tool_choice"] == "required"
|
||||
|
||||
|
||||
def test_chat_options_to_provider_settings_with_falsy_values():
|
||||
"""Test that falsy values (except None) are included in provider settings."""
|
||||
options = ChatOptions(
|
||||
temperature=0.0, # falsy but not None
|
||||
top_p=0.0, # falsy but not None
|
||||
presence_penalty=False, # falsy but not None
|
||||
frequency_penalty=None, # None - should be excluded
|
||||
additional_properties={"empty_string": "", "zero": 0, "false_flag": False, "none_value": None},
|
||||
)
|
||||
|
||||
settings = options.to_provider_settings()
|
||||
|
||||
# Falsy values that are not None should be included
|
||||
assert "temperature" in settings
|
||||
assert isinstance(settings["temperature"], float)
|
||||
assert settings["temperature"] == 0.0
|
||||
assert "top_p" in settings
|
||||
assert isinstance(settings["top_p"], float)
|
||||
assert settings["top_p"] == 0.0
|
||||
assert "presence_penalty" in settings
|
||||
assert isinstance(settings["presence_penalty"], float) # converted to float
|
||||
assert settings["presence_penalty"] == 0.0
|
||||
|
||||
# None values should be excluded
|
||||
assert "frequency_penalty" not in settings
|
||||
|
||||
# Additional properties - falsy values should always be included
|
||||
assert "empty_string" in settings
|
||||
assert settings["empty_string"] == ""
|
||||
assert "zero" in settings
|
||||
assert settings["zero"] == 0
|
||||
assert "false_flag" in settings
|
||||
assert settings["false_flag"] is False
|
||||
assert "none_value" in settings
|
||||
assert settings["none_value"] is None
|
||||
|
||||
|
||||
def test_chat_options_empty_logit_bias_and_metadata_excluded():
|
||||
"""Test that empty logit_bias and metadata are excluded from provider settings."""
|
||||
options = ChatOptions(
|
||||
model_id="gpt-4o",
|
||||
logit_bias={}, # empty dict should be excluded
|
||||
metadata={}, # empty dict should be excluded
|
||||
)
|
||||
|
||||
settings = options.to_provider_settings()
|
||||
|
||||
# Empty logit_bias and metadata should be excluded
|
||||
assert "logit_bias" not in settings
|
||||
assert "metadata" not in settings
|
||||
assert settings["model"] == "gpt-4o"
|
||||
|
||||
|
||||
# region AgentRunResponse
|
||||
|
||||
|
||||
@@ -1905,7 +1800,8 @@ def test_content_roundtrip_serialization(content_class: type[BaseContent], init_
|
||||
elif isinstance(value, dict) and hasattr(reconstructed_value, "to_dict"):
|
||||
# Compare the dict with the serialized form of the object, excluding 'type' key
|
||||
reconstructed_dict = reconstructed_value.to_dict()
|
||||
assert len(reconstructed_dict) == len(value)
|
||||
if value:
|
||||
assert len(reconstructed_dict) == len(value)
|
||||
else:
|
||||
assert reconstructed_value == value
|
||||
|
||||
|
||||
@@ -71,9 +71,7 @@ async def test_cmc(
|
||||
chat_history.append(ChatMessage(role="user", text="hello world"))
|
||||
|
||||
openai_chat_completion = OpenAIChatClient()
|
||||
await openai_chat_completion.get_response(
|
||||
messages=chat_history,
|
||||
)
|
||||
await openai_chat_completion.get_response(messages=chat_history)
|
||||
mock_create.assert_awaited_once_with(
|
||||
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
|
||||
stream=False,
|
||||
@@ -189,6 +187,26 @@ async def test_cmc_general_exception(
|
||||
)
|
||||
|
||||
|
||||
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||
async def test_cmc_additional_properties(
|
||||
mock_create: AsyncMock,
|
||||
chat_history: list[ChatMessage],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(ChatMessage(role="user", text="hello world"))
|
||||
|
||||
openai_chat_completion = OpenAIChatClient()
|
||||
await openai_chat_completion.get_response(messages=chat_history, additional_properties={"reasoning_effort": "low"})
|
||||
mock_create.assert_awaited_once_with(
|
||||
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
|
||||
stream=False,
|
||||
messages=openai_chat_completion._prepare_chat_history_for_request(chat_history), # type: ignore
|
||||
reasoning_effort="low",
|
||||
)
|
||||
|
||||
|
||||
# region Streaming
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from openai import BadRequestError
|
||||
from openai.types.responses.response_reasoning_item import Summary
|
||||
from openai.types.responses.response_reasoning_summary_text_delta_event import ResponseReasoningSummaryTextDeltaEvent
|
||||
from openai.types.responses.response_reasoning_summary_text_done_event import ResponseReasoningSummaryTextDoneEvent
|
||||
from openai.types.responses.response_reasoning_text_delta_event import ResponseReasoningTextDeltaEvent
|
||||
@@ -209,7 +210,7 @@ def test_get_response_with_all_parameters() -> None:
|
||||
instructions="You are a helpful assistant",
|
||||
max_tokens=100,
|
||||
parallel_tool_calls=True,
|
||||
model="gpt-4",
|
||||
model_id="gpt-4",
|
||||
previous_response_id="prev-123",
|
||||
reasoning={"chain_of_thought": "enabled"},
|
||||
service_tier="auto",
|
||||
@@ -535,13 +536,13 @@ def test_response_content_creation_with_reasoning() -> None:
|
||||
mock_reasoning_item = MagicMock()
|
||||
mock_reasoning_item.type = "reasoning"
|
||||
mock_reasoning_item.content = [mock_reasoning_content]
|
||||
mock_reasoning_item.summary = ["Summary"]
|
||||
mock_reasoning_item.summary = [Summary(text="Summary", type="summary_text")]
|
||||
|
||||
mock_response.output = [mock_reasoning_item]
|
||||
|
||||
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
|
||||
|
||||
assert len(response.messages[0].contents) == 1
|
||||
assert len(response.messages[0].contents) == 2
|
||||
assert isinstance(response.messages[0].contents[0], TextReasoningContent)
|
||||
assert response.messages[0].contents[0].text == "Reasoning step"
|
||||
|
||||
@@ -1536,11 +1537,9 @@ async def test_openai_responses_client_agent_chat_options_run_level() -> None:
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
response = await agent.run(
|
||||
"Provide a brief, helpful response.",
|
||||
max_tokens=100,
|
||||
temperature=0.7,
|
||||
top_p=0.9,
|
||||
seed=123,
|
||||
"Provide a brief, helpful response about why the sky blue is.",
|
||||
max_tokens=600,
|
||||
model_id="gpt-4o",
|
||||
user="comprehensive-test-user",
|
||||
tools=[get_weather],
|
||||
tool_choice="auto",
|
||||
@@ -2077,7 +2076,6 @@ def test_prepare_options_store_parameter_handling() -> None:
|
||||
chat_options = ChatOptions(store=False, conversation_id="")
|
||||
options = client._prepare_options(messages, chat_options) # type: ignore
|
||||
assert options["store"] is False
|
||||
assert "previous_response_id" not in options
|
||||
|
||||
chat_options = ChatOptions(store=None, conversation_id=None)
|
||||
options = client._prepare_options(messages, chat_options) # type: ignore
|
||||
|
||||
@@ -189,7 +189,7 @@ class EntityDiscovery:
|
||||
framework="agent_framework",
|
||||
tools=[str(tool) for tool in (tools_list or [])],
|
||||
instructions=instructions,
|
||||
model=model,
|
||||
model_id=model,
|
||||
chat_client_type=chat_client_type,
|
||||
context_providers=context_providers_list,
|
||||
middleware=middleware_list,
|
||||
@@ -547,7 +547,7 @@ class EntityDiscovery:
|
||||
description=description,
|
||||
tools=tools_union,
|
||||
instructions=instructions,
|
||||
model=model,
|
||||
model_id=model,
|
||||
chat_client_type=chat_client_type,
|
||||
context_providers=context_providers_list,
|
||||
middleware=middleware_list,
|
||||
|
||||
@@ -67,7 +67,7 @@ class SessionManager:
|
||||
logger.debug(f"Closed session: {session_id}")
|
||||
|
||||
def add_request_record(
|
||||
self, session_id: str, entity_id: str, executor_name: str, request_input: Any, model: str
|
||||
self, session_id: str, entity_id: str, executor_name: str, request_input: Any, model_id: str
|
||||
) -> str:
|
||||
"""Add a request record to a session.
|
||||
|
||||
@@ -76,7 +76,7 @@ class SessionManager:
|
||||
entity_id: ID of the entity being executed
|
||||
executor_name: Name of the executor
|
||||
request_input: Input for the request
|
||||
model: Model name
|
||||
model_id: Model name
|
||||
|
||||
Returns:
|
||||
Request ID
|
||||
@@ -91,7 +91,7 @@ class SessionManager:
|
||||
"entity_id": entity_id,
|
||||
"executor": executor_name,
|
||||
"input": request_input,
|
||||
"model": model,
|
||||
"model_id": model_id,
|
||||
"stream": True,
|
||||
}
|
||||
session["requests"].append(request_record)
|
||||
|
||||
@@ -39,7 +39,7 @@ class EntityInfo(BaseModel):
|
||||
|
||||
# Agent-specific fields (optional, populated when available)
|
||||
instructions: str | None = None
|
||||
model: str | None = None
|
||||
model_id: str | None = None
|
||||
chat_client_type: str | None = None
|
||||
context_providers: list[str] | None = None
|
||||
middleware: list[str] | None = None
|
||||
|
||||
@@ -2,41 +2,66 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import HostedCodeInterpreterTool, TextContent, TextReasoningContent, UsageContent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
OpenAI Responses Client Reasoning Example
|
||||
|
||||
This sample demonstrates advanced reasoning capabilities using OpenAI's o1 models,
|
||||
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.
|
||||
|
||||
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(
|
||||
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"}},
|
||||
)
|
||||
|
||||
|
||||
async def reasoning_example() -> None:
|
||||
"""Example of reasoning response (get results as they are generated)."""
|
||||
print("=== Reasoning Example ===")
|
||||
print("\033[92m=== Reasoning Example ===\033[0m")
|
||||
|
||||
agent = OpenAIResponsesClient(model_id="gpt-5").create_agent(
|
||||
name="MathHelper",
|
||||
instructions="You are a personal math tutor. When asked a math question, "
|
||||
"write and run code using the python tool to answer the question.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
reasoning={"effort": "high", "summary": "detailed"},
|
||||
)
|
||||
query = "I need to solve the equation 3x + 11 = 14 and I need to prove the pythagorean theorem. Can you help me?"
|
||||
print(f"User: {query}")
|
||||
print(f"{agent.name}: ", end="", flush=True)
|
||||
response = await agent.run(query)
|
||||
for msg in response.messages:
|
||||
if msg.contents:
|
||||
for content in msg.contents:
|
||||
if content.type == "text_reasoning":
|
||||
print(f"\033[94m{content.text}\033[0m", end="", flush=True)
|
||||
elif content.type == "text":
|
||||
print(content.text, end="", flush=True)
|
||||
print("\n")
|
||||
if response.usage_details:
|
||||
print(f"Usage: {response.usage_details}")
|
||||
|
||||
query = "I need to solve the equation 3x + 11 = 14. Can you help me?"
|
||||
|
||||
async def streaming_reasoning_example() -> None:
|
||||
"""Example of reasoning response (get results as they are generated)."""
|
||||
print("\033[92m=== Streaming Reasoning Example ===\033[0m")
|
||||
|
||||
query = "I need to solve the equation 3x + 11 = 14 and I need to prove the pythagorean theorem. Can you help me?"
|
||||
print(f"User: {query}")
|
||||
print(f"{agent.name}: ", end="", flush=True)
|
||||
usage = None
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.contents:
|
||||
for content in chunk.contents:
|
||||
if isinstance(content, TextReasoningContent):
|
||||
print(f"\033[97m{content.text}\033[0m", end="", flush=True)
|
||||
elif isinstance(content, TextContent):
|
||||
if content.type == "text_reasoning":
|
||||
print(f"\033[94m{content.text}\033[0m", end="", flush=True)
|
||||
elif content.type == "text":
|
||||
print(content.text, end="", flush=True)
|
||||
elif isinstance(content, UsageContent):
|
||||
elif content.type == "usage":
|
||||
usage = content
|
||||
print("\n")
|
||||
if usage:
|
||||
@@ -44,9 +69,10 @@ async def reasoning_example() -> None:
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Basic OpenAI Responses Reasoning Agent Example ===")
|
||||
print("\033[92m=== Basic OpenAI Responses Reasoning Agent Example ===\033[0m")
|
||||
|
||||
await reasoning_example()
|
||||
await streaming_reasoning_example()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user