Skip to content

Python: Support agent thread #11119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions python/samples/concepts/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,83 @@ more information.

Concept samples can be run in an IDE or via the command line. After setting up the required api key or token authentication
for your AI connector, the samples run without any extra command line arguments.

## Managing Conversation Threads with AgentThread

This section explains how to manage conversation context using the `AgentThread` base class. Each agent has its own thread implementation that preserves the context of a conversation. If you invoke an agent without specifying a thread, a new one is created automatically and returned as part of the `AgentItemResponse` object—which includes both the message (of type `ChatMessageContent`) and the thread (`AgentThread`). You also have the option to create a custom thread for a specific agent by providing a unique `thread_id`.

## Overview

**Automatic Thread Creation:**
When an agent is invoked without a provided thread, it creates a new thread to manage the conversation context automatically.

**Manual Thread Management:**
You can explicitly create a specific implementation for the desired `Agent` that derives from the base class `AgentThread`. You have the option to assign a `thread_id` to manage the conversation session. This is particularly useful in complex scenarios or multi-user environments.

## Code Example

Below is a sample code snippet demonstrating thread management:

```python
USER_INPUTS = [
"Why is the sky blue?",
]

# 1. Create the agent by specifying the service
agent = ChatCompletionAgent(
service=AzureChatCompletion(),
name="Assistant",
instructions="Answer the user's questions.",
)

# 2. Create a thread to hold the conversation
# If no thread is provided, a new thread will be
# created and returned with the initial response
thread: ChatCompletionAgentThread = None

for user_input in USER_INPUTS:
print(f"# User: {user_input}")
# 3. Invoke the agent for a response
response = await agent.get_response(
message=user_input,
thread=thread,
)
print(f"# {response.message.name}: {response.message}")
thread = response.thread

# 4. Cleanup: Clear the thread
await thread.end() if thread else None

"""
Sample output:
# User: Hello, I am John Doe.
# Assistant: Hello, John Doe! How can I assist you today?
# User: What is your name?
# Assistant: I don't have a personal name like a human does, but you can call me Assistant.?
# User: What is my name?
# Assistant: You mentioned that your name is John Doe. How can I assist you further, John?
"""
```

## Detailed Explanation

**Thread Initialization:**
The thread is initially set to `None`. If no thread is provided, the agent creates a new one and includes it in the response.

**Processing User Inputs:**
A list of `user_inputs` simulates a conversation. For each input:
- The code prints the user's message.
- The agent is invoked using the `get_response` method, which returns the response asynchronously.

**Handling Responses:**
- The thread is updated with each response to maintain the conversation context.

**Cleanup:**
The code safely ends the thread if it exists.

## Conclusion

By leveraging the `AgentThread`, you ensure that each conversation maintains its context seamlessly -- whether the thread is automatically created or manually managed with a custom `thread_id`. This approach is crucial for developing agents that deliver coherent and context-aware interactions.



Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from autogen import ConversableAgent
from autogen.coding import LocalCommandLineCodeExecutor

from semantic_kernel.agents.autogen.autogen_conversable_agent import AutoGenConversableAgent
from semantic_kernel.agents.autogen import AutoGenConversableAgent, AutoGenConversableAgentThread

"""
The following sample demonstrates how to use the AutoGenConversableAgent to create a reply from an agent
Expand All @@ -17,6 +17,8 @@


async def main():
thread: AutoGenConversableAgentThread = None

# Create a temporary directory to store the code files.
import os

Expand All @@ -42,19 +44,27 @@ async def main():
message_with_code_block = """This is a message with code block.
The code block is below:
```python
import numpy as np
import matplotlib.pyplot as plt
x = np.random.randint(0, 100, 100)
y = np.random.randint(0, 100, 100)
plt.scatter(x, y)
plt.savefig('scatter.png')
print('Scatter plot saved to scatter.png')
def generate_fibonacci(max_val):
a, b = 0, 1
fibonacci_numbers = []
while a <= max_val:
fibonacci_numbers.append(a)
a, b = b, a + b
return fibonacci_numbers

if __name__ == "__main__":
fib_numbers = generate_fibonacci(101)
print(fib_numbers)
```
This is the end of the message.
"""

async for content in autogen_agent.invoke(message=message_with_code_block):
print(f"# {content.role} - {content.name or '*'}: '{content.content}'")
async for response in autogen_agent.invoke(message=message_with_code_block, thread=thread):
print(f"# {response.message.role} - {response.message.name or '*'}: '{response.message}'")
thread = response.thread

# Cleanup: Delete the thread and agent
await thread.delete() if thread else None


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from autogen import ConversableAgent, register_function

from semantic_kernel.agents.autogen.autogen_conversable_agent import AutoGenConversableAgent
from semantic_kernel.agents.autogen import AutoGenConversableAgent, AutoGenConversableAgentThread
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.function_result_content import FunctionResultContent

Expand Down Expand Up @@ -50,6 +50,9 @@ def calculator(a: int, b: int, operator: Annotated[Operator, "operator"]) -> int
},
)

# Create a thread for use with the agent.
thread: AutoGenConversableAgentThread = None

# Create a Semantic Kernel AutoGenConversableAgent based on the AutoGen ConversableAgent.
assistant_agent = AutoGenConversableAgent(conversable_agent=assistant)

Expand All @@ -76,19 +79,26 @@ def calculator(a: int, b: int, operator: Annotated[Operator, "operator"]) -> int
# Create a Semantic Kernel AutoGenConversableAgent based on the AutoGen ConversableAgent.
user_proxy_agent = AutoGenConversableAgent(conversable_agent=user_proxy)

async for content in user_proxy_agent.invoke(
async for response in user_proxy_agent.invoke(
thread=thread,
recipient=assistant_agent,
message="What is (44232 + 13312 / (232 - 32)) * 5?",
max_turns=10,
):
for item in content.items:
for item in response.message.items:
match item:
case FunctionResultContent(result=r):
print(f"# {content.role} - {content.name or '*'}: '{r}'")
print(f"# {response.message.role} - {response.message.name or '*'}: '{r}'")
case FunctionCallContent(function_name=fn, arguments=arguments):
print(f"# {content.role} - {content.name or '*'}: Function Name: '{fn}', Arguments: '{arguments}'")
print(
f"# {response.message.role} - {response.message.name or '*'}: Function Name: '{fn}', Arguments: '{arguments}'" # noqa: E501
)
case _:
print(f"# {content.role} - {content.name or '*'}: '{content.content}'")
print(f"# {response.message.role} - {response.message.name or '*'}: '{response.message}'")
thread = response.thread

# Cleanup: Delete the thread and agent
await thread.delete() if thread else None


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from autogen import ConversableAgent

from semantic_kernel.agents.autogen.autogen_conversable_agent import AutoGenConversableAgent
from semantic_kernel.agents.autogen import AutoGenConversableAgent, AutoGenConversableAgentThread

"""
The following sample demonstrates how to use the AutoGenConversableAgent to create a conversation between two agents
Expand All @@ -17,6 +17,8 @@


async def main():
thread: AutoGenConversableAgentThread = None

cathy = ConversableAgent(
"cathy",
system_message="Your name is Cathy and you are a part of a duo of comedians.",
Expand Down Expand Up @@ -51,10 +53,14 @@ async def main():

joe_autogen_agent = AutoGenConversableAgent(conversable_agent=joe)

async for content in cathy_autogen_agent.invoke(
recipient=joe_autogen_agent, message="Tell me a joke about the stock market.", max_turns=3
async for response in cathy_autogen_agent.invoke(
recipient=joe_autogen_agent, message="Tell me a joke about the stock market.", thread=thread, max_turns=3
):
print(f"# {content.role} - {content.name or '*'}: '{content.content}'")
print(f"# {response.message.role} - {response.message.name or '*'}: '{response.message}'")
thread = response.thread

# Cleanup: Delete the thread and agent
await thread.delete() if thread else None


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
from azure.ai.projects.models import AzureAISearchTool, ConnectionType
from azure.identity.aio import DefaultAzureCredential

from semantic_kernel.agents.azure_ai import AzureAIAgent, AzureAIAgentSettings
from semantic_kernel.agents.azure_ai import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread

logging.basicConfig(level=logging.WARNING)

"""
The following sample demonstrates how to create a simple,
Azure AI agent that uses the Azure AI Search tool and the demo
hotels-sample-index to answer questions about hotels.
This sample requires:
- A "Standard" Agent Setup (choose the Python (Azure SDK) tab):
https://learn.microsoft.com/en-us/azure/ai-services/agents/quickstart
- An Azure AI Search index named 'hotels-sample-index' created in your
Azure AI Search service. You may follow this guide to create the index:
The following sample demonstrates how to create a simple,
Azure AI agent that uses the Azure AI Search tool and the demo
hotels-sample-index to answer questions about hotels.

This sample requires:
- A "Standard" Agent Setup (choose the Python (Azure SDK) tab):
https://learn.microsoft.com/en-us/azure/ai-services/agents/quickstart
- An Azure AI Search index named 'hotels-sample-index' created in your
Azure AI Search service. You may follow this guide to create the index:
https://learn.microsoft.com/azure/search/search-get-started-portal
- You will need to make sure your Azure AI Agent project is set up with
the required Knowledge Source to be able to use the Azure AI Search tool.
Refer to the following link for information on how to do this:
Refer to the following link for information on how to do this:
https://learn.microsoft.com/en-us/azure/ai-services/agents/how-to/tools/azure-ai-search

Refer to the README for information about configuring the index to work
Expand Down Expand Up @@ -69,8 +69,10 @@ async def main() -> None:
definition=agent_definition,
)

# Create a new thread
thread = await client.agents.create_thread()
# Create a thread for the agent
# If no thread is provided, a new thread will be
# created and returned with the initial response
thread: AzureAIAgentThread = None

user_inputs = [
"Which hotels are available with full-sized kitchens in Nashville, TN?",
Expand All @@ -79,38 +81,35 @@ async def main() -> None:

try:
for user_input in user_inputs:
# Add the user input as a chat message
await agent.add_chat_message(
thread_id=thread.id,
message=user_input,
)
print(f"# User: '{user_input}'\n")
# Invoke the agent for the specified thread
async for content in agent.invoke(thread_id=thread.id):
print(f"# Agent: {content.content}\n")
async for response in agent.invoke(message=user_input, thread=thread):
print(f"# Agent: {response.message}\n")
thread = response.thread
finally:
await client.agents.delete_thread(thread.id)
# Cleanup: Delete the thread and agent
await thread.delete() if thread else None
await client.agents.delete_agent(agent.id)

"""
Sample output:

# User: 'Which hotels are available with full-sized kitchens in Nashville, TN?'

# Agent: In Nashville, TN, there are several hotels available that feature full-sized kitchens:

1. **Extended-Stay Hotel Options**:
- Many extended-stay hotels offer suites equipped with full-sized kitchens, which include cookware and
appliances. These hotels are designed for longer stays, making them a great option for those needing more space
- Many extended-stay hotels offer suites equipped with full-sized kitchens, which include cookware and
appliances. These hotels are designed for longer stays, making them a great option for those needing more space
and kitchen facilities【3:0†source】【3:1†source】.

2. **Amenities Included**:
- Most of these hotels provide additional amenities like free Wi-Fi, laundry services, fitness centers, and some
have on-site dining options【3:1†source】【3:2†source】.

3. **Location**:
- The extended-stay hotels are often located near downtown Nashville, making it convenient for guests to
explore the vibrant local music scene while enjoying the comfort of a home-like
- The extended-stay hotels are often located near downtown Nashville, making it convenient for guests to
explore the vibrant local music scene while enjoying the comfort of a home-like
environment【3:0†source】【3:4†source】.

If you are looking for specific names or more detailed options, I can further assist you with that!
Expand All @@ -120,22 +119,22 @@ async def main() -> None:
# Agent: Here are some fun hotels that offer free WiFi:

1. **Vibrant Downtown Hotel**:
- Located near the heart of downtown, this hotel offers a warm atmosphere with free WiFi and even provides a
- Located near the heart of downtown, this hotel offers a warm atmosphere with free WiFi and even provides a
delightful milk and cookies treat【7:2†source】.

2. **Extended-Stay Options**:
- These hotels often feature fun amenities such as a bowling alley, fitness center, and themed rooms. They also
- These hotels often feature fun amenities such as a bowling alley, fitness center, and themed rooms. They also
provide free WiFi and are well-situated near local attractions【7:0†source】【7:1†source】.

3. **Luxury Hotel**:
- Ranked highly by Traveler magazine, this 5-star luxury hotel boasts the biggest rooms in the city, free WiFi,
- Ranked highly by Traveler magazine, this 5-star luxury hotel boasts the biggest rooms in the city, free WiFi,
espresso in the room, and flexible check-in/check-out options【7:1†source】.

4. **Budget-Friendly Hotels**:
- Several budget hotels offer free WiFi, breakfast, and shuttle services to nearby attractions and airports
- Several budget hotels offer free WiFi, breakfast, and shuttle services to nearby attractions and airports
while still providing a fun stay【7:3†source】.

These options ensure you stay connected while enjoying your visit! If you need more specific recommendations or
These options ensure you stay connected while enjoying your visit! If you need more specific recommendations or
details, feel free to ask!
"""

Expand Down
Loading
Loading