From d152c9c51dd8eb56f8c509c7ed6be8846a940374 Mon Sep 17 00:00:00 2001 From: leykun10 Date: Wed, 12 Mar 2025 15:07:56 +0300 Subject: [PATCH 01/13] fix: replace undefined variable name in documentation code snippet --- docs/running_agents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/running_agents.md b/docs/running_agents.md index a2f137cf..32abf9d5 100644 --- a/docs/running_agents.md +++ b/docs/running_agents.md @@ -78,7 +78,7 @@ async def main(): # San Francisco # Second turn - new_input = output.to_input_list() + [{"role": "user", "content": "What state is it in?"}] + new_input = result.to_input_list() + [{"role": "user", "content": "What state is it in?"}] result = await Runner.run(agent, new_input) print(result.final_output) # California From 6a32dab4c699d61e87de76cb1b6882df4102bf66 Mon Sep 17 00:00:00 2001 From: Harsh Jain <96345745+HarshJa1n@users.noreply.github.com> Date: Wed, 12 Mar 2025 18:10:28 +0530 Subject: [PATCH 02/13] Fix guardrail trigger in input_guardrails.py Remove the `not` keyword from the `tripwire_triggered` parameter in the `math_guardrail` function in `examples/agent_patterns/input_guardrails.py`. The not keyword prevented the guardrail from being triggered, which defeats the purpose of the example. --- examples/agent_patterns/input_guardrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/agent_patterns/input_guardrails.py b/examples/agent_patterns/input_guardrails.py index 62591886..8c8e1826 100644 --- a/examples/agent_patterns/input_guardrails.py +++ b/examples/agent_patterns/input_guardrails.py @@ -53,7 +53,7 @@ async def math_guardrail( return GuardrailFunctionOutput( output_info=final_output, - tripwire_triggered=not final_output.is_math_homework, + tripwire_triggered=final_output.is_math_homework, ) From cafa77fc750dbdfff2e02a25bd647c9abe3371da Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Wed, 12 Mar 2025 22:45:17 +0900 Subject: [PATCH 03/13] chore: update guardrail.py minor fix --- src/agents/guardrail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/guardrail.py b/src/agents/guardrail.py index fcae0b8a..5bebcd66 100644 --- a/src/agents/guardrail.py +++ b/src/agents/guardrail.py @@ -86,7 +86,7 @@ class InputGuardrail(Generic[TContext]): [RunContextWrapper[TContext], Agent[Any], str | list[TResponseInputItem]], MaybeAwaitable[GuardrailFunctionOutput], ] - """A function that receives the the agent input and the context, and returns a + """A function that receives the agent input and the context, and returns a `GuardrailResult`. The result marks whether the tripwire was triggered, and can optionally include information about the guardrail's output. """ From b5bbc060d3867b83a1a55fbf12161fa26f94488f Mon Sep 17 00:00:00 2001 From: Muhammad Junaid Date: Wed, 12 Mar 2025 21:10:03 +0500 Subject: [PATCH 04/13] fix: support assistant role in message conversion - The _Converter.items_to_messages method was incorrectly rejecting 'assistant' as a valid role in conversation messages, causing runtime errors when processing standard chat completion message formats. - This fix enables proper handling of complete conversation contexts that include both user and assistant messages. --- src/agents/models/openai_chatcompletions.py | 7 ++++ .../test_openai_chatcompletions_converter.py | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/agents/models/openai_chatcompletions.py b/src/agents/models/openai_chatcompletions.py index a7340d05..c20f5bb9 100644 --- a/src/agents/models/openai_chatcompletions.py +++ b/src/agents/models/openai_chatcompletions.py @@ -808,6 +808,13 @@ class _Converter: "content": cls.extract_text_content(content), } result.append(msg_developer) + elif role == "assistant": + flush_assistant_message() + msg_assistant: ChatCompletionAssistantMessageParam = { + "role": "assistant", + "content": cls.extract_text_content(content), + } + result.append(msg_assistant) else: raise UserError(f"Unexpected role in easy_input_message: {role}") diff --git a/tests/test_openai_chatcompletions_converter.py b/tests/test_openai_chatcompletions_converter.py index 8cf07d7c..47bf47c2 100644 --- a/tests/test_openai_chatcompletions_converter.py +++ b/tests/test_openai_chatcompletions_converter.py @@ -393,3 +393,35 @@ def test_unknown_object_errors(): with pytest.raises(UserError, match="Unhandled item type or structure"): # Purposely ignore the type error _Converter.items_to_messages([TestObject()]) # type: ignore + + +def test_assistant_messages_in_history(): + """ + Test that assistant messages are added to the history. + """ + messages = _Converter.items_to_messages( + [ + { + "role": "user", + "content": "Hello", + }, + { + "role": "assistant", + "content": "Hello?", + }, + { + "role": "user", + "content": "What was my Name?", + }, + ] + ) + + # OUTPUT is [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hello?'}, {'role': 'user', 'content': 'What was my Name?'}] + assert messages == [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hello?'}, {'role': 'user', 'content': 'What was my Name?'}] + assert len(messages) == 3 + assert messages[0]["role"] == "user" + assert messages[0]["content"] == "Hello" + assert messages[1]["role"] == "assistant" + assert messages[1]["content"] == "Hello?" + assert messages[2]["role"] == "user" + assert messages[2]["content"] == "What was my Name?" From 737dcedc79431afeebdb4b4c39e409922d3ddb32 Mon Sep 17 00:00:00 2001 From: jhills20 Date: Wed, 12 Mar 2025 11:15:42 -0700 Subject: [PATCH 05/13] use @function_tool decorator in docs --- docs/agents.md | 3 ++- docs/context.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 9b6264b5..17589b3d 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -13,6 +13,7 @@ The most common properties of an agent you'll configure are: ```python from agents import Agent, ModelSettings, function_tool +@function_tool def get_weather(city: str) -> str: return f"The weather in {city} is sunny" @@ -20,7 +21,7 @@ agent = Agent( name="Haiku agent", instructions="Always respond in haiku form", model="o3-mini", - tools=[function_tool(get_weather)], + tools=[get_weather], ) ``` diff --git a/docs/context.md b/docs/context.md index 5dcacebe..69c43fbb 100644 --- a/docs/context.md +++ b/docs/context.md @@ -36,6 +36,7 @@ class UserInfo: # (1)! name: str uid: int +@function_tool async def fetch_user_age(wrapper: RunContextWrapper[UserInfo]) -> str: # (2)! return f"User {wrapper.context.name} is 47 years old" @@ -44,7 +45,7 @@ async def main(): agent = Agent[UserInfo]( # (4)! name="Assistant", - tools=[function_tool(fetch_user_age)], + tools=[fetch_user_age], ) result = await Runner.run( From fafeeac958c96975fa721592328ed236d34f9e75 Mon Sep 17 00:00:00 2001 From: Muhammad Junaid Date: Thu, 13 Mar 2025 00:20:59 +0500 Subject: [PATCH 06/13] refactor: update formatting in test_assistant_messages_in_history - Enhanced the readability of the test case by reformatting the expected output and input message structures. - This change maintains the same functionality while making the test code cleaner and easier to understand. --- .../test_openai_chatcompletions_converter.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/tests/test_openai_chatcompletions_converter.py b/tests/test_openai_chatcompletions_converter.py index 47bf47c2..73acb8ab 100644 --- a/tests/test_openai_chatcompletions_converter.py +++ b/tests/test_openai_chatcompletions_converter.py @@ -400,24 +400,27 @@ def test_assistant_messages_in_history(): Test that assistant messages are added to the history. """ messages = _Converter.items_to_messages( - [ - { - "role": "user", - "content": "Hello", - }, - { - "role": "assistant", - "content": "Hello?", - }, - { - "role": "user", - "content": "What was my Name?", - }, - ] - ) - - # OUTPUT is [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hello?'}, {'role': 'user', 'content': 'What was my Name?'}] - assert messages == [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hello?'}, {'role': 'user', 'content': 'What was my Name?'}] + [ + { + "role": "user", + "content": "Hello", + }, + { + "role": "assistant", + "content": "Hello?", + }, + { + "role": "user", + "content": "What was my Name?", + }, + ] + ) + + assert messages == [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": "Hello?"}, + {"role": "user", "content": "What was my Name?"}, + ] assert len(messages) == 3 assert messages[0]["role"] == "user" assert messages[0]["content"] == "Hello" From 4f6276c8c7129f312123bf7ef1d4056e1eaa1789 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Wed, 12 Mar 2025 13:08:00 -0700 Subject: [PATCH 07/13] Remove duplicated code --- src/agents/result.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/agents/result.py b/src/agents/result.py index 56838273..6e806b72 100644 --- a/src/agents/result.py +++ b/src/agents/result.py @@ -216,5 +216,3 @@ class RunResultStreaming(RunResultBase): if self._output_guardrails_task and not self._output_guardrails_task.done(): self._output_guardrails_task.cancel() - self._output_guardrails_task.cancel() - self._output_guardrails_task.cancel() From 4f842cfec2da506054a3dad6641d7413456430de Mon Sep 17 00:00:00 2001 From: Dmitry Pimenov Date: Wed, 12 Mar 2025 13:15:53 -0700 Subject: [PATCH 08/13] adding Keywords AI as a trace processor --- README.md | 2 +- docs/tracing.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 90fea502..d3f25fa7 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ The Agents SDK is designed to be highly flexible, allowing you to model a wide r ## Tracing -The Agents SDK automatically traces your agent runs, making it easy to track and debug the behavior of your agents. Tracing is extensible by design, supporting custom spans and a wide variety of external destinations, including [Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#openai-agents), [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk), and [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#openai-agents-sdk). For more details about how to customize or disable tracing, see [Tracing](http://openai.github.io/openai-agents-python/tracing). +The Agents SDK automatically traces your agent runs, making it easy to track and debug the behavior of your agents. Tracing is extensible by design, supporting custom spans and a wide variety of external destinations, including [Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#openai-agents), [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk), [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#openai-agents-sdk), and [Keywords AI](https://docs.keywordsai.co/integration/development-frameworks/openai-agent). For more details about how to customize or disable tracing, see [Tracing](http://openai.github.io/openai-agents-python/tracing). ## Development (only needed if you need to edit the SDK/examples) diff --git a/docs/tracing.md b/docs/tracing.md index da0d536f..fa5d5224 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -93,3 +93,4 @@ External trace processors include: - [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#openai-agents-sdk) - [Pydantic Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#openai-agents) - [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk) +- [Keywords AI](https://docs.keywordsai.co/integration/development-frameworks/openai-agent) From d737ad0e1b6a13aecf177185c49a987f6b5f2d85 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Wed, 12 Mar 2025 13:35:10 -0700 Subject: [PATCH 09/13] Add max_tokens and documentation to model settings --- src/agents/model_settings.py | 20 ++++++++++++++++++++ src/agents/models/openai_chatcompletions.py | 1 + src/agents/models/openai_responses.py | 1 + 3 files changed, 22 insertions(+) diff --git a/src/agents/model_settings.py b/src/agents/model_settings.py index d8178ae3..cc4b6cb6 100644 --- a/src/agents/model_settings.py +++ b/src/agents/model_settings.py @@ -10,15 +10,34 @@ class ModelSettings: This class holds optional model configuration parameters (e.g. temperature, top_p, penalties, truncation, etc.). + + Not all models/providers support all of these parameters, so please check the API documentation + for the specific model and provider you are using. """ temperature: float | None = None + """The temperature to use when calling the model.""" + top_p: float | None = None + """The top_p to use when calling the model.""" + frequency_penalty: float | None = None + """The frequency penalty to use when calling the model.""" + presence_penalty: float | None = None + """The presence penalty to use when calling the model.""" + tool_choice: Literal["auto", "required", "none"] | str | None = None + """The tool choice to use when calling the model.""" + parallel_tool_calls: bool | None = False + """Whether to use parallel tool calls when calling the model.""" + truncation: Literal["auto", "disabled"] | None = None + """The truncation strategy to use when calling the model.""" + + max_tokens: int | None = None + """The maximum number of output tokens to generate.""" def resolve(self, override: ModelSettings | None) -> ModelSettings: """Produce a new ModelSettings by overlaying any non-None values from the @@ -33,4 +52,5 @@ class ModelSettings: tool_choice=override.tool_choice or self.tool_choice, parallel_tool_calls=override.parallel_tool_calls or self.parallel_tool_calls, truncation=override.truncation or self.truncation, + max_tokens=override.max_tokens or self.max_tokens, ) diff --git a/src/agents/models/openai_chatcompletions.py b/src/agents/models/openai_chatcompletions.py index c20f5bb9..50e27fc4 100644 --- a/src/agents/models/openai_chatcompletions.py +++ b/src/agents/models/openai_chatcompletions.py @@ -503,6 +503,7 @@ class OpenAIChatCompletionsModel(Model): top_p=self._non_null_or_not_given(model_settings.top_p), frequency_penalty=self._non_null_or_not_given(model_settings.frequency_penalty), presence_penalty=self._non_null_or_not_given(model_settings.presence_penalty), + max_tokens=self._non_null_or_not_given(model_settings.max_tokens), tool_choice=tool_choice, response_format=response_format, parallel_tool_calls=parallel_tool_calls, diff --git a/src/agents/models/openai_responses.py b/src/agents/models/openai_responses.py index e060fb8e..86c82153 100644 --- a/src/agents/models/openai_responses.py +++ b/src/agents/models/openai_responses.py @@ -235,6 +235,7 @@ class OpenAIResponsesModel(Model): temperature=self._non_null_or_not_given(model_settings.temperature), top_p=self._non_null_or_not_given(model_settings.top_p), truncation=self._non_null_or_not_given(model_settings.truncation), + max_output_tokens=self._non_null_or_not_given(model_settings.max_tokens), tool_choice=tool_choice, parallel_tool_calls=parallel_tool_calls, stream=stream, From 2acac3e0dbd41d8dbd1491d27b90bb2aab9ccde9 Mon Sep 17 00:00:00 2001 From: Dingkang Wang Date: Wed, 12 Mar 2025 14:54:11 -0700 Subject: [PATCH 10/13] fix typo in tracing.md --- docs/tracing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tracing.md b/docs/tracing.md index fa5d5224..357a8734 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -50,7 +50,7 @@ async def main(): with trace("Joke workflow"): # (1)! first_result = await Runner.run(agent, "Tell me a joke") - second_result = await Runner.run(agent, f"Rate this joke: {first_output.final_output}") + second_result = await Runner.run(agent, f"Rate this joke: {first_result.final_output}") print(f"Joke: {first_result.final_output}") print(f"Rating: {second_result.final_output}") ``` From 54f4b6e4c2b045507fca80f970cab85c61ec69df Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Wed, 12 Mar 2025 16:46:53 -0700 Subject: [PATCH 11/13] Create pull_request_template.md. --- .../pull_request_template.md. | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md. b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md. new file mode 100644 index 00000000..0fdeab1e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md. @@ -0,0 +1,18 @@ +### Summary + + + +### Test plan + + + +### Issue number + + + +### Checks + +- [ ] I've added new tests (if relevant) +- [ ] I've added/updated the relevant documentation +- [ ] I've run `make lint` and `make format` +- [ ] I've made sure tests pass From 85a3f1898334de5a43796638ad9fc739aa210bf7 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Wed, 12 Mar 2025 16:47:14 -0700 Subject: [PATCH 12/13] Rename pull_request_template.md. to pull_request_template.md --- .../{pull_request_template.md. => pull_request_template.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/PULL_REQUEST_TEMPLATE/{pull_request_template.md. => pull_request_template.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md. b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md. rename to .github/PULL_REQUEST_TEMPLATE/pull_request_template.md From a9f717b22064ffe3baa183f448996e771e1c32b5 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Wed, 12 Mar 2025 17:17:07 -0700 Subject: [PATCH 13/13] Fix streaming in chat completions --- src/agents/models/openai_chatcompletions.py | 20 +++++++++++++++++++- tests/test_openai_chatcompletions_stream.py | 5 +++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/agents/models/openai_chatcompletions.py b/src/agents/models/openai_chatcompletions.py index 50e27fc4..3543225d 100644 --- a/src/agents/models/openai_chatcompletions.py +++ b/src/agents/models/openai_chatcompletions.py @@ -51,8 +51,10 @@ from openai.types.responses import ( ResponseOutputText, ResponseRefusalDeltaEvent, ResponseTextDeltaEvent, + ResponseUsage, ) from openai.types.responses.response_input_param import FunctionCallOutput, ItemReference, Message +from openai.types.responses.response_usage import OutputTokensDetails from .. import _debug from ..agent_output import AgentOutputSchema @@ -405,7 +407,23 @@ class OpenAIChatCompletionsModel(Model): for function_call in state.function_calls.values(): outputs.append(function_call) - final_response = response.model_copy(update={"output": outputs, "usage": usage}) + final_response = response.model_copy() + final_response.output = outputs + final_response.usage = ( + ResponseUsage( + input_tokens=usage.prompt_tokens, + output_tokens=usage.completion_tokens, + total_tokens=usage.total_tokens, + output_tokens_details=OutputTokensDetails( + reasoning_tokens=usage.completion_tokens_details.reasoning_tokens + if usage.completion_tokens_details + and usage.completion_tokens_details.reasoning_tokens + else 0 + ), + ) + if usage + else None + ) yield ResponseCompletedEvent( response=final_response, diff --git a/tests/test_openai_chatcompletions_stream.py b/tests/test_openai_chatcompletions_stream.py index 2a15f7f0..7add92a6 100644 --- a/tests/test_openai_chatcompletions_stream.py +++ b/tests/test_openai_chatcompletions_stream.py @@ -107,6 +107,11 @@ async def test_stream_response_yields_events_for_text_content(monkeypatch) -> No assert isinstance(completed_resp.output[0].content[0], ResponseOutputText) assert completed_resp.output[0].content[0].text == "Hello" + assert completed_resp.usage, "usage should not be None" + assert completed_resp.usage.input_tokens == 7 + assert completed_resp.usage.output_tokens == 5 + assert completed_resp.usage.total_tokens == 12 + @pytest.mark.allow_call_model_methods @pytest.mark.asyncio