Skip to content

Commit

Permalink
Python: Allow plugins via agent constructors. Update samples. (#10707)
Browse files Browse the repository at this point in the history
### Motivation and Context

In Semantic Kernel, our goal is to simplify working with agents. When an
agent is created without a specified kernel, one is automatically
created on the user’s behalf. This enables developers to pass in a list
of plugins that are then added to the underlying kernel. If both the
agent constructor and the provided kernel include plugins, those
specified via the agent constructor take precedence—since plugins are
stored in a dictionary, any duplicate names result in the agent’s plugin
overwriting the existing one.

For the `ChatCompletionAgent`, usage is further simplified by allowing a
`ChatCompletionClientBase` service to be passed via the constructor. If
a kernel is supplied and already contains a chat completion service, the
new service is added to the kernel. Moreover, if no execution settings
are provided via KernelArguments, the first service registered on the
kernel is used.

New usage to create a ChatCompletionAgent

```python
agent = ChatCompletionAgent(
    service=AzureChatCompletion(),
    name="<name>",
    instructions="<instructions>",
)
```

New usage to create a ChatCompletionAgent with plugins:

```python
agent = ChatCompletionAgent(
    service=AzureChatCompletion(),
    name="<name>",
    instructions="<instructions>",
    plugins=[SamplePlugin()],
)
```

Previously, the `service_id` constructor argument was retained after
introducing KernelArguments. However, with our transition to using the
kernel’s AI service selector, the `service_id` parameter has become
redundant. As we move from the experimental phase toward a release
candidate, removing `service_id` is a necessary, albeit breaking,
change.

The `getting_started_with_agents/chat_completion` samples now begin by
demonstrating the simplest way to configure the agent. The subsequent
step illustrates the original method for setting up the chat completion
service on the kernel. Similarly, for plugins, the initial approach is
the easiest to follow, while the following step shows the traditional
method of managing the kernel.

Updated documentation will follow, once these changes are released in a
new package.

!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

### Description

Simplifying interactions with agents.
- Updated the `ChatCompletionAgent`, `AzureAssistantAgent`, and
`OpenAIAssistantAgent` classes with the `release_candidate` decorator in
place of the `experimental` decorator.
- Add a chat completion agent structured outputs sample
- Closes #10604

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [X] The code builds clean without any errors or warnings
- [X] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [X] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄
  • Loading branch information
moonbox3 authored Feb 28, 2025
1 parent 5e8fc1e commit fd27470
Showing 37 changed files with 579 additions and 216 deletions.
Original file line number Diff line number Diff line change
@@ -105,7 +105,6 @@ async def main():

# Create the agent
agent = ChatCompletionAgent(
service_id=service_id,
kernel=kernel,
name=HOST_NAME,
instructions=HOST_INSTRUCTIONS,
Original file line number Diff line number Diff line change
@@ -52,7 +52,6 @@ def _create_kernel_with_chat_completion(service_id: str) -> Kernel:

async def main():
agent_reviewer = ChatCompletionAgent(
service_id="artdirector",
kernel=_create_kernel_with_chat_completion("artdirector"),
name=REVIEWER_NAME,
instructions=REVIEWER_INSTRUCTIONS,
Original file line number Diff line number Diff line change
@@ -81,7 +81,6 @@ async def main():
# Configure the function choice behavior to auto invoke kernel functions
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
agent_reviewer = ChatCompletionAgent(
service_id="artdirector",
kernel=kernel,
name=REVIEWER_NAME,
instructions=REVIEWER_INSTRUCTIONS,
Original file line number Diff line number Diff line change
@@ -62,7 +62,6 @@ async def main():

service_id = "summary"
summary_agent = ChatCompletionAgent(
service_id=service_id,
kernel=_create_kernel_with_chat_completion(service_id=service_id),
instructions="Summarize the entire conversation for the user in natural language.",
name="SummaryAgent",
Original file line number Diff line number Diff line change
@@ -47,7 +47,6 @@ async def main():

service_id = "summary"
summary_agent = ChatCompletionAgent(
service_id=service_id,
kernel=_create_kernel_with_chat_completion(service_id=service_id),
instructions="Summarize the entire conversation for the user in natural language.",
name="Summarizer",
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@ def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
async def main():
# First create the ChatCompletionAgent
chat_agent = ChatCompletionAgent(
service_id="chat",
kernel=_create_kernel_with_chat_completion("chat"),
name="chat_agent",
instructions="""
Original file line number Diff line number Diff line change
@@ -34,7 +34,6 @@ def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
async def main():
# First create a ChatCompletionAgent
agent_reviewer = ChatCompletionAgent(
service_id="artdirector",
kernel=_create_kernel_with_chat_completion("artdirector"),
name="ArtDirector",
instructions="""
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio

from samples.concepts.agents.assistant_agent.assistant_sample_utils import download_response_images
from samples.concepts.agents.openai_assistant.openai_assistant_sample_utils import download_response_images
from semantic_kernel.agents.open_ai import AzureAssistantAgent
from semantic_kernel.contents.file_reference_content import FileReferenceContent

15 changes: 9 additions & 6 deletions python/samples/getting_started_with_agents/README.md
Original file line number Diff line number Diff line change
@@ -21,12 +21,15 @@ The getting started with agents examples include:

Example|Description
---|---
[step1_chat_completion_agent](../getting_started_with_agents/chat_completion/step1_chat_completion_agent.py)|How to create and use an agent.
[step2_chat_completion_agent_plugin](../getting_started_with_agents/chat_completion/step2_chat_completion_agent_plugin.py)|How to create and use an agent with plugins.
[step3_chat_completion_agent_group_chat](../getting_started_with_agents/chat_completion/step3_chat_completion_agent_group_chat.py)|How to create a conversation between agents.
[step4_kernel_function_strategies](../getting_started_with_agents/chat_completion/step4_kernel_function_strategies.py)|How to utilize a `KernelFunction` as a chat strategy.
[step5_chat_completion_agent_json_result](../getting_started_with_agents/chat_completion/step5_chat_completion_agent_json_result.py)|How to have an agent produce JSON.
[step6_chat_completion_agent_logging](../getting_started_with_agents/chat_completion/step6_chat_completion_agent_logging.py)|How to enable logging for agents.
[step1_chat_completion_agent_simple](../getting_started_with_agents/chat_completion/step1_chat_completion_agent_simple.py)|How to create and use a simple chat completion agent.
[step2_chat_completion_agent_with_kernel](../getting_started_with_agents/chat_completion/step2_chat_completion_agent_with_kernel.py)|How to create and use a a chat completion agent with the AI service created on the kernel.
[step3_chat_completion_agent_plugin_simple](../getting_started_with_agents/chat_completion/step3_chat_completion_agent_plugin_simple.py)|How to create a simple chat completion agent and specify plugins via the constructor with a kernel.
[step4_chat_completion_agent_plugin_with_kernel](../getting_started_with_agents/chat_completion/step4_chat_completion_agent_plugin_with_kernel.py)|How to create and use a chat completion agent by registering plugins on the kernel.
[step5_chat_completion_agent_group_chat](../getting_started_with_agents/chat_completion/step5_chat_completion_agent_group_chat.py)|How to create a conversation between agents.
[step6_kernel_function_strategies](../getting_started_with_agents/chat_completion/step6_kernel_function_strategies.py)|How to utilize a `KernelFunction` as a chat strategy.
[step7_chat_completion_agent_json_result](../getting_started_with_agents/chat_completion/step7_chat_completion_agent_json_result.py)|How to have an agent produce JSON.
[step8_chat_completion_agent_logging](../getting_started_with_agents/chat_completion/step8_chat_completion_agent_logging.py)|How to enable logging for agents.
[step9_chat_completion_agent_structured_outputs](../getting_started_with_agents/chat_completion/step9_chat_completion_agent_structured_outputs.py)|How to use have a chat completion agent use structured outputs

## OpenAI Assistant Agent

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio

from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistory

"""
The following sample demonstrates how to create a chat completion agent that
answers user questions using the Azure Chat Completion service. The Chat Completion
Service is passed directly via the ChatCompletionAgent constructor. This sample
demonstrates the basic steps to create an agent and simulate a conversation
with the agent.
The interaction with the agent is via the `get_response` method, which sends a
user input to the agent and receives a response from the agent. The conversation
history needs to be maintained by the caller in the chat history object.
"""

# Simulate a conversation with the agent
USER_INPUTS = [
"Hello, I am John Doe.",
"What is your name?",
"What is my name?",
]


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

# 2. Create a chat history to hold the conversation
chat_history = ChatHistory()

for user_input in USER_INPUTS:
# 3. Add the user input to the chat history
chat_history.add_user_message(user_input)
print(f"# User: {user_input}")
# 4. Invoke the agent for a response
response = await agent.get_response(chat_history)
print(f"# {response.name}: {response}")
# 5. Add the agent response to the chat history
chat_history.add_message(response)

"""
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?
"""


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -9,9 +9,12 @@

"""
The following sample demonstrates how to create a chat completion agent that
answers user questions using the Azure Chat Completion service. This sample
demonstrates the basic steps to create an agent and simulate a conversation
with the agent.
answers user questions using the Azure Chat Completion service. The Chat Completion
Service is first added to the kernel, and the kernel is passed in to the
ChatCompletionAgent constructor. This sample demonstrates the basic steps to
create an agent and simulate a conversation with the agent.
Note: if both a service and a kernel are provided, the service will be used.
The interaction with the agent is via the `get_response` method, which sends a
user input to the agent and receives a response from the agent. The conversation
@@ -28,13 +31,11 @@

async def main():
# 1. Create the instance of the Kernel to register an AI service
service_id = "agent"
kernel = Kernel()
kernel.add_service(AzureChatCompletion(service_id=service_id))
kernel.add_service(AzureChatCompletion())

# 2. Create the agent
agent = ChatCompletionAgent(
service_id=service_id,
kernel=kernel,
name="Assistant",
instructions="Answer the user's questions.",
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
from typing import Annotated

from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistory
from semantic_kernel.functions import kernel_function

"""
The following sample demonstrates how to create a chat completion agent that
answers questions about a sample menu using a Semantic Kernel Plugin. The Chat
Completion Service is passed directly via the ChatCompletionAgent constructor.
Additionally, the plugin is supplied via the constructor.
"""


# Define a sample plugin for the sample
class MenuPlugin:
"""A sample Menu Plugin used for the concept sample."""

@kernel_function(description="Provides a list of specials from the menu.")
def get_specials(self) -> Annotated[str, "Returns the specials from the menu."]:
return """
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
"""

@kernel_function(description="Provides the price of the requested menu item.")
def get_item_price(
self, menu_item: Annotated[str, "The name of the menu item."]
) -> Annotated[str, "Returns the price of the menu item."]:
return "$9.99"


# Simulate a conversation with the agent
USER_INPUTS = [
"Hello",
"What is the special soup?",
"What does that cost?",
"Thank you",
]


async def main():
# 1. Create the agent
agent = ChatCompletionAgent(
service=AzureChatCompletion(),
name="Host",
instructions="Answer questions about the menu.",
plugins=[MenuPlugin()],
)

# 2. Create a chat history to hold the conversation
chat_history = ChatHistory()

for user_input in USER_INPUTS:
# 3. Add the user input to the chat history
chat_history.add_user_message(user_input)
print(f"# User: {user_input}")
# 4. Invoke the agent for a response
response = await agent.get_response(chat_history)
print(f"# {response.name}: {response.content} ")

"""
Sample output:
# User: Hello
# Host: Hello! How can I assist you today?
# User: What is the special soup?
# Host: The special soup is Clam Chowder.
# User: What does that cost?
# Host: The special soup, Clam Chowder, costs $9.99.
# User: Thank you
# Host: You're welcome! If you have any more questions, feel free to ask. Enjoy your day!
"""


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -12,7 +12,12 @@

"""
The following sample demonstrates how to create a chat completion agent that
answers questions about a sample menu using a Semantic Kernel Plugin.
answers questions about a sample menu using a Semantic Kernel Plugin. The Chat
Completion Service is first added to the kernel, and the kernel is passed in to the
ChatCompletionAgent constructor. Additionally, the plugin is supplied via the kernel.
To enable auto-function calling, the prompt execution settings are retrieved from the kernel
using the specified `service_id`. The function choice behavior is set to `Auto` to allow the
agent to automatically execute the plugin's functions when needed.
"""


@@ -58,7 +63,6 @@ async def main():

# 3. Create the agent
agent = ChatCompletionAgent(
service_id=service_id,
kernel=kernel,
name="Host",
instructions="Answer questions about the menu.",
Original file line number Diff line number Diff line change
@@ -52,15 +52,13 @@ async def should_agent_terminate(self, agent, history):
async def main():
# 1. Create the reviewer agent based on the chat completion service
agent_reviewer = ChatCompletionAgent(
service_id="artdirector",
kernel=_create_kernel_with_chat_completion("artdirector"),
name=REVIEWER_NAME,
instructions=REVIEWER_INSTRUCTIONS,
)

# 2. Create the copywriter agent based on the chat completion service
agent_writer = ChatCompletionAgent(
service_id="copywriter",
kernel=_create_kernel_with_chat_completion("copywriter"),
name=COPYWRITER_NAME,
instructions=COPYWRITER_INSTRUCTIONS,
Original file line number Diff line number Diff line change
@@ -47,15 +47,13 @@ def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
async def main():
# 1. Create the reviewer agent based on the chat completion service
agent_reviewer = ChatCompletionAgent(
service_id="artdirector",
kernel=_create_kernel_with_chat_completion("artdirector"),
name=REVIEWER_NAME,
instructions=REVIEWER_INSTRUCTIONS,
)

# 2. Create the copywriter agent based on the chat completion service
agent_writer = ChatCompletionAgent(
service_id="copywriter",
kernel=_create_kernel_with_chat_completion("copywriter"),
name=COPYWRITER_NAME,
instructions=COPYWRITER_INSTRUCTIONS,
Original file line number Diff line number Diff line change
@@ -69,7 +69,6 @@ async def main():

# 3. Create the agent
agent = ChatCompletionAgent(
service_id=service_id,
kernel=kernel,
name="Tutor",
instructions=INSTRUCTION,
Original file line number Diff line number Diff line change
@@ -59,15 +59,13 @@ def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
async def main():
# 1. Create the reviewer agent based on the chat completion service
agent_reviewer = ChatCompletionAgent(
service_id="artdirector",
kernel=_create_kernel_with_chat_completion("artdirector"),
name=REVIEWER_NAME,
instructions=REVIEWER_INSTRUCTIONS,
)

# 2. Create the copywriter agent based on the chat completion service
agent_writer = ChatCompletionAgent(
service_id="copywriter",
kernel=_create_kernel_with_chat_completion("copywriter"),
name=COPYWRITER_NAME,
instructions=COPYWRITER_INSTRUCTIONS,
Loading

0 comments on commit fd27470

Please sign in to comment.