Skip to content

Commit 6985ad0

Browse files
add kernel function to agent
1 parent bfc8b40 commit 6985ad0

File tree

6 files changed

+202
-12
lines changed

6 files changed

+202
-12
lines changed

python/samples/concepts/agents/chat_completion_agent/chat_completion_function_termination.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_item_price(
4646
return "$9.99"
4747

4848

49-
def _create_kernel_with_chat_completionand_filter() -> Kernel:
49+
def _create_kernel_with_chat_completion_and_filter() -> Kernel:
5050
"""A helper function to create a kernel with a chat completion service and a filter."""
5151
kernel = Kernel()
5252
kernel.add_service(AzureChatCompletion())
@@ -72,7 +72,7 @@ async def main():
7272
# 1. Create the agent with a kernel instance that contains
7373
# the auto function invocation filter and the AI service
7474
agent = ChatCompletionAgent(
75-
kernel=_create_kernel_with_chat_completionand_filter(),
75+
kernel=_create_kernel_with_chat_completion_and_filter(),
7676
name="Host",
7777
instructions="Answer questions about the menu.",
7878
)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
5+
from samples.concepts.setup.chat_completion_services import Services, get_chat_completion_service_and_request_settings
6+
from semantic_kernel import Kernel
7+
from semantic_kernel.agents.chat_completion.chat_completion_agent import ChatCompletionAgent
8+
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
9+
from semantic_kernel.contents import ChatHistory
10+
from semantic_kernel.filters.functions.function_invocation_context import FunctionInvocationContext
11+
12+
"""
13+
This sample demonstrates how to build a conversational chatbot
14+
using Semantic Kernel, featuring auto function calling,
15+
using agents as functions, this includes letting a chat interaction
16+
call an agent, and giving one agent another agent to do things.
17+
"""
18+
19+
# System message defining the behavior and persona of the chat bot.
20+
system_message = """
21+
You are a chat bot. Your name is Mosscap and
22+
you have one goal: figure out what people need.
23+
Your full name, should you need to know it, is
24+
Splendid Speckled Mosscap. You communicate
25+
effectively, but you tend to answer with long
26+
flowery prose. You are also a math wizard,
27+
especially for adding and subtracting.
28+
You also excel at joke telling, where your tone is often sarcastic.
29+
Once you have the answer I am looking for,
30+
you will return a full answer to me as soon as possible.
31+
"""
32+
33+
34+
# Define the auto function invocation filter that will be used by the kernel
35+
async def function_invocation_filter(context: FunctionInvocationContext, next):
36+
"""A filter that will be called for each function call in the response."""
37+
if "task" not in context.arguments:
38+
await next(context)
39+
return
40+
print(f" Agent {context.function.name} called with task: {context.arguments['task']}")
41+
await next(context)
42+
print(f" Response from agent {context.function.name}: {context.result.value}")
43+
44+
45+
# Create and configure the kernel.
46+
kernel = Kernel()
47+
kernel.add_filter("function_invocation", function_invocation_filter)
48+
49+
# You can select from the following chat completion services that support function calling:
50+
# - Services.OPENAI
51+
# - Services.AZURE_OPENAI
52+
# - Services.AZURE_AI_INFERENCE
53+
# - Services.ANTHROPIC
54+
# - Services.BEDROCK
55+
# - Services.GOOGLE_AI
56+
# - Services.MISTRAL_AI
57+
# - Services.OLLAMA
58+
# - Services.ONNX
59+
# - Services.VERTEX_AI
60+
# - Services.DEEPSEEK
61+
# Please make sure you have configured your environment correctly for the selected chat completion service.
62+
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.OPENAI)
63+
64+
# Configure the function choice behavior. Here, we set it to Auto, where auto_invoke=True by default.
65+
# With `auto_invoke=True`, the model will automatically choose and call functions as needed.
66+
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(filters={"excluded_plugins": ["ChatBot"]})
67+
68+
# Create a chat history to store the system message, initial messages, and the conversation.
69+
history = ChatHistory()
70+
history.add_system_message(system_message)
71+
72+
REVIEWER_NAME = "ArtDirector"
73+
REVIEWER_INSTRUCTIONS = """
74+
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
75+
You ask one of the copy-writing agents for a piece of copy, which you then review.
76+
The goal is to determine if the given copy is acceptable to print.
77+
If so, respond with the created copy.
78+
If not, do not return, but ask a copywriter again for copy, providing the returned copy and feedback.
79+
"""
80+
81+
COPYWRITER_NAME = "CopyWriter"
82+
COPYWRITER_INSTRUCTIONS = """
83+
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
84+
The goal is to refine and decide on the single best copy as an expert in the field.
85+
Only provide a single proposal per response.
86+
You're laser focused on the goal at hand.
87+
Don't waste time with chit chat.
88+
Consider suggestions when refining an idea.
89+
"""
90+
91+
writer_agent = ChatCompletionAgent(
92+
service=chat_completion_service,
93+
name=COPYWRITER_NAME,
94+
description="This agent can write copy about any topic.",
95+
instructions=COPYWRITER_INSTRUCTIONS,
96+
)
97+
reviewer_agent = ChatCompletionAgent(
98+
service=chat_completion_service,
99+
name=REVIEWER_NAME,
100+
description="This agent can review copy and provide feedback, he does has copy writers available.",
101+
instructions=REVIEWER_INSTRUCTIONS,
102+
plugins=[writer_agent],
103+
)
104+
105+
reviewer_agent.kernel.add_filter("function_invocation", function_invocation_filter)
106+
107+
kernel.add_plugins([reviewer_agent])
108+
109+
110+
async def chat() -> bool:
111+
"""
112+
Continuously prompt the user for input and show the assistant's response.
113+
Type 'exit' to exit.
114+
"""
115+
try:
116+
user_input = input("User:> ")
117+
except (KeyboardInterrupt, EOFError):
118+
print("\n\nExiting chat...")
119+
return False
120+
121+
if user_input.lower().strip() == "exit":
122+
print("\n\nExiting chat...")
123+
return False
124+
history.add_user_message(user_input)
125+
# Handle non-streaming responses
126+
result = await chat_completion_service.get_chat_message_content(
127+
chat_history=history, settings=request_settings, kernel=kernel
128+
)
129+
130+
# Update the chat history with the user's input and the assistant's response
131+
if result:
132+
print(f"Mosscap:> {result}")
133+
history.add_message(result)
134+
135+
return True
136+
137+
138+
"""
139+
Sample output:
140+
Welcome to the chat bot!
141+
Type 'exit' to exit.
142+
Try to get some copy written by the copy writer, make sure to ask it is reviewed.).
143+
User:> write a slogan for electric vehicles
144+
Mosscap:> Ah, the realm of electric vehicles, where the whispers of sustainability dance with the vibrant hum of
145+
innovation! How about this for a slogan:
146+
147+
"Drive the Future: Silent, Smart, and Sustainable!"
148+
149+
This phrase encapsulates the essence of electric vehicles, inviting all to embrace a journey that is not only
150+
forward-thinking but also harmoniously aligned with the gentle rhythms of our planet. Would you like to explore
151+
more options or perhaps delve into another aspect of this electrifying topic?
152+
User:> ask the art director for it
153+
Agent ArtDirector called with task: Create a slogan for electric vehicles that captures their innovative and
154+
sustainable essence.
155+
Agent CopyWriter called with task: Create a slogan for electric vehicles that captures their innovative and
156+
sustainable essence.
157+
Response from agent CopyWriter: "Drive the Future: Silent, Smart, Sustainable."
158+
Response from agent ArtDirector: "Drive the Future: Silent, Smart, Sustainable."
159+
Mosscap:> The Art Director has conjured forth a splendid slogan for electric vehicles:
160+
161+
"Drive the Future: Silent, Smart, Sustainable."
162+
163+
This phrase beautifully encapsulates the innovative spirit and eco-friendly nature of electric vehicles.
164+
If you seek further refinement or wish to explore additional ideas, simply let me know, and I shall be at your service!
165+
"""
166+
167+
168+
async def main() -> None:
169+
print(
170+
"Welcome to the chat bot!\n"
171+
" Type 'exit' to exit.\n"
172+
" Try to get some copy written by the copy writer, make sure to ask it is reviewed.)."
173+
)
174+
chatting = True
175+
while chatting:
176+
chatting = await chat()
177+
178+
179+
if __name__ == "__main__":
180+
asyncio.run(main())

python/samples/concepts/auto_function_calling/chat_completion_with_manual_function_calling.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

33
import asyncio
4-
from typing import TYPE_CHECKING
54

65
from samples.concepts.setup.chat_completion_services import Services, get_chat_completion_service_and_request_settings
76
from semantic_kernel import Kernel
@@ -13,9 +12,6 @@
1312
from semantic_kernel.core_plugins.time_plugin import TimePlugin
1413
from semantic_kernel.functions import KernelArguments
1514

16-
if TYPE_CHECKING:
17-
pass
18-
1915
#####################################################################
2016
# This sample demonstrates how to build a conversational chatbot #
2117
# using Semantic Kernel, featuring manual function calling, #

python/semantic_kernel/agents/agent.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
import uuid
55
from abc import ABC, abstractmethod
66
from collections.abc import AsyncIterable, Iterable
7-
from typing import Any, ClassVar
7+
from typing import Annotated, Any, ClassVar
88

99
from pydantic import Field, model_validator
1010

1111
from semantic_kernel.agents.channels.agent_channel import AgentChannel
12+
from semantic_kernel.contents.chat_history import ChatHistory
1213
from semantic_kernel.contents.chat_message_content import ChatMessageContent
1314
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
1415
from semantic_kernel.functions.kernel_arguments import KernelArguments
16+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
1517
from semantic_kernel.functions.kernel_plugin import KernelPlugin
1618
from semantic_kernel.kernel import Kernel
1719
from semantic_kernel.kernel_pydantic import KernelBaseModel
@@ -56,9 +58,7 @@ class Agent(KernelBaseModel, ABC):
5658
@staticmethod
5759
def _get_plugin_name(plugin: KernelPlugin | object) -> str:
5860
"""Helper method to get the plugin name."""
59-
if isinstance(plugin, KernelPlugin):
60-
return plugin.name
61-
return plugin.__class__.__name__
61+
return getattr(plugin, "name", plugin.__class__.__name__)
6262

6363
@model_validator(mode="before")
6464
@classmethod
@@ -74,6 +74,19 @@ def _configure_plugins(cls, data: Any) -> Any:
7474
data["kernel"] = kernel
7575
return data
7676

77+
def model_post_init(self, __context: Any) -> None:
78+
"""Post initialization."""
79+
80+
@kernel_function(name=self.name, description=self.description)
81+
async def _invoke_agent_as_function_inner(
82+
task: Annotated[str, "The task to perform."],
83+
) -> Annotated[str, "The response from the agent."]:
84+
history = ChatHistory()
85+
history.add_user_message(task)
86+
return (await self.get_response(history=history)).content
87+
88+
setattr(self, "_as_function", _invoke_agent_as_function_inner)
89+
7790
@abstractmethod
7891
async def get_response(self, *args, **kwargs) -> ChatMessageContent:
7992
"""Get a response from the agent.

python/semantic_kernel/functions/kernel_function_extension.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def add_plugin(
8585
self.plugins[plugin.name] = plugin
8686
return self.plugins[plugin.name]
8787
if not plugin_name:
88-
raise ValueError("plugin_name must be provided if a plugin is not supplied.")
88+
plugin_name = plugin.name if hasattr(plugin, "name") else plugin.__class__.__name__
8989
if not isinstance(plugin_name, str):
9090
raise TypeError("plugin_name must be a string.")
9191
if plugin:
@@ -103,7 +103,7 @@ def add_plugin(
103103
return self.plugins[plugin_name]
104104
raise ValueError("plugin or parent_directory must be provided.")
105105

106-
def add_plugins(self, plugins: list[KernelPlugin] | dict[str, KernelPlugin | object]) -> None:
106+
def add_plugins(self, plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object]) -> None:
107107
"""Adds a list of plugins to the kernel's collection of plugins.
108108
109109
Args:

python/semantic_kernel/functions/kernel_plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ def from_object(
238238
candidates = plugin_instance.items()
239239
else:
240240
candidates = inspect.getmembers(plugin_instance, inspect.ismethod)
241+
candidates.extend(inspect.getmembers(plugin_instance, inspect.isfunction)) # type: ignore
241242
# Read every method from the plugin instance
242243
functions = [
243244
KernelFunctionFromMethod(method=candidate, plugin_name=plugin_name)

0 commit comments

Comments
 (0)