You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_assistant_agent.py 137 kB

add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
add tool_call_summary_msg_format_fct and test (#6460) ## Why are these changes needed? This change introduces support for dynamic formatting of tool call summary messages by allowing a user-defined `tool_call_summary_format_fct`. Instead of relying solely on a static string template, this function enables runtime generation of summary messages based on the specific tool call and its result. This provides greater flexibility and cleaner integration without introducing any breaking changes. ### My Use Case / Problem In my use case, I needed concise summaries for successful tool calls and detailed messages for failures. The existing static summary string didn't allow conditional formatting, which led to overly verbose success messages or inconsistent failure outputs. This change allows customizing summaries per result type, solving that limitation cleanly. ## Related issue number Closes #6426 ## Checks - [x] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de> Co-authored-by: EeS <chiyoung.song@motov.co.kr> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com> Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com> Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Add output_format to AssistantAgent for structured output (#6071) Resolves #5934 This PR adds ability for `AssistantAgent` to generate a `StructuredMessage[T]` where `T` is the content type in base model. How to use? ```python from typing import Literal from pydantic import BaseModel from autogen_agentchat.agents import AssistantAgent from autogen_ext.models.openai import OpenAIChatCompletionClient from autogen_agentchat.ui import Console # The response format for the agent as a Pydantic base model. class AgentResponse(BaseModel): thoughts: str response: Literal["happy", "sad", "neutral"] # Create an agent that uses the OpenAI GPT-4o model which supports structured output. model_client = OpenAIChatCompletionClient(model="gpt-4o") agent = AssistantAgent( "assistant", model_client=model_client, system_message="Categorize the input as happy, sad, or neutral following the JSON format.", # Setting the output format to AgentResponse to force the agent to produce a JSON string as response. output_content_type=AgentResponse, ) result = await Console(agent.run_stream(task="I am happy.")) # Check the last message in the result, validate its type, and print the thoughts and response. assert isinstance(result.messages[-1], StructuredMessage) assert isinstance(result.messages[-1].content, AgentResponse) print("Thought: ", result.messages[-1].content.thoughts) print("Response: ", result.messages[-1].content.response) await model_client.close() ``` ``` ---------- user ---------- I am happy. ---------- assistant ---------- { "thoughts": "The user explicitly states they are happy.", "response": "happy" } Thought: The user explicitly states they are happy. Response: happy ``` --------- Co-authored-by: Victor Dibia <victordibia@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
Support for external agent runtime in AgentChat (#5843) Resolves #4075 1. Introduce custom runtime parameter for all AgentChat teams (RoundRobinGroupChat, SelectorGroupChat, etc.). This is done by making sure each team's topics are isolated from other teams, and decoupling state from agent identities. Also, I removed the closure agent from the BaseGroupChat and use the group chat manager agent to relay messages to the output message queue. 2. Added unit tests to test scenarios with custom runtimes by using pytest fixture 3. Refactored existing unit tests to use ReplayChatCompletionClient with a few improvements to the client. 4. Fix a one-liner bug in AssistantAgent that caused deserialized agent to have handoffs. How to use it? ```python import asyncio from autogen_core import SingleThreadedAgentRuntime from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.teams import RoundRobinGroupChat from autogen_agentchat.conditions import TextMentionTermination from autogen_ext.models.replay import ReplayChatCompletionClient async def main() -> None: # Create a runtime runtime = SingleThreadedAgentRuntime() runtime.start() # Create a model client. model_client = ReplayChatCompletionClient( ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], ) # Create agents agent1 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent2 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") # Create a termination condition termination_condition = TextMentionTermination("10", sources=["assistant1", "assistant2"]) # Create a team team = RoundRobinGroupChat([agent1, agent2], runtime=runtime, termination_condition=termination_condition) # Run the team stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Save the state. state = await team.save_state() # Load the state to an existing team. await team.load_state(state) # Run the team again model_client.reset() stream = team.run_stream(task="Count to 10.") async for message in stream: print(message) # Create a new team, with the same agent names. agent3 = AssistantAgent("assistant1", model_client=model_client, system_message="You are a helpful assistant.") agent4 = AssistantAgent("assistant2", model_client=model_client, system_message="You are a helpful assistant.") new_team = RoundRobinGroupChat([agent3, agent4], runtime=runtime, termination_condition=termination_condition) # Load the state to the new team. await new_team.load_state(state) # Run the new team model_client.reset() new_stream = new_team.run_stream(task="Count to 10.") async for message in new_stream: print(message) # Stop the runtime await runtime.stop() asyncio.run(main()) ``` TODOs as future PRs: 1. Documentation. 2. How to handle errors in custom runtime when the agent has exception? --------- Co-authored-by: Ryan Sweet <rysweet@microsoft.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
FIX: resolving_workbench_and_tools_conflict_at_desirialize_assistant_agent (#6407) ## Why are these changes needed? Starting from AutoGen v0.5.5, tools are internally managed through `StaticWorkbench`. However, both tools and workbench were being serialized and deserialized, which caused conflicts during deserialization: • When both are restored, the constructor raises: ``` ValueError: Tools cannot be used with a workbench. ``` The changes address this issue by: 1. Removing tools from serialization/deserialization: • tools are now considered internal state of `StaticWorkbench`, and are no longer serialized. • Only workbench is serialized, ensuring consistency and avoiding duplication. 2. Ensuring logical integrity: • Since tools are not used directly after initialization, persisting them separately serves no functional purpose. • This avoids scenarios where both are populated, violating constructor constraints. Summary: This change prevents tools/workbench conflicts by fully delegating tool management to `StaticWorkbench` and avoiding unnecessary persistence of tools themselves. <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number Closes #6405 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 year ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562
  1. """Comprehensive tests for AssistantAgent functionality."""
  2. # Standard library imports
  3. import asyncio
  4. import json
  5. import os
  6. from typing import Any, List, Optional, Union, cast
  7. from unittest.mock import AsyncMock, MagicMock, patch
  8. # Third-party imports
  9. import pytest
  10. # First-party imports
  11. from autogen_agentchat.agents import AssistantAgent
  12. from autogen_agentchat.agents._assistant_agent import AssistantAgentConfig
  13. from autogen_agentchat.base import Handoff, Response, TaskResult
  14. from autogen_agentchat.messages import (
  15. BaseAgentEvent,
  16. BaseChatMessage,
  17. HandoffMessage,
  18. MemoryQueryEvent,
  19. ModelClientStreamingChunkEvent,
  20. StructuredMessage,
  21. TextMessage,
  22. ThoughtEvent,
  23. ToolCallExecutionEvent,
  24. ToolCallRequestEvent,
  25. ToolCallSummaryMessage,
  26. )
  27. from autogen_core import CancellationToken, ComponentModel, FunctionCall
  28. from autogen_core.memory import Memory, MemoryContent, UpdateContextResult
  29. from autogen_core.memory import MemoryQueryResult as MemoryQueryResultSet
  30. from autogen_core.model_context import BufferedChatCompletionContext
  31. from autogen_core.models import (
  32. AssistantMessage,
  33. CreateResult,
  34. FunctionExecutionResult,
  35. ModelFamily,
  36. RequestUsage,
  37. SystemMessage,
  38. UserMessage,
  39. )
  40. from autogen_ext.models.anthropic import AnthropicChatCompletionClient
  41. from autogen_ext.models.openai import OpenAIChatCompletionClient
  42. from autogen_ext.models.replay import ReplayChatCompletionClient
  43. from autogen_ext.tools.mcp import McpWorkbench, SseServerParams
  44. from pydantic import BaseModel, ValidationError
  45. def mock_tool_function(param: str) -> str:
  46. """Mock tool function for testing.
  47. Args:
  48. param: Input parameter to process
  49. Returns:
  50. Formatted string with the input parameter
  51. """
  52. return f"Tool executed with: {param}"
  53. async def async_mock_tool_function(param: str) -> str:
  54. """Async mock tool function for testing.
  55. Args:
  56. param: Input parameter to process
  57. Returns:
  58. Formatted string with the input parameter
  59. """
  60. return f"Async tool executed with: {param}"
  61. def _pass_function(input: str) -> str:
  62. """Pass through function for testing.
  63. Args:
  64. input: Input to pass through
  65. Returns:
  66. The string "pass"
  67. """
  68. return "pass"
  69. def _echo_function(input: str) -> str:
  70. """Echo function for testing.
  71. Args:
  72. input: Input to echo
  73. Returns:
  74. The input string
  75. """
  76. return input
  77. class MockMemory(Memory):
  78. """Mock memory implementation for testing.
  79. A simple memory implementation that stores strings and provides basic memory operations
  80. for testing purposes.
  81. Args:
  82. contents: Optional list of initial memory contents
  83. """
  84. def __init__(self, contents: Optional[List[str]] = None) -> None:
  85. """Initialize mock memory.
  86. Args:
  87. contents: Optional list of initial memory contents
  88. """
  89. self._contents: List[str] = contents or []
  90. async def add(self, content: MemoryContent, cancellation_token: Optional[CancellationToken] = None) -> None:
  91. """Add content to memory.
  92. Args:
  93. content: Content to add to memory
  94. cancellation_token: Optional token for cancelling operation
  95. """
  96. self._contents.append(str(content))
  97. async def query(
  98. self, query: Union[str, MemoryContent], cancellation_token: Optional[CancellationToken] = None, **kwargs: Any
  99. ) -> MemoryQueryResultSet:
  100. """Query memory contents.
  101. Args:
  102. query: Search query
  103. cancellation_token: Optional token for cancelling operation
  104. kwargs: Additional query parameters
  105. Returns:
  106. Query results containing all memory contents
  107. """
  108. results = [MemoryContent(content=content, mime_type="text/plain") for content in self._contents]
  109. return MemoryQueryResultSet(results=results)
  110. async def clear(self, cancellation_token: Optional[CancellationToken] = None) -> None:
  111. """Clear all memory contents.
  112. Args:
  113. cancellation_token: Optional token for cancelling operation
  114. """
  115. self._contents.clear()
  116. async def close(self) -> None:
  117. """Close memory resources."""
  118. pass
  119. async def update_context(self, model_context: Any) -> UpdateContextResult:
  120. """Update model context with memory contents.
  121. Args:
  122. model_context: Context to update
  123. Returns:
  124. Update result containing memory contents
  125. """
  126. if self._contents:
  127. results = [MemoryContent(content=content, mime_type="text/plain") for content in self._contents]
  128. return UpdateContextResult(memories=MemoryQueryResultSet(results=results))
  129. return UpdateContextResult(memories=MemoryQueryResultSet(results=[]))
  130. def dump_component(self) -> ComponentModel:
  131. """Dump memory state as component model.
  132. Returns:
  133. Component model representing memory state
  134. """
  135. return ComponentModel(provider="test", config={"type": "mock_memory"})
  136. class StructuredOutput(BaseModel):
  137. """Test structured output model.
  138. Attributes:
  139. content: Main content string
  140. confidence: Confidence score between 0 and 1
  141. """
  142. content: str
  143. confidence: float
  144. @pytest.mark.asyncio
  145. async def test_model_client_stream() -> None:
  146. mock_client = ReplayChatCompletionClient(
  147. [
  148. "Response to message 3",
  149. ]
  150. )
  151. agent = AssistantAgent(
  152. "test_agent",
  153. model_client=mock_client,
  154. model_client_stream=True,
  155. )
  156. chunks: List[str] = []
  157. async for message in agent.run_stream(task="task"):
  158. if isinstance(message, TaskResult):
  159. assert isinstance(message.messages[-1], TextMessage)
  160. assert message.messages[-1].content == "Response to message 3"
  161. elif isinstance(message, ModelClientStreamingChunkEvent):
  162. chunks.append(message.content)
  163. assert "".join(chunks) == "Response to message 3"
  164. @pytest.mark.asyncio
  165. async def test_model_client_stream_with_tool_calls() -> None:
  166. mock_client = ReplayChatCompletionClient(
  167. [
  168. CreateResult(
  169. content=[
  170. FunctionCall(id="1", name="_pass_function", arguments=r'{"input": "task"}'),
  171. FunctionCall(id="3", name="_echo_function", arguments=r'{"input": "task"}'),
  172. ],
  173. finish_reason="function_calls",
  174. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  175. cached=False,
  176. ),
  177. "Example response 2 to task",
  178. ]
  179. )
  180. mock_client._model_info["function_calling"] = True # pyright: ignore
  181. agent = AssistantAgent(
  182. "test_agent",
  183. model_client=mock_client,
  184. model_client_stream=True,
  185. reflect_on_tool_use=True,
  186. tools=[_pass_function, _echo_function],
  187. )
  188. chunks: List[str] = []
  189. async for message in agent.run_stream(task="task"):
  190. if isinstance(message, TaskResult):
  191. assert isinstance(message.messages[-1], TextMessage)
  192. assert isinstance(message.messages[1], ToolCallRequestEvent)
  193. assert message.messages[-1].content == "Example response 2 to task"
  194. assert message.messages[1].content == [
  195. FunctionCall(id="1", name="_pass_function", arguments=r'{"input": "task"}'),
  196. FunctionCall(id="3", name="_echo_function", arguments=r'{"input": "task"}'),
  197. ]
  198. assert isinstance(message.messages[2], ToolCallExecutionEvent)
  199. assert message.messages[2].content == [
  200. FunctionExecutionResult(call_id="1", content="pass", is_error=False, name="_pass_function"),
  201. FunctionExecutionResult(call_id="3", content="task", is_error=False, name="_echo_function"),
  202. ]
  203. elif isinstance(message, ModelClientStreamingChunkEvent):
  204. chunks.append(message.content)
  205. assert "".join(chunks) == "Example response 2 to task"
  206. @pytest.mark.asyncio
  207. async def test_invalid_structured_output_format() -> None:
  208. class AgentResponse(BaseModel):
  209. response: str
  210. status: str
  211. model_client = ReplayChatCompletionClient(
  212. [
  213. CreateResult(
  214. finish_reason="stop",
  215. content='{"response": "Hello"}',
  216. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  217. cached=False,
  218. ),
  219. ]
  220. )
  221. agent = AssistantAgent(
  222. name="assistant",
  223. model_client=model_client,
  224. output_content_type=AgentResponse,
  225. )
  226. with pytest.raises(ValidationError):
  227. await agent.run()
  228. @pytest.mark.asyncio
  229. async def test_structured_message_factory_serialization() -> None:
  230. class AgentResponse(BaseModel):
  231. result: str
  232. status: str
  233. model_client = ReplayChatCompletionClient(
  234. [
  235. CreateResult(
  236. finish_reason="stop",
  237. content=AgentResponse(result="All good", status="ok").model_dump_json(),
  238. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  239. cached=False,
  240. )
  241. ]
  242. )
  243. agent = AssistantAgent(
  244. name="structured_agent",
  245. model_client=model_client,
  246. output_content_type=AgentResponse,
  247. output_content_type_format="{result} - {status}",
  248. )
  249. dumped = agent.dump_component()
  250. restored_agent = AssistantAgent.load_component(dumped)
  251. result = await restored_agent.run()
  252. assert isinstance(result.messages[0], StructuredMessage)
  253. assert result.messages[0].content.result == "All good" # type: ignore
  254. assert result.messages[0].content.status == "ok" # type: ignore
  255. @pytest.mark.asyncio
  256. async def test_structured_message_format_string() -> None:
  257. class AgentResponse(BaseModel):
  258. field1: str
  259. field2: str
  260. expected = AgentResponse(field1="foo", field2="bar")
  261. model_client = ReplayChatCompletionClient(
  262. [
  263. CreateResult(
  264. finish_reason="stop",
  265. content=expected.model_dump_json(),
  266. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  267. cached=False,
  268. )
  269. ]
  270. )
  271. agent = AssistantAgent(
  272. name="formatted_agent",
  273. model_client=model_client,
  274. output_content_type=AgentResponse,
  275. output_content_type_format="{field1} - {field2}",
  276. )
  277. result = await agent.run()
  278. assert len(result.messages) == 1
  279. message = result.messages[0]
  280. # Check that it's a StructuredMessage with the correct content model
  281. assert isinstance(message, StructuredMessage)
  282. assert isinstance(message.content, AgentResponse) # type: ignore[reportUnknownMemberType]
  283. assert message.content == expected
  284. # Check that the format_string was applied correctly
  285. assert message.to_model_text() == "foo - bar"
  286. @pytest.mark.asyncio
  287. async def test_tools_serialize_and_deserialize() -> None:
  288. def test() -> str:
  289. return "hello world"
  290. client = OpenAIChatCompletionClient(
  291. model="gpt-4o",
  292. api_key="API_KEY",
  293. )
  294. agent = AssistantAgent(
  295. name="test",
  296. model_client=client,
  297. tools=[test],
  298. )
  299. serialize = agent.dump_component()
  300. deserialize = AssistantAgent.load_component(serialize)
  301. assert deserialize.name == agent.name
  302. for original, restored in zip(agent._workbench, deserialize._workbench, strict=True): # type: ignore
  303. assert await original.list_tools() == await restored.list_tools() # type: ignore
  304. assert agent.component_version == deserialize.component_version
  305. @pytest.mark.asyncio
  306. async def test_workbench_serialize_and_deserialize() -> None:
  307. workbench = McpWorkbench(server_params=SseServerParams(url="http://test-url"))
  308. client = OpenAIChatCompletionClient(
  309. model="gpt-4o",
  310. api_key="API_KEY",
  311. )
  312. agent = AssistantAgent(
  313. name="test",
  314. model_client=client,
  315. workbench=workbench,
  316. )
  317. serialize = agent.dump_component()
  318. deserialize = AssistantAgent.load_component(serialize)
  319. assert deserialize.name == agent.name
  320. for original, restored in zip(agent._workbench, deserialize._workbench, strict=True): # type: ignore
  321. assert isinstance(original, McpWorkbench)
  322. assert isinstance(restored, McpWorkbench)
  323. assert original._to_config() == restored._to_config() # type: ignore
  324. @pytest.mark.asyncio
  325. async def test_multiple_workbenches_serialize_and_deserialize() -> None:
  326. workbenches: List[McpWorkbench] = [
  327. McpWorkbench(server_params=SseServerParams(url="http://test-url-1")),
  328. McpWorkbench(server_params=SseServerParams(url="http://test-url-2")),
  329. ]
  330. client = OpenAIChatCompletionClient(
  331. model="gpt-4o",
  332. api_key="API_KEY",
  333. )
  334. agent = AssistantAgent(
  335. name="test_multi",
  336. model_client=client,
  337. workbench=workbenches,
  338. )
  339. serialize = agent.dump_component()
  340. deserialized_agent: AssistantAgent = AssistantAgent.load_component(serialize)
  341. assert deserialized_agent.name == agent.name
  342. assert isinstance(deserialized_agent._workbench, list) # type: ignore
  343. assert len(deserialized_agent._workbench) == len(workbenches) # type: ignore
  344. for original, restored in zip(agent._workbench, deserialized_agent._workbench, strict=True): # type: ignore
  345. assert isinstance(original, McpWorkbench)
  346. assert isinstance(restored, McpWorkbench)
  347. assert original._to_config() == restored._to_config() # type: ignore
  348. @pytest.mark.asyncio
  349. async def test_tools_deserialize_aware() -> None:
  350. dump = """
  351. {
  352. "provider": "autogen_agentchat.agents.AssistantAgent",
  353. "component_type": "agent",
  354. "version": 1,
  355. "component_version": 2,
  356. "description": "An agent that provides assistance with tool use.",
  357. "label": "AssistantAgent",
  358. "config": {
  359. "name": "TestAgent",
  360. "model_client":{
  361. "provider": "autogen_ext.models.replay.ReplayChatCompletionClient",
  362. "component_type": "replay_chat_completion_client",
  363. "version": 1,
  364. "component_version": 1,
  365. "description": "A mock chat completion client that replays predefined responses using an index-based approach.",
  366. "label": "ReplayChatCompletionClient",
  367. "config": {
  368. "chat_completions": [
  369. {
  370. "finish_reason": "function_calls",
  371. "content": [
  372. {
  373. "id": "hello",
  374. "arguments": "{}",
  375. "name": "hello"
  376. }
  377. ],
  378. "usage": {
  379. "prompt_tokens": 0,
  380. "completion_tokens": 0
  381. },
  382. "cached": false
  383. }
  384. ],
  385. "model_info": {
  386. "vision": false,
  387. "function_calling": true,
  388. "json_output": false,
  389. "family": "unknown",
  390. "structured_output": false
  391. }
  392. }
  393. },
  394. "tools": [
  395. {
  396. "provider": "autogen_core.tools.FunctionTool",
  397. "component_type": "tool",
  398. "version": 1,
  399. "component_version": 1,
  400. "description": "Create custom tools by wrapping standard Python functions.",
  401. "label": "FunctionTool",
  402. "config": {
  403. "source_code": "def hello():\\n return 'Hello, World!'\\n",
  404. "name": "hello",
  405. "description": "",
  406. "global_imports": [],
  407. "has_cancellation_support": false
  408. }
  409. }
  410. ],
  411. "model_context": {
  412. "provider": "autogen_core.model_context.UnboundedChatCompletionContext",
  413. "component_type": "chat_completion_context",
  414. "version": 1,
  415. "component_version": 1,
  416. "description": "An unbounded chat completion context that keeps a view of the all the messages.",
  417. "label": "UnboundedChatCompletionContext",
  418. "config": {}
  419. },
  420. "description": "An agent that provides assistance with ability to use tools.",
  421. "system_message": "You are a helpful assistant.",
  422. "model_client_stream": false,
  423. "reflect_on_tool_use": false,
  424. "tool_call_summary_format": "{result}",
  425. "metadata": {}
  426. }
  427. }
  428. """
  429. # Test that agent can be deserialized from configuration
  430. config = json.loads(dump)
  431. agent = AssistantAgent.load_component(config)
  432. # Verify the agent was loaded correctly
  433. assert agent.name == "TestAgent"
  434. assert agent.description == "An agent that provides assistance with ability to use tools."
  435. class TestAssistantAgentToolCallLoop:
  436. """Test suite for tool call loop functionality.
  437. Tests the behavior of AssistantAgent's tool call loop feature, which allows
  438. multiple sequential tool calls before producing a final response.
  439. """
  440. @pytest.mark.asyncio
  441. async def test_tool_call_loop_enabled(self) -> None:
  442. """Test that tool call loop works when enabled.
  443. Verifies that:
  444. 1. Multiple tool calls are executed in sequence
  445. 2. Loop continues until non-tool response
  446. 3. Final response is correct type
  447. """
  448. # Create mock client with multiple tool calls followed by text response
  449. model_client = ReplayChatCompletionClient(
  450. [
  451. # First tool call
  452. CreateResult(
  453. finish_reason="function_calls",
  454. content=[FunctionCall(id="1", arguments=json.dumps({"param": "first"}), name="mock_tool_function")],
  455. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  456. cached=False,
  457. ),
  458. # Second tool call (loop continues)
  459. CreateResult(
  460. finish_reason="function_calls",
  461. content=[
  462. FunctionCall(id="2", arguments=json.dumps({"param": "second"}), name="mock_tool_function")
  463. ],
  464. usage=RequestUsage(prompt_tokens=12, completion_tokens=5),
  465. cached=False,
  466. ),
  467. # Final text response (loop ends)
  468. CreateResult(
  469. finish_reason="stop",
  470. content="Task completed successfully!",
  471. usage=RequestUsage(prompt_tokens=15, completion_tokens=10),
  472. cached=False,
  473. ),
  474. ],
  475. model_info={
  476. "function_calling": True,
  477. "vision": False,
  478. "json_output": False,
  479. "family": ModelFamily.GPT_4O,
  480. "structured_output": False,
  481. },
  482. )
  483. agent = AssistantAgent(
  484. name="test_agent",
  485. model_client=model_client,
  486. tools=[mock_tool_function],
  487. max_tool_iterations=3,
  488. )
  489. result = await agent.run(task="Execute multiple tool calls")
  490. # Verify multiple model calls were made
  491. assert len(model_client.create_calls) == 3, f"Expected 3 calls, got {len(model_client.create_calls)}"
  492. # Verify final response is text
  493. final_message = result.messages[-1]
  494. assert isinstance(final_message, TextMessage)
  495. assert final_message.content == "Task completed successfully!"
  496. @pytest.mark.asyncio
  497. async def test_tool_call_loop_disabled_default(self) -> None:
  498. """Test that tool call loop is disabled by default.
  499. Verifies that:
  500. 1. Only one tool call is made when loop is disabled
  501. 2. Agent returns after first tool call
  502. """
  503. model_client = ReplayChatCompletionClient(
  504. [
  505. CreateResult(
  506. finish_reason="function_calls",
  507. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  508. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  509. cached=False,
  510. )
  511. ],
  512. model_info={
  513. "function_calling": True,
  514. "vision": False,
  515. "json_output": False,
  516. "family": ModelFamily.GPT_4O,
  517. "structured_output": False,
  518. },
  519. )
  520. agent = AssistantAgent(
  521. name="test_agent",
  522. model_client=model_client,
  523. tools=[mock_tool_function],
  524. max_tool_iterations=1,
  525. )
  526. result = await agent.run(task="Execute single tool call")
  527. # Should only make one model call
  528. assert len(model_client.create_calls) == 1, f"Expected 1 call, got {len(model_client.create_calls)}"
  529. assert result is not None
  530. @pytest.mark.asyncio
  531. async def test_tool_call_loop_max_iterations(self) -> None:
  532. """Test that tool call loop respects max_iterations limit."""
  533. # Create responses that would continue forever without max_iterations
  534. responses: List[CreateResult] = []
  535. for i in range(15): # More than default max_iterations (10)
  536. responses.append(
  537. CreateResult(
  538. finish_reason="function_calls",
  539. content=[
  540. FunctionCall(id=str(i), arguments=json.dumps({"param": f"call_{i}"}), name="mock_tool_function")
  541. ],
  542. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  543. cached=False,
  544. )
  545. )
  546. model_client = ReplayChatCompletionClient(
  547. responses,
  548. model_info={
  549. "function_calling": True,
  550. "vision": False,
  551. "json_output": False,
  552. "family": ModelFamily.GPT_4O,
  553. "structured_output": False,
  554. },
  555. )
  556. agent = AssistantAgent(
  557. name="test_agent",
  558. model_client=model_client,
  559. tools=[mock_tool_function],
  560. max_tool_iterations=5, # Set max iterations to 5
  561. )
  562. result = await agent.run(task="Test max iterations")
  563. # Should stop at max_iterations
  564. assert len(model_client.create_calls) == 5, f"Expected 5 calls, got {len(model_client.create_calls)}"
  565. # Verify result is not None
  566. assert result is not None
  567. @pytest.mark.asyncio
  568. async def test_tool_call_loop_with_handoff(self) -> None:
  569. """Test that tool call loop stops on handoff."""
  570. model_client = ReplayChatCompletionClient(
  571. [
  572. # Tool call followed by handoff
  573. CreateResult(
  574. finish_reason="function_calls",
  575. content=[
  576. FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function"),
  577. FunctionCall(
  578. id="2", arguments=json.dumps({"target": "other_agent"}), name="transfer_to_other_agent"
  579. ),
  580. ],
  581. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  582. cached=False,
  583. ),
  584. ],
  585. model_info={
  586. "function_calling": True,
  587. "vision": False,
  588. "json_output": False,
  589. "family": ModelFamily.GPT_4O,
  590. "structured_output": False,
  591. },
  592. )
  593. agent = AssistantAgent(
  594. name="test_agent",
  595. model_client=model_client,
  596. tools=[mock_tool_function],
  597. handoffs=["other_agent"],
  598. max_tool_iterations=1,
  599. )
  600. result = await agent.run(task="Test handoff in loop")
  601. # Should stop at handoff
  602. assert len(model_client.create_calls) == 1, f"Expected 1 call, got {len(model_client.create_calls)}"
  603. # Should return HandoffMessage
  604. assert isinstance(result.messages[-1], HandoffMessage)
  605. @pytest.mark.asyncio
  606. async def test_tool_call_config_validation(self) -> None:
  607. """Test that ToolCallConfig validation works correctly."""
  608. # Test that max_iterations must be >= 1
  609. with pytest.raises(
  610. ValueError, match="Maximum number of tool iterations must be greater than or equal to 1, got 0"
  611. ):
  612. AssistantAgent(
  613. name="test_agent",
  614. model_client=MagicMock(),
  615. max_tool_iterations=0, # Should raise error
  616. )
  617. class TestAssistantAgentInitialization:
  618. """Test suite for AssistantAgent initialization.
  619. Tests various initialization scenarios and configurations of the AssistantAgent class.
  620. """
  621. @pytest.mark.asyncio
  622. async def test_basic_initialization(self) -> None:
  623. """Test basic agent initialization with minimal parameters.
  624. Verifies that:
  625. 1. Agent initializes with required parameters
  626. 2. Default values are set correctly
  627. 3. Basic functionality works
  628. """
  629. model_client = ReplayChatCompletionClient(
  630. [
  631. CreateResult(
  632. finish_reason="stop",
  633. content="Hello!",
  634. usage=RequestUsage(prompt_tokens=5, completion_tokens=2),
  635. cached=False,
  636. )
  637. ],
  638. model_info={
  639. "function_calling": True,
  640. "vision": False,
  641. "json_output": False,
  642. "family": ModelFamily.GPT_4O,
  643. "structured_output": False,
  644. },
  645. )
  646. agent = AssistantAgent(name="test_agent", model_client=model_client)
  647. result = await agent.run(task="Say hello")
  648. assert isinstance(result.messages[-1], TextMessage)
  649. assert result.messages[-1].content == "Hello!"
  650. @pytest.mark.asyncio
  651. async def test_initialization_with_tools(self) -> None:
  652. """Test agent initialization with tools.
  653. Verifies that:
  654. 1. Agent accepts tool configurations
  655. 2. Tools are properly registered
  656. 3. Tool calls work correctly
  657. """
  658. model_client = ReplayChatCompletionClient(
  659. [
  660. CreateResult(
  661. finish_reason="function_calls",
  662. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  663. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  664. cached=False,
  665. )
  666. ],
  667. model_info={
  668. "function_calling": True,
  669. "vision": False,
  670. "json_output": False,
  671. "family": ModelFamily.GPT_4O,
  672. "structured_output": False,
  673. },
  674. )
  675. agent = AssistantAgent(
  676. name="test_agent",
  677. model_client=model_client,
  678. tools=[mock_tool_function],
  679. )
  680. result = await agent.run(task="Use the tool")
  681. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  682. assert "Tool executed with: test" in result.messages[-1].content
  683. @pytest.mark.asyncio
  684. async def test_initialization_with_memory(self) -> None:
  685. """Test agent initialization with memory.
  686. Verifies that:
  687. 1. Memory is properly integrated
  688. 2. Memory contents affect responses
  689. 3. Memory updates work correctly
  690. """
  691. model_client = ReplayChatCompletionClient(
  692. [
  693. CreateResult(
  694. finish_reason="stop",
  695. content="Using memory content",
  696. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  697. cached=False,
  698. )
  699. ],
  700. model_info={
  701. "function_calling": True,
  702. "vision": False,
  703. "json_output": False,
  704. "family": ModelFamily.GPT_4O,
  705. "structured_output": False,
  706. },
  707. )
  708. memory = MockMemory(contents=["Test memory content"])
  709. agent = AssistantAgent(
  710. name="test_agent",
  711. model_client=model_client,
  712. memory=[memory],
  713. )
  714. result = await agent.run(task="Use memory")
  715. assert isinstance(result.messages[-1], TextMessage)
  716. assert result.messages[-1].content == "Using memory content"
  717. @pytest.mark.asyncio
  718. async def test_initialization_with_handoffs(self) -> None:
  719. """Test agent initialization with handoffs."""
  720. model_client = MagicMock()
  721. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  722. agent = AssistantAgent(
  723. name="test_agent",
  724. model_client=model_client,
  725. handoffs=["agent1", Handoff(target="agent2")],
  726. )
  727. assert len(agent._handoffs) == 2 # type: ignore[reportPrivateUsage]
  728. assert "transfer_to_agent1" in agent._handoffs # type: ignore[reportPrivateUsage]
  729. assert "transfer_to_agent2" in agent._handoffs # type: ignore[reportPrivateUsage]
  730. @pytest.mark.asyncio
  731. async def test_initialization_with_custom_model_context(self) -> None:
  732. """Test agent initialization with custom model context."""
  733. model_client = MagicMock()
  734. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  735. model_context = BufferedChatCompletionContext(buffer_size=5)
  736. agent = AssistantAgent(
  737. name="test_agent",
  738. model_client=model_client,
  739. model_context=model_context,
  740. )
  741. assert agent._model_context == model_context # type: ignore[reportPrivateUsage]
  742. @pytest.mark.asyncio
  743. async def test_initialization_with_structured_output(self) -> None:
  744. """Test agent initialization with structured output."""
  745. model_client = MagicMock()
  746. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  747. agent = AssistantAgent(
  748. name="test_agent",
  749. model_client=model_client,
  750. output_content_type=StructuredOutput,
  751. )
  752. assert agent._output_content_type == StructuredOutput # type: ignore[reportPrivateUsage]
  753. assert agent._reflect_on_tool_use is True # type: ignore[reportPrivateUsage] # Should be True by default with structured output
  754. @pytest.mark.asyncio
  755. async def test_initialization_with_metadata(self) -> None:
  756. """Test agent initialization with metadata."""
  757. model_client = MagicMock()
  758. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  759. metadata = {"key1": "value1", "key2": "value2"}
  760. agent = AssistantAgent(
  761. name="test_agent",
  762. model_client=model_client,
  763. metadata=metadata,
  764. )
  765. assert agent._metadata == metadata # type: ignore[reportPrivateUsage]
  766. @pytest.mark.asyncio
  767. async def test_output_task_messages_false(self) -> None:
  768. """Test agent with output_task_messages=False.
  769. Verifies that:
  770. 1. Task messages are excluded from result when output_task_messages=False
  771. 2. Only agent response messages are included in output
  772. 3. Both run and run_stream respect the parameter
  773. """
  774. model_client = ReplayChatCompletionClient(
  775. [
  776. CreateResult(
  777. finish_reason="stop",
  778. content="Agent response without task message",
  779. usage=RequestUsage(prompt_tokens=10, completion_tokens=8),
  780. cached=False,
  781. ),
  782. CreateResult(
  783. finish_reason="stop",
  784. content="Second agent response",
  785. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  786. cached=False,
  787. ),
  788. ],
  789. model_info={
  790. "function_calling": False,
  791. "vision": False,
  792. "json_output": False,
  793. "family": ModelFamily.GPT_4O,
  794. "structured_output": False,
  795. },
  796. )
  797. agent = AssistantAgent(name="test_agent", model_client=model_client)
  798. # Test run() with output_task_messages=False
  799. result = await agent.run(task="Test task message", output_task_messages=False)
  800. # Should only contain the agent's response, not the task message
  801. assert len(result.messages) == 1
  802. assert isinstance(result.messages[0], TextMessage)
  803. assert result.messages[0].content == "Agent response without task message"
  804. assert result.messages[0].source == "test_agent" # Test run_stream() with output_task_messages=False
  805. # Create a new model client for streaming test to avoid response conflicts
  806. stream_model_client = ReplayChatCompletionClient(
  807. [
  808. CreateResult(
  809. finish_reason="stop",
  810. content="Stream agent response",
  811. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  812. cached=False,
  813. ),
  814. ],
  815. model_info={
  816. "function_calling": False,
  817. "vision": False,
  818. "json_output": False,
  819. "family": ModelFamily.GPT_4O,
  820. "structured_output": False,
  821. },
  822. )
  823. stream_agent = AssistantAgent(name="test_agent", model_client=stream_model_client)
  824. streamed_messages: List[BaseAgentEvent | BaseChatMessage] = []
  825. final_result: TaskResult | None = None
  826. async for message in stream_agent.run_stream(task="Test task message", output_task_messages=False):
  827. if isinstance(message, TaskResult):
  828. final_result = message
  829. else:
  830. streamed_messages.append(message)
  831. # Verify streaming behavior
  832. assert final_result is not None
  833. assert len(final_result.messages) == 1
  834. assert isinstance(final_result.messages[0], TextMessage)
  835. assert final_result.messages[0].content == "Stream agent response"
  836. # Verify that no task message was streamed
  837. task_messages = [msg for msg in streamed_messages if isinstance(msg, TextMessage) and msg.source == "user"]
  838. assert len(task_messages) == 0 # Test with multiple task messages
  839. multi_model_client = ReplayChatCompletionClient(
  840. [
  841. CreateResult(
  842. finish_reason="stop",
  843. content="Multi task response",
  844. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  845. cached=False,
  846. ),
  847. ],
  848. model_info={
  849. "function_calling": False,
  850. "vision": False,
  851. "json_output": False,
  852. "family": ModelFamily.GPT_4O,
  853. "structured_output": False,
  854. },
  855. )
  856. multi_agent = AssistantAgent(name="test_agent", model_client=multi_model_client)
  857. task_messages_list = [
  858. TextMessage(content="First task", source="user"),
  859. TextMessage(content="Second task", source="user"),
  860. ]
  861. result_multi = await multi_agent.run(task=task_messages_list, output_task_messages=False)
  862. # Should only contain the agent's response, not the multiple task messages
  863. assert len(result_multi.messages) == 1
  864. assert isinstance(result_multi.messages[0], TextMessage)
  865. assert result_multi.messages[0].source == "test_agent"
  866. assert result_multi.messages[0].content == "Multi task response"
  867. class TestAssistantAgentValidation:
  868. """Test suite for AssistantAgent validation.
  869. Tests various validation scenarios to ensure proper error handling and input validation.
  870. """
  871. @pytest.mark.asyncio
  872. async def test_tool_names_must_be_unique(self) -> None:
  873. """Test validation of unique tool names.
  874. Verifies that:
  875. 1. Duplicate tool names are detected
  876. 2. Appropriate error is raised
  877. """
  878. def duplicate_tool(param: str) -> str:
  879. """Test tool with duplicate name.
  880. Args:
  881. param: Input parameter
  882. Returns:
  883. Formatted string with parameter
  884. """
  885. return f"Duplicate tool: {param}"
  886. model_client = ReplayChatCompletionClient(
  887. [],
  888. model_info={
  889. "function_calling": True,
  890. "vision": False,
  891. "json_output": False,
  892. "family": ModelFamily.GPT_4O,
  893. "structured_output": False,
  894. },
  895. )
  896. with pytest.raises(ValueError, match="Tool names must be unique"):
  897. AssistantAgent(
  898. name="test_agent",
  899. model_client=model_client,
  900. tools=[mock_tool_function, duplicate_tool, mock_tool_function],
  901. )
  902. @pytest.mark.asyncio
  903. async def test_handoff_names_must_be_unique(self) -> None:
  904. """Test validation of unique handoff names.
  905. Verifies that:
  906. 1. Duplicate handoff names are detected
  907. 2. Appropriate error is raised
  908. """
  909. model_client = ReplayChatCompletionClient(
  910. [],
  911. model_info={
  912. "function_calling": True,
  913. "vision": False,
  914. "json_output": False,
  915. "family": ModelFamily.GPT_4O,
  916. "structured_output": False,
  917. },
  918. )
  919. with pytest.raises(ValueError, match="Handoff names must be unique"):
  920. AssistantAgent(
  921. name="test_agent",
  922. model_client=model_client,
  923. handoffs=["agent1", "agent2", "agent1"],
  924. )
  925. @pytest.mark.asyncio
  926. async def test_handoff_names_must_be_unique_from_tool_names(self) -> None:
  927. """Test validation of handoff names against tool names.
  928. Verifies that:
  929. 1. Handoff names cannot conflict with tool names
  930. 2. Appropriate error is raised
  931. """
  932. def test_tool() -> str:
  933. """Test tool with name that conflicts with handoff.
  934. Returns:
  935. Static test string
  936. """
  937. return "test"
  938. model_client = ReplayChatCompletionClient(
  939. [],
  940. model_info={
  941. "function_calling": True,
  942. "vision": False,
  943. "json_output": False,
  944. "family": ModelFamily.GPT_4O,
  945. "structured_output": False,
  946. },
  947. )
  948. with pytest.raises(ValueError, match="Handoff names must be unique from tool names"):
  949. AssistantAgent(
  950. name="test_agent",
  951. model_client=model_client,
  952. tools=[test_tool],
  953. handoffs=["test_tool"],
  954. )
  955. @pytest.mark.asyncio
  956. async def test_function_calling_required_for_tools(self) -> None:
  957. """Test that function calling is required for tools."""
  958. model_client = MagicMock()
  959. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  960. with pytest.raises(ValueError, match="The model does not support function calling"):
  961. AssistantAgent(
  962. name="test_agent",
  963. model_client=model_client,
  964. tools=[mock_tool_function],
  965. )
  966. @pytest.mark.asyncio
  967. async def test_function_calling_required_for_handoffs(self) -> None:
  968. """Test that function calling is required for handoffs."""
  969. model_client = MagicMock()
  970. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  971. with pytest.raises(
  972. ValueError, match="The model does not support function calling, which is needed for handoffs"
  973. ):
  974. AssistantAgent(
  975. name="test_agent",
  976. model_client=model_client,
  977. handoffs=["agent1"],
  978. )
  979. @pytest.mark.asyncio
  980. async def test_memory_type_validation(self) -> None:
  981. """Test memory type validation."""
  982. model_client = MagicMock()
  983. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  984. with pytest.raises(TypeError, match="Expected Memory, List\\[Memory\\], or None"):
  985. AssistantAgent(
  986. name="test_agent",
  987. model_client=model_client,
  988. memory="invalid_memory", # type: ignore
  989. )
  990. @pytest.mark.asyncio
  991. async def test_tools_and_workbench_mutually_exclusive(self) -> None:
  992. """Test that tools and workbench are mutually exclusive."""
  993. model_client = MagicMock()
  994. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  995. workbench = MagicMock()
  996. with pytest.raises(ValueError, match="Tools cannot be used with a workbench"):
  997. AssistantAgent(
  998. name="test_agent",
  999. model_client=model_client,
  1000. tools=[mock_tool_function],
  1001. workbench=workbench,
  1002. )
  1003. @pytest.mark.asyncio
  1004. async def test_unsupported_tool_type(self) -> None:
  1005. """Test error handling for unsupported tool types."""
  1006. model_client = MagicMock()
  1007. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1008. with pytest.raises(ValueError, match="Unsupported tool type"):
  1009. AssistantAgent(
  1010. name="test_agent",
  1011. model_client=model_client,
  1012. tools=["invalid_tool"], # type: ignore
  1013. )
  1014. @pytest.mark.asyncio
  1015. async def test_unsupported_handoff_type(self) -> None:
  1016. """Test error handling for unsupported handoff types."""
  1017. model_client = MagicMock()
  1018. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1019. with pytest.raises(ValueError, match="Unsupported handoff type"):
  1020. AssistantAgent(
  1021. name="test_agent",
  1022. model_client=model_client,
  1023. handoffs=[123], # type: ignore
  1024. )
  1025. class TestAssistantAgentStateManagement:
  1026. """Test suite for AssistantAgent state management."""
  1027. @pytest.mark.asyncio
  1028. async def test_save_and_load_state(self) -> None:
  1029. """Test saving and loading agent state."""
  1030. model_client = MagicMock()
  1031. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1032. # Mock model context state
  1033. mock_context = MagicMock()
  1034. mock_context.save_state = AsyncMock(return_value={"context": "state"})
  1035. mock_context.load_state = AsyncMock()
  1036. agent = AssistantAgent(
  1037. name="test_agent",
  1038. model_client=model_client,
  1039. model_context=mock_context,
  1040. )
  1041. # Test save state
  1042. state = await agent.save_state()
  1043. assert "llm_context" in state
  1044. # Test load state
  1045. await agent.load_state(state)
  1046. mock_context.load_state.assert_called_once()
  1047. @pytest.mark.asyncio
  1048. async def test_on_reset(self) -> None:
  1049. """Test agent reset functionality."""
  1050. model_client = MagicMock()
  1051. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1052. mock_context = MagicMock()
  1053. mock_context.clear = AsyncMock()
  1054. agent = AssistantAgent(
  1055. name="test_agent",
  1056. model_client=model_client,
  1057. model_context=mock_context,
  1058. )
  1059. cancellation_token = CancellationToken()
  1060. await agent.on_reset(cancellation_token)
  1061. mock_context.clear.assert_called_once()
  1062. class TestAssistantAgentProperties:
  1063. """Test suite for AssistantAgent properties."""
  1064. @pytest.mark.asyncio
  1065. async def test_produced_message_types_text_only(self) -> None:
  1066. """Test produced message types for text-only agent."""
  1067. model_client = MagicMock()
  1068. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1069. agent = AssistantAgent(
  1070. name="test_agent",
  1071. model_client=model_client,
  1072. )
  1073. message_types = agent.produced_message_types
  1074. assert TextMessage in message_types
  1075. @pytest.mark.asyncio
  1076. async def test_produced_message_types_with_tools(self) -> None:
  1077. """Test produced message types for agent with tools."""
  1078. model_client = MagicMock()
  1079. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1080. agent = AssistantAgent(
  1081. name="test_agent",
  1082. model_client=model_client,
  1083. tools=[mock_tool_function],
  1084. )
  1085. message_types = agent.produced_message_types
  1086. assert ToolCallSummaryMessage in message_types
  1087. @pytest.mark.asyncio
  1088. async def test_produced_message_types_with_handoffs(self) -> None:
  1089. """Test produced message types for agent with handoffs."""
  1090. model_client = MagicMock()
  1091. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1092. agent = AssistantAgent(
  1093. name="test_agent",
  1094. model_client=model_client,
  1095. handoffs=["agent1"],
  1096. )
  1097. message_types = agent.produced_message_types
  1098. assert HandoffMessage in message_types
  1099. @pytest.mark.asyncio
  1100. async def test_model_context_property(self) -> None:
  1101. """Test model_context property access."""
  1102. model_client = MagicMock()
  1103. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1104. custom_context = BufferedChatCompletionContext(buffer_size=3)
  1105. agent = AssistantAgent(
  1106. name="test_agent",
  1107. model_client=model_client,
  1108. model_context=custom_context,
  1109. )
  1110. assert agent.model_context == custom_context
  1111. class TestAssistantAgentErrorHandling:
  1112. """Test suite for error handling scenarios."""
  1113. @pytest.mark.asyncio
  1114. async def test_invalid_json_in_tool_arguments(self) -> None:
  1115. """Test handling of invalid JSON in tool arguments."""
  1116. model_client = ReplayChatCompletionClient(
  1117. [
  1118. CreateResult(
  1119. finish_reason="function_calls",
  1120. content=[FunctionCall(id="1", arguments="invalid json", name="mock_tool_function")],
  1121. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1122. cached=False,
  1123. ),
  1124. ],
  1125. model_info={
  1126. "function_calling": True,
  1127. "vision": False,
  1128. "json_output": False,
  1129. "family": ModelFamily.GPT_4O,
  1130. "structured_output": False,
  1131. },
  1132. )
  1133. agent = AssistantAgent(
  1134. name="test_agent",
  1135. model_client=model_client,
  1136. tools=[mock_tool_function],
  1137. )
  1138. result = await agent.run(task="Execute tool with invalid JSON")
  1139. # Should handle JSON parsing error
  1140. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  1141. class TestAssistantAgentMemoryIntegration:
  1142. """Test suite for AssistantAgent memory integration.
  1143. Tests the integration between AssistantAgent and memory components, including:
  1144. - Memory initialization
  1145. - Context updates
  1146. - Query operations
  1147. - Memory persistence
  1148. """
  1149. @pytest.mark.asyncio
  1150. async def test_memory_updates_context(self) -> None:
  1151. """Test that memory properly updates model context.
  1152. Verifies that:
  1153. 1. Memory contents are added to context
  1154. 2. Context updates trigger appropriate events
  1155. 3. Memory query results are properly handled
  1156. """
  1157. # Setup test memory with initial content
  1158. memory = MockMemory(contents=["Previous conversation about topic A"])
  1159. # Configure model client with expected response
  1160. model_client = ReplayChatCompletionClient(
  1161. [
  1162. CreateResult(
  1163. finish_reason="stop",
  1164. content="Response incorporating memory content",
  1165. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1166. cached=False,
  1167. )
  1168. ],
  1169. model_info={
  1170. "function_calling": True,
  1171. "vision": False,
  1172. "json_output": False,
  1173. "family": ModelFamily.GPT_4O,
  1174. "structured_output": False,
  1175. },
  1176. )
  1177. # Create agent with memory
  1178. agent = AssistantAgent(
  1179. name="test_agent",
  1180. model_client=model_client,
  1181. memory=[memory],
  1182. description="Agent with memory integration",
  1183. )
  1184. # Track memory events during execution
  1185. memory_events: List[MemoryQueryEvent] = []
  1186. async def event_handler(event: MemoryQueryEvent) -> None:
  1187. """Handle memory query events.
  1188. Args:
  1189. event: Memory query event to process
  1190. """
  1191. memory_events.append(event)
  1192. # Create a handler function to capture memory events
  1193. async def handle_memory_events(result: Any) -> None:
  1194. messages: List[BaseChatMessage] = result.messages if hasattr(result, "messages") else []
  1195. for msg in messages:
  1196. if isinstance(msg, MemoryQueryEvent):
  1197. await event_handler(msg)
  1198. # Run agent
  1199. result = await agent.run(task="Respond using memory context")
  1200. # Process the events
  1201. await handle_memory_events(result)
  1202. # Verify memory integration
  1203. assert len(memory_events) > 0, "No memory events were generated"
  1204. assert isinstance(result.messages[-1], TextMessage)
  1205. assert "Response incorporating memory content" in result.messages[-1].content
  1206. @pytest.mark.asyncio
  1207. async def test_memory_persistence(self) -> None:
  1208. """Test memory persistence across multiple sessions.
  1209. Verifies:
  1210. 1. Memory content persists between sessions
  1211. 2. Memory updates are preserved
  1212. 3. Context is properly restored
  1213. 4. Memory query events are generated correctly
  1214. """
  1215. # Create memory with initial content
  1216. memory = MockMemory(contents=["Initial memory"])
  1217. # Create model client
  1218. model_client = ReplayChatCompletionClient(
  1219. [
  1220. CreateResult(
  1221. finish_reason="stop",
  1222. content="Response using memory",
  1223. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1224. cached=False,
  1225. ),
  1226. CreateResult(
  1227. finish_reason="stop",
  1228. content="Response with updated memory",
  1229. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1230. cached=False,
  1231. ),
  1232. ],
  1233. model_info={
  1234. "function_calling": False,
  1235. "vision": False,
  1236. "json_output": False,
  1237. "family": ModelFamily.GPT_4O,
  1238. "structured_output": False,
  1239. },
  1240. )
  1241. # Create agent with memory
  1242. agent = AssistantAgent(name="memory_test_agent", model_client=model_client, memory=[memory])
  1243. # First session
  1244. result1 = await agent.run(task="First task")
  1245. state = await agent.save_state()
  1246. # Add new memory content
  1247. await memory.add(MemoryContent(content="New memory", mime_type="text/plain"))
  1248. # Create new agent and restore state
  1249. new_agent = AssistantAgent(name="memory_test_agent", model_client=model_client, memory=[memory])
  1250. await new_agent.load_state(state)
  1251. # Second session
  1252. result2 = await new_agent.run(task="Second task")
  1253. # Verify memory persistence
  1254. assert isinstance(result1.messages[-1], TextMessage)
  1255. assert isinstance(result2.messages[-1], TextMessage)
  1256. assert result1.messages[-1].content == "Response using memory"
  1257. assert result2.messages[-1].content == "Response with updated memory"
  1258. # Verify memory events
  1259. memory_events = [msg for msg in result2.messages if isinstance(msg, MemoryQueryEvent)]
  1260. assert len(memory_events) > 0
  1261. assert any("New memory" in str(event.content) for event in memory_events)
  1262. class TestAssistantAgentSystemMessage:
  1263. """Test suite for system message functionality."""
  1264. @pytest.mark.asyncio
  1265. async def test_system_message_none(self) -> None:
  1266. """Test agent with system_message=None."""
  1267. model_client = MagicMock()
  1268. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1269. agent = AssistantAgent(
  1270. name="test_agent",
  1271. model_client=model_client,
  1272. system_message=None,
  1273. )
  1274. assert agent._system_messages == [] # type: ignore[reportPrivateUsage]
  1275. @pytest.mark.asyncio
  1276. async def test_custom_system_message(self) -> None:
  1277. """Test agent with custom system message."""
  1278. model_client = MagicMock()
  1279. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1280. custom_message = "You are a specialized assistant."
  1281. agent = AssistantAgent(
  1282. name="test_agent",
  1283. model_client=model_client,
  1284. system_message=custom_message,
  1285. )
  1286. assert len(agent._system_messages) == 1 # type: ignore[reportPrivateUsage]
  1287. assert agent._system_messages[0].content == custom_message # type: ignore[reportPrivateUsage]
  1288. class TestAssistantAgentModelCompatibility:
  1289. """Test suite for model compatibility functionality."""
  1290. @pytest.mark.asyncio
  1291. async def test_vision_compatibility(self) -> None:
  1292. """Test vision model compatibility."""
  1293. model_client = MagicMock()
  1294. model_client.model_info = {"function_calling": False, "vision": True, "family": ModelFamily.GPT_4O}
  1295. agent = AssistantAgent(
  1296. name="test_agent",
  1297. model_client=model_client,
  1298. )
  1299. # Test _get_compatible_context with vision model
  1300. from autogen_core.models import LLMMessage
  1301. messages: List[LLMMessage] = [SystemMessage(content="Test")]
  1302. compatible_messages = agent._get_compatible_context(model_client, messages) # type: ignore[reportPrivateUsage]
  1303. # Should return original messages for vision models
  1304. assert compatible_messages == messages
  1305. class TestAssistantAgentComponentSerialization:
  1306. """Test suite for component serialization functionality."""
  1307. @pytest.mark.asyncio
  1308. async def test_to_config_basic_agent(self) -> None:
  1309. """Test _to_config method with basic agent configuration."""
  1310. model_client = MagicMock()
  1311. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1312. model_client.dump_component = MagicMock(
  1313. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1314. )
  1315. mock_context = MagicMock()
  1316. mock_context.dump_component = MagicMock(
  1317. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1318. )
  1319. agent = AssistantAgent(
  1320. name="test_agent",
  1321. model_client=model_client,
  1322. description="Test description",
  1323. system_message="Test system message",
  1324. model_context=mock_context,
  1325. metadata={"key": "value"},
  1326. )
  1327. config = agent._to_config() # type: ignore[reportPrivateUsage]
  1328. assert config.name == "test_agent"
  1329. assert config.description == "Test description"
  1330. assert config.system_message == "Test system message"
  1331. assert config.model_client_stream is False
  1332. assert config.reflect_on_tool_use is False
  1333. assert config.max_tool_iterations == 1
  1334. assert config.metadata == {"key": "value"}
  1335. model_client.dump_component.assert_called_once()
  1336. mock_context.dump_component.assert_called_once()
  1337. @pytest.mark.asyncio
  1338. async def test_to_config_agent_with_handoffs(self) -> None:
  1339. """Test _to_config method with agent having handoffs."""
  1340. model_client = MagicMock()
  1341. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1342. model_client.dump_component = MagicMock(
  1343. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1344. )
  1345. mock_context = MagicMock()
  1346. mock_context.dump_component = MagicMock(
  1347. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1348. )
  1349. agent = AssistantAgent(
  1350. name="test_agent",
  1351. model_client=model_client,
  1352. handoffs=["agent1", Handoff(target="agent2")],
  1353. model_context=mock_context,
  1354. )
  1355. config = agent._to_config() # type: ignore[reportPrivateUsage]
  1356. assert config.handoffs is not None
  1357. assert len(config.handoffs) == 2
  1358. handoff_targets: List[str] = [h.target if hasattr(h, "target") else str(h) for h in config.handoffs] # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
  1359. assert "agent1" in handoff_targets
  1360. assert "agent2" in handoff_targets
  1361. @pytest.mark.asyncio
  1362. async def test_to_config_agent_with_memory(self) -> None:
  1363. """Test _to_config method with agent having memory modules."""
  1364. model_client = MagicMock()
  1365. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1366. model_client.dump_component = MagicMock(
  1367. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1368. )
  1369. mock_context = MagicMock()
  1370. mock_context.dump_component = MagicMock(
  1371. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1372. )
  1373. mock_memory = MockMemory()
  1374. agent = AssistantAgent(
  1375. name="test_agent",
  1376. model_client=model_client,
  1377. memory=[mock_memory],
  1378. model_context=mock_context,
  1379. )
  1380. config = agent._to_config() # type: ignore[reportPrivateUsage]
  1381. assert config.memory is not None
  1382. assert len(config.memory) == 1
  1383. assert config.memory[0].provider == "test"
  1384. assert config.memory[0].config == {"type": "mock_memory"}
  1385. @pytest.mark.asyncio
  1386. async def test_to_config_agent_with_workbench(self) -> None:
  1387. """Test _to_config method with agent having workbench."""
  1388. model_client = MagicMock()
  1389. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1390. model_client.dump_component = MagicMock(
  1391. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1392. )
  1393. mock_context = MagicMock()
  1394. mock_context.dump_component = MagicMock(
  1395. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1396. )
  1397. mock_workbench = MagicMock()
  1398. mock_workbench.dump_component = MagicMock(
  1399. return_value=ComponentModel(provider="test", config={"type": "mock_workbench"})
  1400. )
  1401. agent = AssistantAgent(
  1402. name="test_agent",
  1403. model_client=model_client,
  1404. tools=[mock_tool_function],
  1405. model_context=mock_context,
  1406. )
  1407. # Replace the workbench with our mock
  1408. agent._workbench = [mock_workbench] # type: ignore[reportPrivateUsage]
  1409. config = agent._to_config() # type: ignore[reportPrivateUsage]
  1410. assert config.workbench is not None
  1411. assert len(config.workbench) == 1
  1412. mock_workbench.dump_component.assert_called_once()
  1413. @pytest.mark.asyncio
  1414. async def test_to_config_agent_with_structured_output(self) -> None:
  1415. """Test _to_config method with agent having structured output."""
  1416. model_client = MagicMock()
  1417. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1418. model_client.dump_component = MagicMock(
  1419. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1420. )
  1421. mock_context = MagicMock()
  1422. mock_context.dump_component = MagicMock(
  1423. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1424. )
  1425. agent = AssistantAgent(
  1426. name="test_agent",
  1427. model_client=model_client,
  1428. output_content_type=StructuredOutput,
  1429. model_context=mock_context,
  1430. )
  1431. config = agent._to_config() # type: ignore[reportPrivateUsage]
  1432. assert config.structured_message_factory is not None
  1433. assert config.reflect_on_tool_use is True # Should be True with structured output
  1434. @pytest.mark.asyncio
  1435. async def test_to_config_system_message_none(self) -> None:
  1436. """Test _to_config method with system_message=None."""
  1437. model_client = MagicMock()
  1438. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1439. model_client.dump_component = MagicMock(
  1440. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1441. )
  1442. mock_context = MagicMock()
  1443. mock_context.dump_component = MagicMock(
  1444. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1445. )
  1446. agent = AssistantAgent(
  1447. name="test_agent",
  1448. model_client=model_client,
  1449. system_message=None,
  1450. model_context=mock_context,
  1451. )
  1452. config = agent._to_config() # type: ignore[reportPrivateUsage]
  1453. assert config.system_message is None
  1454. @pytest.mark.asyncio
  1455. async def test_from_config_basic_agent(self) -> None:
  1456. """Test _from_config method with basic agent configuration."""
  1457. mock_model_client = MagicMock()
  1458. mock_model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1459. with patch("autogen_core.models.ChatCompletionClient.load_component", return_value=mock_model_client):
  1460. config = AssistantAgentConfig(
  1461. name="test_agent",
  1462. model_client=ComponentModel(provider="test", config={"type": "mock_client"}),
  1463. description="Test description",
  1464. system_message="Test system",
  1465. model_client_stream=True,
  1466. reflect_on_tool_use=False,
  1467. tool_call_summary_format="{tool_name}: {result}",
  1468. metadata={"test": "value"},
  1469. )
  1470. agent = AssistantAgent._from_config(config) # type: ignore[reportPrivateUsage]
  1471. assert agent.name == "test_agent"
  1472. assert agent.description == "Test description"
  1473. assert agent._model_client_stream is True # type: ignore[reportPrivateUsage]
  1474. assert agent._reflect_on_tool_use is False # type: ignore[reportPrivateUsage]
  1475. assert agent._tool_call_summary_format == "{tool_name}: {result}" # type: ignore[reportPrivateUsage]
  1476. assert agent._metadata == {"test": "value"} # type: ignore[reportPrivateUsage]
  1477. @pytest.mark.asyncio
  1478. async def test_from_config_with_structured_output(self) -> None:
  1479. """Test _from_config method with structured output configuration."""
  1480. mock_model_client = MagicMock()
  1481. mock_model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  1482. mock_structured_factory = MagicMock()
  1483. mock_structured_factory.format_string = "Test format"
  1484. mock_structured_factory.ContentModel = StructuredOutput
  1485. with (
  1486. patch("autogen_core.models.ChatCompletionClient.load_component", return_value=mock_model_client),
  1487. patch(
  1488. "autogen_agentchat.messages.StructuredMessageFactory.load_component",
  1489. return_value=mock_structured_factory,
  1490. ),
  1491. ):
  1492. config = AssistantAgentConfig(
  1493. name="test_agent",
  1494. model_client=ComponentModel(provider="test", config={"type": "mock_client"}),
  1495. description="Test description",
  1496. reflect_on_tool_use=True,
  1497. tool_call_summary_format="{result}",
  1498. structured_message_factory=ComponentModel(provider="test", config={"type": "mock_factory"}),
  1499. )
  1500. agent = AssistantAgent._from_config(config) # type: ignore[reportPrivateUsage]
  1501. assert agent._reflect_on_tool_use is True # type: ignore[reportPrivateUsage]
  1502. assert agent._output_content_type == StructuredOutput # type: ignore[reportPrivateUsage]
  1503. assert agent._output_content_type_format == "Test format" # type: ignore[reportPrivateUsage]
  1504. @pytest.mark.asyncio
  1505. async def test_from_config_with_workbench_and_memory(self) -> None:
  1506. """Test _from_config method with workbench and memory."""
  1507. mock_model_client = MagicMock()
  1508. mock_model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1509. mock_workbench = MagicMock()
  1510. mock_memory = MockMemory()
  1511. mock_context = MagicMock()
  1512. with (
  1513. patch("autogen_core.models.ChatCompletionClient.load_component", return_value=mock_model_client),
  1514. patch("autogen_core.tools.Workbench.load_component", return_value=mock_workbench),
  1515. patch("autogen_core.memory.Memory.load_component", return_value=mock_memory),
  1516. patch("autogen_core.model_context.ChatCompletionContext.load_component", return_value=mock_context),
  1517. ):
  1518. config = AssistantAgentConfig(
  1519. name="test_agent",
  1520. model_client=ComponentModel(provider="test", config={"type": "mock_client"}),
  1521. description="Test description",
  1522. workbench=[ComponentModel(provider="test", config={"type": "mock_workbench"})],
  1523. memory=[ComponentModel(provider="test", config={"type": "mock_memory"})],
  1524. model_context=ComponentModel(provider="test", config={"type": "mock_context"}),
  1525. reflect_on_tool_use=True,
  1526. tool_call_summary_format="{result}",
  1527. )
  1528. agent = AssistantAgent._from_config(config) # type: ignore[reportPrivateUsage]
  1529. assert len(agent._workbench) == 1 # type: ignore[reportPrivateUsage]
  1530. assert agent._memory is not None # type: ignore[reportPrivateUsage]
  1531. assert len(agent._memory) == 1 # type: ignore[reportPrivateUsage]
  1532. assert agent._model_context == mock_context # type: ignore[reportPrivateUsage]
  1533. @pytest.mark.asyncio
  1534. async def test_config_roundtrip_consistency(self) -> None:
  1535. """Test that converting to config and back preserves agent properties."""
  1536. model_client = MagicMock()
  1537. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  1538. model_client.dump_component = MagicMock(
  1539. return_value=ComponentModel(provider="test", config={"type": "mock_client"})
  1540. )
  1541. mock_context = MagicMock()
  1542. mock_context.dump_component = MagicMock(
  1543. return_value=ComponentModel(provider="test", config={"type": "mock_context"})
  1544. )
  1545. original_agent = AssistantAgent(
  1546. name="test_agent",
  1547. model_client=model_client,
  1548. description="Test description",
  1549. system_message="Test system message",
  1550. model_client_stream=True,
  1551. reflect_on_tool_use=True,
  1552. max_tool_iterations=5,
  1553. tool_call_summary_format="{tool_name}: {result}",
  1554. handoffs=["agent1"],
  1555. model_context=mock_context,
  1556. metadata={"test": "value"},
  1557. )
  1558. # Convert to config
  1559. config = original_agent._to_config() # type: ignore[reportPrivateUsage]
  1560. # Verify config properties
  1561. assert config.name == "test_agent"
  1562. assert config.description == "Test description"
  1563. assert config.system_message == "Test system message"
  1564. assert config.model_client_stream is True
  1565. assert config.reflect_on_tool_use is True
  1566. assert config.max_tool_iterations == 5
  1567. assert config.tool_call_summary_format == "{tool_name}: {result}"
  1568. assert config.metadata == {"test": "value"}
  1569. class TestAssistantAgentThoughtHandling:
  1570. """Test suite for thought handling functionality."""
  1571. @pytest.mark.asyncio
  1572. async def test_thought_event_yielded_from_model_result(self) -> None:
  1573. """Test that thought events are yielded when model result contains thoughts."""
  1574. model_client = ReplayChatCompletionClient(
  1575. [
  1576. CreateResult(
  1577. finish_reason="stop",
  1578. content="Final response",
  1579. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1580. cached=False,
  1581. thought="This is my internal thought process",
  1582. ),
  1583. ],
  1584. model_info={
  1585. "function_calling": False,
  1586. "vision": False,
  1587. "json_output": False,
  1588. "family": ModelFamily.GPT_4O,
  1589. "structured_output": False,
  1590. },
  1591. )
  1592. agent = AssistantAgent(
  1593. name="test_agent",
  1594. model_client=model_client,
  1595. )
  1596. messages: List[Any] = []
  1597. async for message in agent.on_messages_stream(
  1598. [TextMessage(content="Test", source="user")], CancellationToken()
  1599. ):
  1600. messages.append(message)
  1601. # Should have ThoughtEvent in the stream
  1602. thought_events = [msg for msg in messages if isinstance(msg, ThoughtEvent)]
  1603. assert len(thought_events) == 1
  1604. assert thought_events[0].content == "This is my internal thought process"
  1605. assert thought_events[0].source == "test_agent"
  1606. @pytest.mark.asyncio
  1607. async def test_thought_event_with_tool_calls(self) -> None:
  1608. """Test that thought events are yielded when tool calls have thoughts."""
  1609. model_client = ReplayChatCompletionClient(
  1610. [
  1611. CreateResult(
  1612. finish_reason="function_calls",
  1613. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  1614. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1615. cached=False,
  1616. thought="I need to use this tool to help the user",
  1617. ),
  1618. CreateResult(
  1619. finish_reason="stop",
  1620. content="Tool execution completed",
  1621. usage=RequestUsage(prompt_tokens=15, completion_tokens=10),
  1622. cached=False,
  1623. ),
  1624. ],
  1625. model_info={
  1626. "function_calling": True,
  1627. "vision": False,
  1628. "json_output": False,
  1629. "family": ModelFamily.GPT_4O,
  1630. "structured_output": False,
  1631. },
  1632. )
  1633. agent = AssistantAgent(
  1634. name="test_agent",
  1635. model_client=model_client,
  1636. tools=[mock_tool_function],
  1637. max_tool_iterations=1,
  1638. )
  1639. messages: List[Any] = []
  1640. async for message in agent.on_messages_stream(
  1641. [TextMessage(content="Test", source="user")], CancellationToken()
  1642. ):
  1643. messages.append(message)
  1644. # Should have ThoughtEvent in the stream
  1645. thought_events = [msg for msg in messages if isinstance(msg, ThoughtEvent)]
  1646. assert len(thought_events) == 1
  1647. assert thought_events[0].content == "I need to use this tool to help the user"
  1648. assert thought_events[0].source == "test_agent"
  1649. @pytest.mark.asyncio
  1650. async def test_thought_event_with_reflection(self) -> None:
  1651. """Test that thought events are yielded during reflection."""
  1652. model_client = ReplayChatCompletionClient(
  1653. [
  1654. # Initial tool call with thought
  1655. CreateResult(
  1656. finish_reason="function_calls",
  1657. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  1658. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1659. cached=False,
  1660. thought="Initial thought before tool call",
  1661. ),
  1662. # Reflection with thought
  1663. CreateResult(
  1664. finish_reason="stop",
  1665. content="Based on the tool result, here's my response",
  1666. usage=RequestUsage(prompt_tokens=15, completion_tokens=10),
  1667. cached=False,
  1668. thought="Reflection thought after tool execution",
  1669. ),
  1670. ],
  1671. model_info={
  1672. "function_calling": True,
  1673. "vision": False,
  1674. "json_output": False,
  1675. "family": ModelFamily.GPT_4O,
  1676. "structured_output": False,
  1677. },
  1678. )
  1679. agent = AssistantAgent(
  1680. name="test_agent",
  1681. model_client=model_client,
  1682. tools=[mock_tool_function],
  1683. reflect_on_tool_use=True,
  1684. model_client_stream=True, # Enable streaming
  1685. )
  1686. messages: List[Any] = []
  1687. async for message in agent.on_messages_stream(
  1688. [TextMessage(content="Test", source="user")], CancellationToken()
  1689. ):
  1690. messages.append(message)
  1691. # Should have two ThoughtEvents - one for initial call, one for reflection
  1692. thought_events = [msg for msg in messages if isinstance(msg, ThoughtEvent)]
  1693. assert len(thought_events) == 2
  1694. thought_contents = [event.content for event in thought_events]
  1695. assert "Initial thought before tool call" in thought_contents
  1696. assert "Reflection thought after tool execution" in thought_contents
  1697. @pytest.mark.asyncio
  1698. async def test_thought_event_with_tool_call_loop(self) -> None:
  1699. """Test that thought events are yielded in tool call loops."""
  1700. model_client = ReplayChatCompletionClient(
  1701. [
  1702. # First tool call with thought
  1703. CreateResult(
  1704. finish_reason="function_calls",
  1705. content=[FunctionCall(id="1", arguments=json.dumps({"param": "first"}), name="mock_tool_function")],
  1706. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1707. cached=False,
  1708. thought="First iteration thought",
  1709. ),
  1710. # Second tool call with thought
  1711. CreateResult(
  1712. finish_reason="function_calls",
  1713. content=[
  1714. FunctionCall(id="2", arguments=json.dumps({"param": "second"}), name="mock_tool_function")
  1715. ],
  1716. usage=RequestUsage(prompt_tokens=12, completion_tokens=5),
  1717. cached=False,
  1718. thought="Second iteration thought",
  1719. ),
  1720. # Final response with thought
  1721. CreateResult(
  1722. finish_reason="stop",
  1723. content="Loop completed",
  1724. usage=RequestUsage(prompt_tokens=15, completion_tokens=10),
  1725. cached=False,
  1726. thought="Final completion thought",
  1727. ),
  1728. ],
  1729. model_info={
  1730. "function_calling": True,
  1731. "vision": False,
  1732. "json_output": False,
  1733. "family": ModelFamily.GPT_4O,
  1734. "structured_output": False,
  1735. },
  1736. )
  1737. agent = AssistantAgent(
  1738. name="test_agent",
  1739. model_client=model_client,
  1740. tools=[mock_tool_function],
  1741. max_tool_iterations=3,
  1742. )
  1743. messages: List[Any] = []
  1744. async for message in agent.on_messages_stream(
  1745. [TextMessage(content="Test", source="user")], CancellationToken()
  1746. ):
  1747. messages.append(message)
  1748. # Should have three ThoughtEvents - one for each iteration
  1749. thought_events = [msg for msg in messages if isinstance(msg, ThoughtEvent)]
  1750. assert len(thought_events) == 3
  1751. thought_contents = [event.content for event in thought_events]
  1752. assert "First iteration thought" in thought_contents
  1753. assert "Second iteration thought" in thought_contents
  1754. assert "Final completion thought" in thought_contents
  1755. @pytest.mark.asyncio
  1756. async def test_thought_event_with_handoff(self) -> None:
  1757. """Test that thought events are included in handoff context."""
  1758. model_client = ReplayChatCompletionClient(
  1759. [
  1760. CreateResult(
  1761. finish_reason="function_calls",
  1762. content=[
  1763. FunctionCall(
  1764. id="1", arguments=json.dumps({"target": "other_agent"}), name="transfer_to_other_agent"
  1765. )
  1766. ],
  1767. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1768. cached=False,
  1769. thought="I need to hand this off to another agent",
  1770. ),
  1771. ],
  1772. model_info={
  1773. "function_calling": True,
  1774. "vision": False,
  1775. "json_output": False,
  1776. "family": ModelFamily.GPT_4O,
  1777. "structured_output": False,
  1778. },
  1779. )
  1780. agent = AssistantAgent(
  1781. name="test_agent",
  1782. model_client=model_client,
  1783. handoffs=["other_agent"],
  1784. max_tool_iterations=1,
  1785. )
  1786. result = await agent.run(task="Test handoff with thought")
  1787. # Should have ThoughtEvent in inner messages
  1788. thought_events = [msg for msg in result.messages if isinstance(msg, ThoughtEvent)]
  1789. assert len(thought_events) == 1
  1790. assert thought_events[0].content == "I need to hand this off to another agent"
  1791. # Should have handoff message with thought in context
  1792. handoff_message = result.messages[-1]
  1793. assert isinstance(handoff_message, HandoffMessage)
  1794. assert len(handoff_message.context) == 1
  1795. assert isinstance(handoff_message.context[0], AssistantMessage)
  1796. assert handoff_message.context[0].content == "I need to hand this off to another agent"
  1797. @pytest.mark.asyncio
  1798. async def test_no_thought_event_when_no_thought(self) -> None:
  1799. """Test that no thought events are yielded when model result has no thoughts."""
  1800. model_client = ReplayChatCompletionClient(
  1801. [
  1802. CreateResult(
  1803. finish_reason="stop",
  1804. content="Simple response without thought",
  1805. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1806. cached=False,
  1807. # No thought field
  1808. ),
  1809. ],
  1810. model_info={
  1811. "function_calling": False,
  1812. "vision": False,
  1813. "json_output": False,
  1814. "family": ModelFamily.GPT_4O,
  1815. "structured_output": False,
  1816. },
  1817. )
  1818. agent = AssistantAgent(
  1819. name="test_agent",
  1820. model_client=model_client,
  1821. )
  1822. messages: List[Any] = []
  1823. async for message in agent.on_messages_stream(
  1824. [TextMessage(content="Test", source="user")], CancellationToken()
  1825. ):
  1826. messages.append(message)
  1827. # Should have no ThoughtEvents
  1828. thought_events = [msg for msg in messages if isinstance(msg, ThoughtEvent)]
  1829. assert len(thought_events) == 0
  1830. @pytest.mark.asyncio
  1831. async def test_thought_event_context_preservation(self) -> None:
  1832. """Test that thoughts are properly preserved in model context."""
  1833. model_client = ReplayChatCompletionClient(
  1834. [
  1835. CreateResult(
  1836. finish_reason="stop",
  1837. content="Response with thought",
  1838. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1839. cached=False,
  1840. thought="Internal reasoning",
  1841. ),
  1842. ],
  1843. model_info={
  1844. "function_calling": False,
  1845. "vision": False,
  1846. "json_output": False,
  1847. "family": ModelFamily.GPT_4O,
  1848. "structured_output": False,
  1849. },
  1850. )
  1851. agent = AssistantAgent(
  1852. name="test_agent",
  1853. model_client=model_client,
  1854. )
  1855. await agent.run(task="Test thought preservation")
  1856. # Check that the model context contains the thought
  1857. messages = await agent.model_context.get_messages()
  1858. assistant_messages = [msg for msg in messages if isinstance(msg, AssistantMessage)]
  1859. assert len(assistant_messages) > 0
  1860. # The last assistant message should have the thought
  1861. last_assistant_msg = assistant_messages[-1]
  1862. # Fix line 2730 - properly check for thought attribute with type checking
  1863. if hasattr(last_assistant_msg, "thought"):
  1864. thought_content = cast(str, last_assistant_msg.thought)
  1865. assert thought_content == "Internal reasoning"
  1866. class TestAssistantAgentAdvancedScenarios:
  1867. """Test suite for advanced usage scenarios."""
  1868. @pytest.mark.asyncio
  1869. async def test_handoff_without_tool_calls(self) -> None:
  1870. """Test handoff without any tool calls."""
  1871. model_client = ReplayChatCompletionClient(
  1872. [
  1873. CreateResult(
  1874. finish_reason="function_calls",
  1875. content=[
  1876. FunctionCall(id="1", arguments=json.dumps({"target": "agent2"}), name="transfer_to_agent2")
  1877. ],
  1878. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1879. cached=False,
  1880. ),
  1881. ],
  1882. model_info={
  1883. "function_calling": True,
  1884. "vision": False,
  1885. "json_output": False,
  1886. "family": ModelFamily.GPT_4O,
  1887. "structured_output": False,
  1888. },
  1889. )
  1890. agent = AssistantAgent(
  1891. name="test_agent",
  1892. model_client=model_client,
  1893. handoffs=["agent2"],
  1894. )
  1895. result = await agent.run(task="Handoff to agent2")
  1896. # Should return HandoffMessage
  1897. assert isinstance(result.messages[-1], HandoffMessage)
  1898. assert result.messages[-1].target == "agent2"
  1899. @pytest.mark.asyncio
  1900. async def test_multiple_handoff_warning(self) -> None:
  1901. """Test warning for multiple handoffs."""
  1902. model_client = ReplayChatCompletionClient(
  1903. [
  1904. CreateResult(
  1905. finish_reason="function_calls",
  1906. content=[
  1907. FunctionCall(id="1", arguments=json.dumps({"target": "agent2"}), name="transfer_to_agent2"),
  1908. FunctionCall(id="2", arguments=json.dumps({"target": "agent3"}), name="transfer_to_agent3"),
  1909. ],
  1910. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1911. cached=False,
  1912. ),
  1913. ],
  1914. model_info={
  1915. "function_calling": True,
  1916. "vision": False,
  1917. "json_output": False,
  1918. "family": ModelFamily.GPT_4O,
  1919. "structured_output": False,
  1920. },
  1921. )
  1922. agent = AssistantAgent(
  1923. name="test_agent",
  1924. model_client=model_client,
  1925. handoffs=["agent2", "agent3"],
  1926. )
  1927. with pytest.warns(UserWarning, match="Multiple handoffs detected"):
  1928. result = await agent.run(task="Multiple handoffs")
  1929. # Should only execute first handoff
  1930. assert isinstance(result.messages[-1], HandoffMessage)
  1931. assert result.messages[-1].target == "agent2"
  1932. @pytest.mark.asyncio
  1933. async def test_structured_output_with_reflection(self) -> None:
  1934. """Test structured output with reflection enabled."""
  1935. model_client = ReplayChatCompletionClient(
  1936. [
  1937. CreateResult(
  1938. finish_reason="function_calls",
  1939. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  1940. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1941. cached=False,
  1942. ),
  1943. CreateResult(
  1944. finish_reason="stop",
  1945. content='{"content": "Structured response", "confidence": 0.95}',
  1946. usage=RequestUsage(prompt_tokens=15, completion_tokens=10),
  1947. cached=False,
  1948. ),
  1949. ],
  1950. model_info={
  1951. "function_calling": True,
  1952. "vision": False,
  1953. "json_output": False,
  1954. "family": ModelFamily.GPT_4O,
  1955. "structured_output": False,
  1956. },
  1957. )
  1958. agent = AssistantAgent(
  1959. name="test_agent",
  1960. model_client=model_client,
  1961. tools=[mock_tool_function],
  1962. output_content_type=StructuredOutput,
  1963. reflect_on_tool_use=True,
  1964. )
  1965. result = await agent.run(task="Test structured output with reflection")
  1966. # Should return StructuredMessage
  1967. from autogen_agentchat.messages import StructuredMessage
  1968. final_message = result.messages[-1]
  1969. assert isinstance(final_message, StructuredMessage)
  1970. # Fix line 1710 - properly access structured content with explicit type annotation
  1971. structured_message: StructuredMessage[StructuredOutput] = cast(
  1972. StructuredMessage[StructuredOutput], final_message
  1973. )
  1974. assert structured_message.content.content == "Structured response"
  1975. assert structured_message.content.confidence == 0.95
  1976. class TestAssistantAgentAdvancedToolFeatures:
  1977. """Test suite for advanced tool features including custom formatters."""
  1978. @pytest.mark.asyncio
  1979. async def test_custom_tool_call_summary_formatter(self) -> None:
  1980. """Test custom tool call summary formatter functionality."""
  1981. model_client = ReplayChatCompletionClient(
  1982. [
  1983. CreateResult(
  1984. finish_reason="function_calls",
  1985. content=[
  1986. FunctionCall(id="1", arguments=json.dumps({"param": "success"}), name="mock_tool_function"),
  1987. FunctionCall(id="2", arguments=json.dumps({"param": "error"}), name="mock_tool_function"),
  1988. ],
  1989. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  1990. cached=False,
  1991. ),
  1992. ],
  1993. model_info={
  1994. "function_calling": True,
  1995. "vision": False,
  1996. "json_output": False,
  1997. "family": ModelFamily.GPT_4O,
  1998. "structured_output": False,
  1999. },
  2000. )
  2001. def custom_formatter(call: FunctionCall, result: FunctionExecutionResult) -> str:
  2002. if result.is_error:
  2003. return f"ERROR in {call.name}: {result.content} (args: {call.arguments})"
  2004. else:
  2005. return f"SUCCESS: {call.name} completed"
  2006. agent = AssistantAgent(
  2007. name="test_agent",
  2008. model_client=model_client,
  2009. tools=[mock_tool_function],
  2010. tool_call_summary_formatter=custom_formatter,
  2011. reflect_on_tool_use=False,
  2012. )
  2013. result = await agent.run(task="Test custom formatter")
  2014. # Should return ToolCallSummaryMessage with custom formatting
  2015. final_message = result.messages[-1]
  2016. assert isinstance(final_message, ToolCallSummaryMessage)
  2017. # Fix line 1875 - properly access content with type checking
  2018. assert hasattr(final_message, "content"), "ToolCallSummaryMessage should have content attribute"
  2019. content = final_message.content
  2020. assert "SUCCESS: mock_tool_function completed" in content
  2021. assert "SUCCESS: mock_tool_function completed" in content # Both calls should be successful
  2022. @pytest.mark.asyncio
  2023. async def test_custom_tool_call_summary_format_string(self) -> None:
  2024. """Test custom tool call summary format string."""
  2025. model_client = ReplayChatCompletionClient(
  2026. [
  2027. CreateResult(
  2028. finish_reason="function_calls",
  2029. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  2030. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2031. cached=False,
  2032. ),
  2033. ],
  2034. model_info={
  2035. "function_calling": True,
  2036. "vision": False,
  2037. "json_output": False,
  2038. "family": ModelFamily.GPT_4O,
  2039. "structured_output": False,
  2040. },
  2041. )
  2042. agent = AssistantAgent(
  2043. name="test_agent",
  2044. model_client=model_client,
  2045. tools=[mock_tool_function],
  2046. tool_call_summary_format="Tool {tool_name} called with {arguments} -> {result}",
  2047. reflect_on_tool_use=False,
  2048. )
  2049. result = await agent.run(task="Test custom format string")
  2050. # Should return ToolCallSummaryMessage with custom format
  2051. final_message = result.messages[-1]
  2052. assert isinstance(final_message, ToolCallSummaryMessage)
  2053. content = final_message.content
  2054. assert "Tool mock_tool_function called with" in content
  2055. assert "Tool executed with: test" in content
  2056. @pytest.mark.asyncio
  2057. async def test_tool_call_summary_formatter_overrides_format_string(self) -> None:
  2058. """Test that tool_call_summary_formatter overrides format string."""
  2059. model_client = ReplayChatCompletionClient(
  2060. [
  2061. CreateResult(
  2062. finish_reason="function_calls",
  2063. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  2064. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2065. cached=False,
  2066. ),
  2067. ],
  2068. model_info={
  2069. "function_calling": True,
  2070. "vision": False,
  2071. "json_output": False,
  2072. "family": ModelFamily.GPT_4O,
  2073. "structured_output": False,
  2074. },
  2075. )
  2076. def custom_formatter(call: FunctionCall, result: FunctionExecutionResult) -> str:
  2077. return f"CUSTOM: {call.name} -> {result.content}"
  2078. agent = AssistantAgent(
  2079. name="test_agent",
  2080. model_client=model_client,
  2081. tools=[mock_tool_function],
  2082. tool_call_summary_format="This should be ignored: {result}",
  2083. tool_call_summary_formatter=custom_formatter,
  2084. reflect_on_tool_use=False,
  2085. )
  2086. result = await agent.run(task="Test formatter override")
  2087. # Should use custom formatter, not format string
  2088. final_message = result.messages[-1]
  2089. assert isinstance(final_message, ToolCallSummaryMessage)
  2090. content = final_message.content
  2091. assert "CUSTOM: mock_tool_function" in content
  2092. assert "This should be ignored" not in content
  2093. @pytest.mark.asyncio
  2094. async def test_output_content_type_format_string(self) -> None:
  2095. """Test structured output with custom format string."""
  2096. model_client = ReplayChatCompletionClient(
  2097. [
  2098. CreateResult(
  2099. finish_reason="stop",
  2100. content='{"content": "Test response", "confidence": 0.8}',
  2101. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2102. cached=False,
  2103. ),
  2104. ],
  2105. model_info={
  2106. "function_calling": False,
  2107. "vision": False,
  2108. "json_output": False,
  2109. "family": ModelFamily.GPT_4O,
  2110. "structured_output": False,
  2111. },
  2112. )
  2113. agent = AssistantAgent(
  2114. name="test_agent",
  2115. model_client=model_client,
  2116. output_content_type=StructuredOutput,
  2117. output_content_type_format="Response: {content} (Confidence: {confidence})",
  2118. )
  2119. result = await agent.run(task="Test structured output format")
  2120. # Should return StructuredMessage with custom format
  2121. final_message = result.messages[-1]
  2122. assert isinstance(final_message, StructuredMessage)
  2123. # Fix line 1880 - properly access structured content with explicit type annotation
  2124. structured_message: StructuredMessage[StructuredOutput] = cast(
  2125. StructuredMessage[StructuredOutput], final_message
  2126. )
  2127. assert structured_message.content.content == "Test response"
  2128. assert structured_message.content.confidence == 0.8
  2129. # The format string should be stored in the agent
  2130. assert hasattr(agent, "_output_content_type_format")
  2131. output_format = getattr(agent, "_output_content_type_format", None)
  2132. assert output_format == "Response: {content} (Confidence: {confidence})"
  2133. @pytest.mark.asyncio
  2134. async def test_tool_call_error_handling_with_custom_formatter(self) -> None:
  2135. """Test error handling in tool calls with custom formatter."""
  2136. def error_tool(param: str) -> str:
  2137. raise ValueError(f"Tool error with param: {param}")
  2138. model_client = ReplayChatCompletionClient(
  2139. [
  2140. CreateResult(
  2141. finish_reason="function_calls",
  2142. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="error_tool")],
  2143. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2144. cached=False,
  2145. ),
  2146. ],
  2147. model_info={
  2148. "function_calling": True,
  2149. "vision": False,
  2150. "json_output": False,
  2151. "family": ModelFamily.GPT_4O,
  2152. "structured_output": False,
  2153. },
  2154. )
  2155. def error_formatter(call: FunctionCall, result: FunctionExecutionResult) -> str:
  2156. if result.is_error:
  2157. return f"ERROR in {call.name}: {result.content}"
  2158. else:
  2159. return f"SUCCESS: {result.content}"
  2160. agent = AssistantAgent(
  2161. name="test_agent",
  2162. model_client=model_client,
  2163. tools=[error_tool],
  2164. tool_call_summary_formatter=error_formatter,
  2165. reflect_on_tool_use=False,
  2166. )
  2167. result = await agent.run(task="Test error handling")
  2168. # Should return ToolCallSummaryMessage with error formatting
  2169. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  2170. content = result.messages[-1].content
  2171. assert "ERROR in error_tool" in content
  2172. @pytest.mark.asyncio
  2173. async def test_multiple_tools_with_different_formats(self) -> None:
  2174. """Test multiple tool calls with different return formats."""
  2175. def json_tool(data: str) -> str:
  2176. return json.dumps({"result": data, "status": "success"})
  2177. def simple_tool(text: str) -> str:
  2178. return f"Processed: {text}"
  2179. model_client = ReplayChatCompletionClient(
  2180. [
  2181. CreateResult(
  2182. finish_reason="function_calls",
  2183. content=[
  2184. FunctionCall(id="1", arguments=json.dumps({"data": "json_data"}), name="json_tool"),
  2185. FunctionCall(id="2", arguments=json.dumps({"text": "simple_text"}), name="simple_tool"),
  2186. ],
  2187. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2188. cached=False,
  2189. ),
  2190. ],
  2191. model_info={
  2192. "function_calling": True,
  2193. "vision": False,
  2194. "json_output": False,
  2195. "family": ModelFamily.GPT_4O,
  2196. "structured_output": False,
  2197. },
  2198. )
  2199. def smart_formatter(call: FunctionCall, result: FunctionExecutionResult) -> str:
  2200. try:
  2201. # Try to parse as JSON
  2202. parsed = json.loads(result.content)
  2203. return f"{call.name}: {parsed}"
  2204. except json.JSONDecodeError:
  2205. # Plain text
  2206. return f"{call.name}: {result.content}"
  2207. agent = AssistantAgent(
  2208. name="test_agent",
  2209. model_client=model_client,
  2210. tools=[json_tool, simple_tool],
  2211. tool_call_summary_formatter=smart_formatter,
  2212. reflect_on_tool_use=False,
  2213. )
  2214. result = await agent.run(task="Test multiple tool formats")
  2215. # Should handle both JSON and plain text tools
  2216. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  2217. content = result.messages[-1].content
  2218. assert "json_tool:" in content
  2219. assert "simple_tool:" in content
  2220. assert "Processed: simple_text" in content
  2221. class TestAssistantAgentCancellationToken:
  2222. """Test suite for cancellation token handling."""
  2223. @pytest.mark.asyncio
  2224. async def test_cancellation_during_model_inference(self) -> None:
  2225. """Test cancellation token during model inference."""
  2226. model_client = MagicMock()
  2227. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2228. # Mock create method to check cancellation token
  2229. model_client.create = AsyncMock()
  2230. model_client.create.return_value = CreateResult(
  2231. finish_reason="stop",
  2232. content="Response",
  2233. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2234. cached=False,
  2235. )
  2236. agent = AssistantAgent(
  2237. name="test_agent",
  2238. model_client=model_client,
  2239. )
  2240. cancellation_token = CancellationToken()
  2241. result = await agent.on_messages([TextMessage(content="Test", source="user")], cancellation_token)
  2242. # Verify cancellation token was passed to model client
  2243. model_client.create.assert_called_once()
  2244. call_args = model_client.create.call_args
  2245. assert call_args.kwargs["cancellation_token"] == cancellation_token
  2246. # Verify result is not None
  2247. assert result is not None
  2248. @pytest.mark.asyncio
  2249. async def test_cancellation_during_streaming_inference(self) -> None:
  2250. """Test cancellation token during streaming model inference."""
  2251. model_client = MagicMock()
  2252. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2253. # Mock create_stream method
  2254. async def mock_create_stream(*args: Any, **kwargs: Any) -> Any:
  2255. yield "chunk1" # First chunk
  2256. yield "chunk2" # Second chunk
  2257. yield CreateResult(
  2258. finish_reason="stop",
  2259. content="chunk1chunk2",
  2260. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2261. cached=False,
  2262. )
  2263. model_client.create_stream = mock_create_stream
  2264. agent = AssistantAgent(
  2265. name="test_agent",
  2266. model_client=model_client,
  2267. model_client_stream=True,
  2268. )
  2269. cancellation_token = CancellationToken()
  2270. messages: List[Any] = []
  2271. async for message in agent.on_messages_stream([TextMessage(content="Test", source="user")], cancellation_token):
  2272. messages.append(message)
  2273. # Should have received streaming chunks and final response
  2274. chunk_events = [msg for msg in messages if isinstance(msg, ModelClientStreamingChunkEvent)]
  2275. assert len(chunk_events) == 2
  2276. assert chunk_events[0].content == "chunk1"
  2277. assert chunk_events[1].content == "chunk2"
  2278. @pytest.mark.asyncio
  2279. async def test_cancellation_during_tool_execution(self) -> None:
  2280. """Test cancellation token during tool execution."""
  2281. async def slow_tool(param: str) -> str:
  2282. await asyncio.sleep(0.1) # Simulate slow operation
  2283. return f"Slow result: {param}"
  2284. model_client = ReplayChatCompletionClient(
  2285. [
  2286. CreateResult(
  2287. finish_reason="function_calls",
  2288. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="slow_tool")],
  2289. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2290. cached=False,
  2291. ),
  2292. ],
  2293. model_info={
  2294. "function_calling": True,
  2295. "vision": False,
  2296. "json_output": False,
  2297. "family": ModelFamily.GPT_4O,
  2298. "structured_output": False,
  2299. },
  2300. )
  2301. agent = AssistantAgent(
  2302. name="test_agent",
  2303. model_client=model_client,
  2304. tools=[slow_tool],
  2305. )
  2306. cancellation_token = CancellationToken()
  2307. result = await agent.on_messages([TextMessage(content="Test", source="user")], cancellation_token)
  2308. # Tool should execute successfully with cancellation token
  2309. assert isinstance(result.chat_message, ToolCallSummaryMessage)
  2310. assert "Slow result: test" in result.chat_message.content
  2311. @pytest.mark.asyncio
  2312. async def test_cancellation_during_workbench_tool_execution(self) -> None:
  2313. """Test cancellation token during workbench tool execution."""
  2314. mock_workbench = MagicMock()
  2315. mock_workbench.list_tools = AsyncMock(return_value=[{"name": "test_tool", "description": "Test tool"}])
  2316. # Mock tool execution result
  2317. mock_result = MagicMock()
  2318. mock_result.to_text.return_value = "Workbench tool result"
  2319. mock_result.is_error = False
  2320. mock_workbench.call_tool = AsyncMock(return_value=mock_result)
  2321. model_client = ReplayChatCompletionClient(
  2322. [
  2323. CreateResult(
  2324. finish_reason="function_calls",
  2325. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="test_tool")],
  2326. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2327. cached=False,
  2328. ),
  2329. ],
  2330. model_info={
  2331. "function_calling": True,
  2332. "vision": False,
  2333. "json_output": False,
  2334. "family": ModelFamily.GPT_4O,
  2335. "structured_output": False,
  2336. },
  2337. )
  2338. agent = AssistantAgent(
  2339. name="test_agent",
  2340. model_client=model_client,
  2341. workbench=[mock_workbench],
  2342. )
  2343. cancellation_token = CancellationToken()
  2344. result = await agent.on_messages([TextMessage(content="Test", source="user")], cancellation_token)
  2345. # Verify cancellation token was passed to workbench
  2346. mock_workbench.call_tool.assert_called_once()
  2347. call_args = mock_workbench.call_tool.call_args
  2348. assert call_args.kwargs["cancellation_token"] == cancellation_token
  2349. # Verify result is not None
  2350. assert result is not None
  2351. @pytest.mark.asyncio
  2352. async def test_cancellation_during_memory_operations(self) -> None:
  2353. """Test cancellation token during memory operations."""
  2354. mock_memory = MagicMock()
  2355. mock_memory.update_context = AsyncMock(return_value=None)
  2356. model_client = MagicMock()
  2357. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2358. model_client.create = AsyncMock(
  2359. return_value=CreateResult(
  2360. finish_reason="stop",
  2361. content="Response",
  2362. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2363. cached=False,
  2364. )
  2365. )
  2366. agent = AssistantAgent(
  2367. name="test_agent",
  2368. model_client=model_client,
  2369. memory=[mock_memory],
  2370. )
  2371. cancellation_token = CancellationToken()
  2372. await agent.on_messages([TextMessage(content="Test", source="user")], cancellation_token)
  2373. # Memory update_context should be called
  2374. mock_memory.update_context.assert_called_once()
  2375. @pytest.mark.asyncio
  2376. async def test_reset_with_cancellation_token(self) -> None:
  2377. """Test agent reset with cancellation token."""
  2378. mock_context = MagicMock()
  2379. mock_context.clear = AsyncMock()
  2380. agent = AssistantAgent(
  2381. name="test_agent",
  2382. model_client=MagicMock(),
  2383. model_context=mock_context,
  2384. )
  2385. cancellation_token = CancellationToken()
  2386. await agent.on_reset(cancellation_token)
  2387. # Context clear should be called
  2388. mock_context.clear.assert_called_once()
  2389. class TestAssistantAgentStreamingEdgeCases:
  2390. """Test suite for streaming edge cases and error scenarios."""
  2391. @pytest.mark.asyncio
  2392. async def test_streaming_with_empty_chunks(self) -> None:
  2393. """Test streaming with empty chunks."""
  2394. model_client = MagicMock()
  2395. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2396. async def mock_create_stream(*args: Any, **kwargs: Any) -> Any:
  2397. yield "" # Empty chunk
  2398. yield "content"
  2399. yield "" # Another empty chunk
  2400. yield CreateResult(
  2401. finish_reason="stop",
  2402. content="content",
  2403. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2404. cached=False,
  2405. )
  2406. model_client.create_stream = mock_create_stream
  2407. agent = AssistantAgent(
  2408. name="test_agent",
  2409. model_client=model_client,
  2410. model_client_stream=True,
  2411. )
  2412. messages: List[Any] = []
  2413. async for message in agent.on_messages_stream(
  2414. [TextMessage(content="Test", source="user")], CancellationToken()
  2415. ):
  2416. messages.append(message)
  2417. # Should handle empty chunks gracefully
  2418. chunk_events = [msg for msg in messages if isinstance(msg, ModelClientStreamingChunkEvent)]
  2419. assert len(chunk_events) == 3 # Including empty chunks
  2420. assert chunk_events[0].content == ""
  2421. assert chunk_events[1].content == "content"
  2422. assert chunk_events[2].content == ""
  2423. @pytest.mark.asyncio
  2424. async def test_streaming_with_invalid_chunk_type(self) -> None:
  2425. """Test streaming with invalid chunk type raises error."""
  2426. model_client = MagicMock()
  2427. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2428. async def mock_create_stream(*args: Any, **kwargs: Any) -> Any:
  2429. yield "valid_chunk"
  2430. yield 123 # Invalid chunk type
  2431. yield CreateResult(
  2432. finish_reason="stop",
  2433. content="content",
  2434. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2435. cached=False,
  2436. )
  2437. model_client.create_stream = mock_create_stream
  2438. agent = AssistantAgent(
  2439. name="test_agent",
  2440. model_client=model_client,
  2441. model_client_stream=True,
  2442. )
  2443. with pytest.raises(RuntimeError, match="Invalid chunk type"):
  2444. async for _ in agent.on_messages_stream([TextMessage(content="Test", source="user")], CancellationToken()):
  2445. pass
  2446. @pytest.mark.asyncio
  2447. async def test_streaming_without_final_result(self) -> None:
  2448. """Test streaming without final CreateResult raises error."""
  2449. model_client = MagicMock()
  2450. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2451. async def mock_create_stream(*args: Any, **kwargs: Any) -> Any:
  2452. yield "chunk1"
  2453. yield "chunk2"
  2454. # No final CreateResult
  2455. model_client.create_stream = mock_create_stream
  2456. agent = AssistantAgent(
  2457. name="test_agent",
  2458. model_client=model_client,
  2459. model_client_stream=True,
  2460. )
  2461. with pytest.raises(RuntimeError, match="No final model result in streaming mode"):
  2462. async for _ in agent.on_messages_stream([TextMessage(content="Test", source="user")], CancellationToken()):
  2463. pass
  2464. @pytest.mark.asyncio
  2465. async def test_streaming_with_tool_calls_and_reflection(self) -> None:
  2466. """Test streaming with tool calls followed by reflection."""
  2467. model_client = MagicMock()
  2468. model_client.model_info = {"function_calling": True, "vision": False, "family": ModelFamily.GPT_4O}
  2469. call_count = 0
  2470. async def mock_create_stream(*args: Any, **kwargs: Any) -> Any:
  2471. nonlocal call_count
  2472. call_count += 1
  2473. if call_count == 1:
  2474. # First call: tool call
  2475. yield CreateResult(
  2476. finish_reason="function_calls",
  2477. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="mock_tool_function")],
  2478. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2479. cached=False,
  2480. )
  2481. else:
  2482. # Second call: reflection streaming
  2483. yield "Reflection "
  2484. yield "response "
  2485. yield "complete"
  2486. yield CreateResult(
  2487. finish_reason="stop",
  2488. content="Reflection response complete",
  2489. usage=RequestUsage(prompt_tokens=15, completion_tokens=10),
  2490. cached=False,
  2491. )
  2492. model_client.create_stream = mock_create_stream
  2493. agent = AssistantAgent(
  2494. name="test_agent",
  2495. model_client=model_client,
  2496. tools=[mock_tool_function],
  2497. reflect_on_tool_use=True,
  2498. model_client_stream=True,
  2499. )
  2500. messages: List[Any] = []
  2501. async for message in agent.on_messages_stream(
  2502. [TextMessage(content="Test", source="user")], CancellationToken()
  2503. ):
  2504. messages.append(message)
  2505. # Should have tool call events, execution events, and streaming chunks for reflection
  2506. tool_call_events = [msg for msg in messages if isinstance(msg, ToolCallRequestEvent)]
  2507. tool_exec_events = [msg for msg in messages if isinstance(msg, ToolCallExecutionEvent)]
  2508. chunk_events = [msg for msg in messages if isinstance(msg, ModelClientStreamingChunkEvent)]
  2509. assert len(tool_call_events) == 1
  2510. assert len(tool_exec_events) == 1
  2511. assert len(chunk_events) == 3 # Three reflection chunks
  2512. assert chunk_events[0].content == "Reflection "
  2513. assert chunk_events[1].content == "response "
  2514. assert chunk_events[2].content == "complete"
  2515. @pytest.mark.asyncio
  2516. async def test_streaming_with_large_chunks(self) -> None:
  2517. """Test streaming with large chunks."""
  2518. model_client = MagicMock()
  2519. model_client.model_info = {"function_calling": False, "vision": False, "family": ModelFamily.GPT_4O}
  2520. large_chunk = "x" * 10000 # 10KB chunk
  2521. async def mock_create_stream(*args: Any, **kwargs: Any) -> Any:
  2522. yield large_chunk
  2523. yield CreateResult(
  2524. finish_reason="stop",
  2525. content=large_chunk,
  2526. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2527. cached=False,
  2528. )
  2529. model_client.create_stream = mock_create_stream
  2530. agent = AssistantAgent(
  2531. name="test_agent",
  2532. model_client=model_client,
  2533. model_client_stream=True,
  2534. )
  2535. messages: List[Any] = []
  2536. async for message in agent.on_messages_stream(
  2537. [TextMessage(content="Test", source="user")], CancellationToken()
  2538. ):
  2539. messages.append(message)
  2540. # Should handle large chunks
  2541. chunk_events = [msg for msg in messages if isinstance(msg, ModelClientStreamingChunkEvent)]
  2542. assert len(chunk_events) == 1
  2543. assert len(chunk_events[0].content) == 10000
  2544. class TestAssistantAgentWorkbenchIntegration:
  2545. """Test suite for comprehensive workbench testing."""
  2546. @pytest.mark.asyncio
  2547. async def test_multiple_workbenches(self) -> None:
  2548. """Test agent with multiple workbenches."""
  2549. mock_workbench1 = MagicMock()
  2550. mock_workbench1.list_tools = AsyncMock(return_value=[{"name": "tool1", "description": "Tool from workbench 1"}])
  2551. mock_result1 = MagicMock()
  2552. mock_result1.to_text.return_value = "Result from workbench 1"
  2553. mock_result1.is_error = False
  2554. mock_workbench1.call_tool = AsyncMock(return_value=mock_result1)
  2555. mock_workbench2 = MagicMock()
  2556. mock_workbench2.list_tools = AsyncMock(return_value=[{"name": "tool2", "description": "Tool from workbench 2"}])
  2557. mock_result2 = MagicMock()
  2558. mock_result2.to_text.return_value = "Result from workbench 2"
  2559. mock_result2.is_error = False
  2560. mock_workbench2.call_tool = AsyncMock(return_value=mock_result2)
  2561. model_client = ReplayChatCompletionClient(
  2562. [
  2563. CreateResult(
  2564. finish_reason="function_calls",
  2565. content=[
  2566. FunctionCall(id="1", arguments=json.dumps({"param": "test1"}), name="tool1"),
  2567. FunctionCall(id="2", arguments=json.dumps({"param": "test2"}), name="tool2"),
  2568. ],
  2569. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2570. cached=False,
  2571. ),
  2572. ],
  2573. model_info={
  2574. "function_calling": True,
  2575. "vision": False,
  2576. "json_output": False,
  2577. "family": ModelFamily.GPT_4O,
  2578. "structured_output": False,
  2579. },
  2580. )
  2581. agent = AssistantAgent(
  2582. name="test_agent",
  2583. model_client=model_client,
  2584. workbench=[mock_workbench1, mock_workbench2],
  2585. )
  2586. result = await agent.run(task="Test multiple workbenches")
  2587. # Both workbenches should be called
  2588. mock_workbench1.call_tool.assert_called_once()
  2589. mock_workbench2.call_tool.assert_called_once()
  2590. # Should return summary with both results
  2591. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  2592. content = result.messages[-1].content
  2593. assert "Result from workbench 1" in content
  2594. assert "Result from workbench 2" in content
  2595. @pytest.mark.asyncio
  2596. async def test_workbench_tool_not_found(self) -> None:
  2597. """Test handling when tool is not found in any workbench."""
  2598. mock_workbench = MagicMock()
  2599. mock_workbench.list_tools = AsyncMock(
  2600. return_value=[{"name": "available_tool", "description": "Available tool"}]
  2601. )
  2602. model_client = ReplayChatCompletionClient(
  2603. [
  2604. CreateResult(
  2605. finish_reason="function_calls",
  2606. content=[FunctionCall(id="1", arguments=json.dumps({"param": "test"}), name="missing_tool")],
  2607. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2608. cached=False,
  2609. ),
  2610. ],
  2611. model_info={
  2612. "function_calling": True,
  2613. "vision": False,
  2614. "json_output": False,
  2615. "family": ModelFamily.GPT_4O,
  2616. "structured_output": False,
  2617. },
  2618. )
  2619. agent = AssistantAgent(
  2620. name="test_agent",
  2621. model_client=model_client,
  2622. workbench=[mock_workbench],
  2623. )
  2624. result = await agent.run(task="Test missing tool")
  2625. # Should return error message for missing tool
  2626. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  2627. content = result.messages[-1].content
  2628. assert "tool 'missing_tool' not found" in content
  2629. @pytest.mark.asyncio
  2630. async def test_workbench_concurrent_tool_execution(self) -> None:
  2631. """Test concurrent execution of multiple workbench tools."""
  2632. mock_workbench = MagicMock()
  2633. mock_workbench.list_tools = AsyncMock(
  2634. return_value=[
  2635. {"name": "concurrent_tool1", "description": "Concurrent tool 1"},
  2636. {"name": "concurrent_tool2", "description": "Concurrent tool 2"},
  2637. ]
  2638. )
  2639. call_order: List[str] = []
  2640. async def mock_call_tool(name: str, **kwargs: Any) -> Any:
  2641. call_order.append(f"start_{name}")
  2642. await asyncio.sleep(0.01) # Simulate work
  2643. call_order.append(f"end_{name}")
  2644. mock_result = MagicMock()
  2645. mock_result.to_text.return_value = f"Result from {name}"
  2646. mock_result.is_error = False
  2647. return mock_result
  2648. mock_workbench.call_tool = mock_call_tool
  2649. model_client = ReplayChatCompletionClient(
  2650. [
  2651. CreateResult(
  2652. finish_reason="function_calls",
  2653. content=[
  2654. FunctionCall(id="1", arguments=json.dumps({"param": "test1"}), name="concurrent_tool1"),
  2655. FunctionCall(id="2", arguments=json.dumps({"param": "test2"}), name="concurrent_tool2"),
  2656. ],
  2657. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2658. cached=False,
  2659. ),
  2660. ],
  2661. model_info={
  2662. "function_calling": True,
  2663. "vision": False,
  2664. "json_output": False,
  2665. "family": ModelFamily.GPT_4O,
  2666. "structured_output": False,
  2667. },
  2668. )
  2669. agent = AssistantAgent(
  2670. name="test_agent",
  2671. model_client=model_client,
  2672. workbench=[mock_workbench],
  2673. )
  2674. result = await agent.run(task="Test concurrent execution")
  2675. # Should execute both tools concurrently (both start before either ends)
  2676. assert "start_concurrent_tool1" in call_order
  2677. assert "start_concurrent_tool2" in call_order
  2678. # Both results should be present
  2679. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  2680. content = result.messages[-1].content
  2681. assert "Result from concurrent_tool1" in content
  2682. assert "Result from concurrent_tool2" in content
  2683. class TestAssistantAgentComplexIntegration:
  2684. """Test suite for complex integration scenarios."""
  2685. @pytest.mark.asyncio
  2686. async def test_complete_workflow_with_all_features(self) -> None:
  2687. """Test agent with tools, handoffs, memory, streaming, and reflection."""
  2688. # Setup memory
  2689. memory = MockMemory(["User prefers detailed explanations"])
  2690. # Setup model client with complex workflow
  2691. model_client = ReplayChatCompletionClient(
  2692. [
  2693. # Initial tool call
  2694. CreateResult(
  2695. finish_reason="function_calls",
  2696. content=[
  2697. FunctionCall(id="1", arguments=json.dumps({"param": "analysis"}), name="mock_tool_function")
  2698. ],
  2699. usage=RequestUsage(prompt_tokens=20, completion_tokens=10),
  2700. cached=False,
  2701. thought="I need to analyze this first",
  2702. ),
  2703. # Reflection result
  2704. CreateResult(
  2705. finish_reason="stop",
  2706. content="Based on the analysis, I can provide a detailed response. The user prefers comprehensive explanations.",
  2707. usage=RequestUsage(prompt_tokens=30, completion_tokens=15),
  2708. cached=False,
  2709. thought="I should be thorough based on user preference",
  2710. ),
  2711. ],
  2712. model_info={
  2713. "function_calling": True,
  2714. "vision": False,
  2715. "json_output": False,
  2716. "family": ModelFamily.GPT_4O,
  2717. "structured_output": False,
  2718. },
  2719. )
  2720. agent = AssistantAgent(
  2721. name="comprehensive_agent",
  2722. model_client=model_client,
  2723. tools=[mock_tool_function],
  2724. handoffs=["specialist_agent"],
  2725. memory=[memory],
  2726. reflect_on_tool_use=True,
  2727. model_client_stream=True,
  2728. tool_call_summary_format="Analysis: {result}",
  2729. metadata={"test": "comprehensive"},
  2730. )
  2731. messages: List[Any] = []
  2732. async for message in agent.on_messages_stream(
  2733. [TextMessage(content="Analyze this complex scenario", source="user")], CancellationToken()
  2734. ):
  2735. messages.append(message)
  2736. # Should have all types of events
  2737. memory_events = [msg for msg in messages if isinstance(msg, MemoryQueryEvent)]
  2738. thought_events = [msg for msg in messages if isinstance(msg, ThoughtEvent)]
  2739. tool_events = [msg for msg in messages if isinstance(msg, ToolCallRequestEvent)]
  2740. execution_events = [msg for msg in messages if isinstance(msg, ToolCallExecutionEvent)]
  2741. chunk_events = [msg for msg in messages if isinstance(msg, ModelClientStreamingChunkEvent)]
  2742. assert len(memory_events) > 0
  2743. assert len(thought_events) == 2 # Initial and reflection thoughts
  2744. assert len(tool_events) == 1
  2745. assert len(execution_events) == 1
  2746. assert len(chunk_events) == 0 # No streaming chunks since we removed the string responses
  2747. # Final response should be TextMessage from reflection
  2748. final_response = None
  2749. for msg in reversed(messages):
  2750. if isinstance(msg, Response):
  2751. final_response = msg
  2752. break
  2753. assert final_response is not None
  2754. assert isinstance(final_response.chat_message, TextMessage)
  2755. assert "comprehensive explanations" in final_response.chat_message.content
  2756. @pytest.mark.asyncio
  2757. async def test_error_recovery_in_complex_workflow(self) -> None:
  2758. """Test error recovery in complex workflow with multiple failures."""
  2759. def failing_tool(param: str) -> str:
  2760. if param == "fail":
  2761. raise ValueError("Tool failure")
  2762. return f"Success: {param}"
  2763. model_client = ReplayChatCompletionClient(
  2764. [
  2765. # Multiple tool calls, some failing
  2766. CreateResult(
  2767. finish_reason="function_calls",
  2768. content=[
  2769. FunctionCall(id="1", arguments=json.dumps({"param": "success"}), name="failing_tool"),
  2770. FunctionCall(id="2", arguments=json.dumps({"param": "fail"}), name="failing_tool"),
  2771. FunctionCall(id="3", arguments=json.dumps({"param": "success2"}), name="failing_tool"),
  2772. ],
  2773. usage=RequestUsage(prompt_tokens=20, completion_tokens=10),
  2774. cached=False,
  2775. ),
  2776. ],
  2777. model_info={
  2778. "function_calling": True,
  2779. "vision": False,
  2780. "json_output": False,
  2781. "family": ModelFamily.GPT_4O,
  2782. "structured_output": False,
  2783. },
  2784. )
  2785. def error_aware_formatter(call: FunctionCall, result: FunctionExecutionResult) -> str:
  2786. if result.is_error:
  2787. return f"⚠️ {call.name} failed: {result.content}"
  2788. else:
  2789. return f"✅ {call.name}: {result.content}"
  2790. agent = AssistantAgent(
  2791. name="error_recovery_agent",
  2792. model_client=model_client,
  2793. tools=[failing_tool],
  2794. tool_call_summary_formatter=error_aware_formatter,
  2795. reflect_on_tool_use=False,
  2796. )
  2797. result = await agent.run(task="Test error recovery")
  2798. # Should handle mixed success/failure gracefully
  2799. assert isinstance(result.messages[-1], ToolCallSummaryMessage)
  2800. content = result.messages[-1].content
  2801. assert "✅ failing_tool: Success: success" in content
  2802. assert "⚠️ failing_tool failed:" in content
  2803. assert "✅ failing_tool: Success: success2" in content
  2804. @pytest.mark.asyncio
  2805. async def test_state_persistence_across_interactions(self) -> None:
  2806. """Test that agent state persists correctly across multiple interactions."""
  2807. model_client = ReplayChatCompletionClient(
  2808. [
  2809. # First interaction
  2810. CreateResult(
  2811. finish_reason="stop",
  2812. content="First response",
  2813. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  2814. cached=False,
  2815. ),
  2816. # Second interaction
  2817. CreateResult(
  2818. finish_reason="stop",
  2819. content="Second response, remembering context",
  2820. usage=RequestUsage(prompt_tokens=15, completion_tokens=8),
  2821. cached=False,
  2822. ),
  2823. ],
  2824. model_info={
  2825. "function_calling": False,
  2826. "vision": False,
  2827. "json_output": False,
  2828. "family": ModelFamily.GPT_4O,
  2829. "structured_output": False,
  2830. },
  2831. )
  2832. agent = AssistantAgent(
  2833. name="stateful_agent",
  2834. model_client=model_client,
  2835. system_message="Remember previous conversations",
  2836. )
  2837. # First interaction
  2838. result1 = await agent.run(task="First task")
  2839. final_message_1 = result1.messages[-1]
  2840. assert isinstance(final_message_1, TextMessage)
  2841. assert final_message_1.content == "First response"
  2842. # Save state
  2843. state = await agent.save_state()
  2844. assert "llm_context" in state
  2845. # Second interaction
  2846. result2 = await agent.run(task="Second task, referring to first")
  2847. # Fix line 2730 - properly access content on TextMessage
  2848. final_message_2 = result2.messages[-1]
  2849. assert isinstance(final_message_2, TextMessage)
  2850. assert final_message_2.content == "Second response, remembering context"
  2851. # Verify context contains both interactions
  2852. context_messages = await agent.model_context.get_messages()
  2853. user_messages = [
  2854. msg for msg in context_messages if hasattr(msg, "source") and getattr(msg, "source", None) == "user"
  2855. ]
  2856. assert len(user_messages) == 2
  2857. class TestAssistantAgentMessageContext:
  2858. """Test suite for message context handling in AssistantAgent.
  2859. Tests various scenarios of message handling, context updates, and state management.
  2860. """
  2861. @pytest.mark.asyncio
  2862. async def test_add_messages_to_context(self) -> None:
  2863. """Test adding different message types to context.
  2864. Verifies:
  2865. 1. Regular messages are added correctly
  2866. 2. Handoff messages with context are handled properly
  2867. 3. Message order is preserved
  2868. 4. Model messages are converted correctly
  2869. """
  2870. # Setup test context
  2871. model_context = BufferedChatCompletionContext(buffer_size=10)
  2872. # Create test messages
  2873. regular_msg = TextMessage(content="Regular message", source="user")
  2874. handoff_msg = HandoffMessage(content="Handoff message", source="agent1", target="agent2")
  2875. # Add messages to context
  2876. await AssistantAgent._add_messages_to_context(model_context=model_context, messages=[regular_msg, handoff_msg]) # type: ignore[reportPrivateUsage]
  2877. # Verify context contents
  2878. context_messages = await model_context.get_messages()
  2879. # Should have: regular + handoff = 2 messages (now that handoff doesn't have context)
  2880. assert len(context_messages) == 2
  2881. # Verify message order and content - only the added messages should be present
  2882. assert isinstance(context_messages[0], UserMessage)
  2883. assert context_messages[0].content == "Regular message"
  2884. assert isinstance(context_messages[1], UserMessage)
  2885. assert context_messages[1].content == "Handoff message"
  2886. # No more assertions needed for context_messages since we already verified both
  2887. @pytest.mark.asyncio
  2888. async def test_complex_model_context(self) -> None:
  2889. """Test complex model context management scenarios.
  2890. Verifies:
  2891. 1. Large context handling
  2892. 2. Mixed message type handling
  2893. 3. Context size limits
  2894. 4. Message filtering
  2895. """
  2896. # Setup test context with limited size
  2897. model_context = BufferedChatCompletionContext(buffer_size=5)
  2898. # Create a mix of message types
  2899. messages: List[BaseChatMessage] = [
  2900. TextMessage(content="First message", source="user"),
  2901. StructuredMessage[StructuredOutput](
  2902. content=StructuredOutput(content="Structured data", confidence=0.9), source="agent"
  2903. ),
  2904. ToolCallSummaryMessage(content="Tool result", source="agent", tool_calls=[], results=[]),
  2905. HandoffMessage(content="Handoff", source="agent1", target="agent2"),
  2906. ]
  2907. # Add messages to context
  2908. await AssistantAgent._add_messages_to_context(model_context=model_context, messages=messages) # type: ignore[reportPrivateUsage]
  2909. # Verify context management
  2910. context_messages = await model_context.get_messages()
  2911. # Should respect buffer size limit
  2912. assert len(context_messages) <= 5
  2913. # Verify message conversion
  2914. for msg in context_messages:
  2915. assert isinstance(msg, (SystemMessage, UserMessage, AssistantMessage))
  2916. class TestAnthropicIntegration:
  2917. """Test suite for Anthropic model API integration."""
  2918. def _get_anthropic_client(self) -> AnthropicChatCompletionClient:
  2919. """Create an Anthropic client for testing."""
  2920. api_key = os.getenv("ANTHROPIC_API_KEY")
  2921. if not api_key:
  2922. pytest.skip("ANTHROPIC_API_KEY not found in environment variables")
  2923. return AnthropicChatCompletionClient(
  2924. model="claude-3-haiku-20240307", # Use haiku for faster/cheaper testing
  2925. api_key=api_key,
  2926. temperature=0.0,
  2927. )
  2928. @pytest.mark.asyncio
  2929. async def test_anthropic_tool_call_loop_max_iterations_10(self) -> None:
  2930. """Test Anthropic integration with tool call loop and max_tool_iterations=10."""
  2931. api_key = os.getenv("ANTHROPIC_API_KEY")
  2932. if not api_key:
  2933. pytest.skip("ANTHROPIC_API_KEY not found in environment variables")
  2934. client = self._get_anthropic_client()
  2935. agent = AssistantAgent(
  2936. name="anthropic_test_agent",
  2937. model_client=client,
  2938. tools=[mock_tool_function],
  2939. max_tool_iterations=10,
  2940. )
  2941. # Test with a task that might require tool calls
  2942. result = await agent.run(
  2943. task="Use the mock_tool_function to process the text 'hello world'. Then provide a summary."
  2944. )
  2945. # Verify that we got a result
  2946. assert result is not None
  2947. assert isinstance(result, TaskResult)
  2948. assert len(result.messages) > 0
  2949. # Check that the last message is a non-tool call.
  2950. assert isinstance(result.messages[-1], TextMessage)
  2951. # Check that a tool call was made
  2952. tool_calls = [msg for msg in result.messages if isinstance(msg, ToolCallRequestEvent)]
  2953. assert len(tool_calls) > 0
  2954. # Check that usage was tracked
  2955. usage = client.total_usage()
  2956. assert usage.prompt_tokens > 0
  2957. assert usage.completion_tokens > 0
  2958. @pytest.mark.asyncio
  2959. async def test_anthropic_tool_call_loop_max_iterations_1_with_reflection(self) -> None:
  2960. """Test Anthropic integration with max_tool_iterations=1 and reflect_on_tool_use=True."""
  2961. api_key = os.getenv("ANTHROPIC_API_KEY")
  2962. if not api_key:
  2963. pytest.skip("ANTHROPIC_API_KEY not found in environment variables")
  2964. client = self._get_anthropic_client()
  2965. agent = AssistantAgent(
  2966. name="anthropic_reflection_agent",
  2967. model_client=client,
  2968. tools=[mock_tool_function],
  2969. max_tool_iterations=1,
  2970. reflect_on_tool_use=True,
  2971. )
  2972. # Test with a task that might require tool calls but should be limited to 1 iteration
  2973. result = await agent.run(
  2974. task="Use the mock_tool_function to process the text 'test input' and then explain what happened."
  2975. )
  2976. # Verify that we got a result
  2977. assert result is not None
  2978. assert isinstance(result, TaskResult)
  2979. assert len(result.messages) > 0
  2980. # Check that the last message is a reflection
  2981. assert isinstance(result.messages[-1], TextMessage)
  2982. # Check that a tool call was made
  2983. tool_calls = [msg for msg in result.messages if isinstance(msg, ToolCallRequestEvent)]
  2984. assert len(tool_calls) > 0
  2985. # Check that usage was tracked
  2986. usage = client.total_usage()
  2987. assert usage.prompt_tokens > 0
  2988. assert usage.completion_tokens > 0
  2989. @pytest.mark.asyncio
  2990. async def test_anthropic_basic_text_response(self) -> None:
  2991. """Test basic Anthropic integration without tools."""
  2992. api_key = os.getenv("ANTHROPIC_API_KEY")
  2993. if not api_key:
  2994. pytest.skip("ANTHROPIC_API_KEY not found in environment variables")
  2995. client = self._get_anthropic_client()
  2996. agent = AssistantAgent(
  2997. name="anthropic_basic_agent",
  2998. model_client=client,
  2999. )
  3000. # Test with a simple task that doesn't require tools
  3001. result = await agent.run(task="What is 2 + 2? Just answer with the number.")
  3002. # Verify that we got a result
  3003. assert result is not None
  3004. assert isinstance(result, TaskResult)
  3005. # Check that we got a text message with content
  3006. assert isinstance(result.messages[-1], TextMessage)
  3007. assert "4" in result.messages[-1].content
  3008. # Check that usage was tracked
  3009. usage = client.total_usage()
  3010. assert usage.prompt_tokens > 0
  3011. assert usage.completion_tokens > 0