Skip to content

Commit

Permalink
Break samples down further - streaming and non streaming auto invoke …
Browse files Browse the repository at this point in the history
…and manual invoke
  • Loading branch information
moonbox3 committed Dec 19, 2024
1 parent ac5a105 commit 8464649
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
from typing import TYPE_CHECKING

from samples.concepts.setup.chat_completion_services import Services, get_chat_completion_service_and_request_settings
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.contents import ChatHistory
from semantic_kernel.core_plugins.math_plugin import MathPlugin
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.functions import KernelArguments

if TYPE_CHECKING:
pass

#####################################################################
# This sample demonstrates how to build a conversational chatbot #
# using Semantic Kernel, featuring auto function calling, #
# non-streaming responses, and support for math and time plugins. #
# The chatbot is designed to interact with the user, call functions #
# as needed, and return responses. #
#####################################################################

# System message defining the behavior and persona of the chat bot.
system_message = """
You are a chat bot. Your name is Mosscap and
you have one goal: figure out what people need.
Your full name, should you need to know it, is
Splendid Speckled Mosscap. You communicate
effectively, but you tend to answer with long
flowery prose. You are also a math wizard,
especially for adding and subtracting.
You also excel at joke telling, where your tone is often sarcastic.
Once you have the answer I am looking for,
you will return a full answer to me as soon as possible.
"""

# Create and configure the kernel.
kernel = Kernel()

# Load some sample plugins (for demonstration of function calling).
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")

# Define a chat function (a template for how to handle user input).
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
)

# You can select from the following chat completion services that support function calling:
# - Services.OPENAI
# - Services.AZURE_OPENAI
# - Services.AZURE_AI_INFERENCE
# - Services.ANTHROPIC
# - Services.BEDROCK
# - Services.GOOGLE_AI
# - Services.MISTRAL_AI
# - Services.OLLAMA
# - Services.ONNX
# - Services.VERTEX_AI
# Please make sure you have configured your environment correctly for the selected chat completion service.
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.AZURE_OPENAI)

# Configure the function choice behavior. Here, we set it to Auto, where auto_invoke=True by default.
# With `auto_invoke=True`, the model will automatically choose and call functions as needed.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

kernel.add_service(chat_completion_service)

# Pass the request settings to the kernel arguments.
arguments = KernelArguments(settings=request_settings)

# Create a chat history to store the system message, initial messages, and the conversation.
history = ChatHistory()
history.add_system_message(system_message)
history.add_user_message("Hi there, who are you?")
history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure out what people need.")


async def chat() -> bool:
"""
Continuously prompt the user for input and show the assistant's response.
Type 'exit' to exit.
"""
try:
user_input = input("User:> ")
except (KeyboardInterrupt, EOFError):
print("\n\nExiting chat...")
return False

if user_input.lower().strip() == "exit":
print("\n\nExiting chat...")
return False

arguments["user_input"] = user_input
arguments["chat_history"] = history

# Handle non-streaming responses
result = await kernel.invoke(chat_function, arguments=arguments)

# Update the chat history with the user's input and the assistant's response
if result:
print(f"Mosscap:> {result}")
history.add_user_message(user_input)
history.add_assistant_message(str(result))

return True


async def main() -> None:
print(
"Welcome to the chat bot!\n"
" Type 'exit' to exit.\n"
" Try a math question to see function calling in action (e.g. 'what is 3+3?')."
)
chatting = True
while chatting:
chatting = await chat()


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
from typing import TYPE_CHECKING

from samples.concepts.setup.chat_completion_services import Services, get_chat_completion_service_and_request_settings
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.contents import ChatHistory
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.core_plugins.math_plugin import MathPlugin
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.functions import KernelArguments

if TYPE_CHECKING:
from semantic_kernel.functions import KernelFunction

#####################################################################
# This sample demonstrates how to build a conversational chatbot #
# using Semantic Kernel, featuring auto function calling, #
# streaming responses, and support for math and time plugins. #
# The chatbot is designed to interact with the user, call functions #
# as needed, and return responses. #
#####################################################################

# System message defining the behavior and persona of the chat bot.
system_message = """
You are a chat bot. Your name is Mosscap and
you have one goal: figure out what people need.
Your full name, should you need to know it, is
Splendid Speckled Mosscap. You communicate
effectively, but you tend to answer with long
flowery prose. You are also a math wizard,
especially for adding and subtracting.
You also excel at joke telling, where your tone is often sarcastic.
Once you have the answer I am looking for,
you will return a full answer to me as soon as possible.
"""

# Create and configure the kernel.
kernel = Kernel()

# Load some sample plugins (for demonstration of function calling).
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")

# Define a chat function (a template for how to handle user input).
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
)

# You can select from the following chat completion services that support function calling:
# - Services.OPENAI
# - Services.AZURE_OPENAI
# - Services.AZURE_AI_INFERENCE
# - Services.ANTHROPIC
# - Services.BEDROCK
# - Services.GOOGLE_AI
# - Services.MISTRAL_AI
# - Services.OLLAMA
# - Services.ONNX
# - Services.VERTEX_AI
# Please make sure you have configured your environment correctly for the selected chat completion service.
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.AZURE_OPENAI)

# Configure the function choice behavior. Here, we set it to Auto, where auto_invoke=True by default.
# With `auto_invoke=True`, the model will automatically choose and call functions as needed.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

kernel.add_service(chat_completion_service)

# Pass the request settings to the kernel arguments.
arguments = KernelArguments(settings=request_settings)

# Create a chat history to store the system message, initial messages, and the conversation.
history = ChatHistory()
history.add_system_message(system_message)
history.add_user_message("Hi there, who are you?")
history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure out what people need.")


async def handle_streaming(
kernel: Kernel,
chat_function: "KernelFunction",
arguments: KernelArguments,
) -> str | None:
"""
Handle the streaming response from the model.
This function demonstrates two possible paths:
When auto function calling is ON (auto_invoke=True):
- The model may call tools automatically and produce a continuous
stream of assistant messages. We can simply print these as they come in.
"""

response = kernel.invoke_stream(
chat_function,
return_function_results=False,
arguments=arguments,
)

print("Mosscap:> ", end="", flush=True)

# For content messages (the final assistant's response text), store them here.
streamed_response_chunks: list[StreamingChatMessageContent] = []

async for message in response:
msg = message[0]

# We only expect assistant messages here.
if not isinstance(msg, StreamingChatMessageContent) or msg.role != AuthorRole.ASSISTANT:
continue

# When auto invocation is ON, no special handling is needed. Just print out messages as they arrive.
streamed_response_chunks.append(msg)
print(str(msg), end="", flush=True)

print("\n", flush=True)

# Return the final concatenated assistant response (if any).
if streamed_response_chunks:
return "".join([str(content) for content in streamed_response_chunks])
return None


async def chat() -> bool:
"""
Continuously prompt the user for input and show the assistant's response.
Type 'exit' to exit.
"""
try:
user_input = input("User:> ")
except (KeyboardInterrupt, EOFError):
print("\n\nExiting chat...")
return False

if user_input.lower().strip() == "exit":
print("\n\nExiting chat...")
return False

arguments["user_input"] = user_input
arguments["chat_history"] = history

result = await handle_streaming(kernel, chat_function, arguments=arguments)

# Update the chat history with the user's input and the assistant's response
if result:
history.add_user_message(user_input)
history.add_assistant_message(str(result))

return True


async def main() -> None:
print(
"Welcome to the chat bot!\n"
" Type 'exit' to exit.\n"
" Try a math question to see function calling in action (e.g. 'what is 3+3?')."
)
chatting = True
while chatting:
chatting = await chat()


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@

#####################################################################
# This sample demonstrates how to build a conversational chatbot #
# using Semantic Kernel, featuring dynamic function calling, #
# using Semantic Kernel, featuring manual function calling, #
# non-streaming responses, and support for math and time plugins. #
# The chatbot is designed to interact with the user, call functions #
# as needed, and return responses. If auto function calling is #
# disabled, then the tool calls will be printed to the console. #
# as needed, and return responses. With auto function calling #
# disabled, the tool calls will be printed to the console. #
#####################################################################

# System message defining the behavior and persona of the chat bot.
Expand Down Expand Up @@ -67,10 +67,10 @@
# Please make sure you have configured your environment correctly for the selected chat completion service.
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.OPENAI)

# Configure the function choice behavior. Here, we set it to Auto with auto_invoke=True.
# - If `auto_invoke=True`, the model will automatically choose and call functions as needed.
# - If `auto_invoke=False`, the model may return tool call instructions that you must handle and call manually.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(auto_invoke=True)
# Configure the function choice behavior. Here, we set it to Auto, where auto_invoke=False.
# With `FunctionChoiceBehavior(auto_invoke=False)`, the model may return tool call instructions
# that you must handle and call manually. We will only print the tool calls in this sample.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(auto_invoke=False)

kernel.add_service(chat_completion_service)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
# Please make sure you have configured your environment correctly for the selected chat completion service.
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.AZURE_OPENAI)

# Configure the function choice behavior. Here, we set it to Auto with auto_invoke=True.
# - If `auto_invoke=True`, the model will automatically choose and call functions as needed.
# - If `auto_invoke=False`, the model may return tool call instructions that you must handle and call manually.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(auto_invoke=True)
# Configure the function choice behavior. Here, we set it to Auto, where auto_invoke=False.
# With `FunctionChoiceBehavior(auto_invoke=False)`, the model may return tool call instructions
# that you must handle and call manually. We will only print the tool calls in this sample.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(auto_invoke=False)

kernel.add_service(chat_completion_service)

Expand Down Expand Up @@ -137,9 +137,6 @@ async def handle_streaming(
arguments=arguments,
)

# We will differentiate behavior based on whether auto invoking kernel functions is enabled.
auto_invoking = request_settings.function_choice_behavior.auto_invoke_kernel_functions

print("Mosscap:> ", end="", flush=True)

# If auto_invoking is False, the model may return separate streaming chunks containing tool instructions.
Expand All @@ -156,25 +153,20 @@ async def handle_streaming(
if not isinstance(msg, StreamingChatMessageContent) or msg.role != AuthorRole.ASSISTANT:
continue

if auto_invoking:
# When auto invocation is ON, no special handling is needed. Just print out messages as they arrive.
# When auto invocation is OFF, the model may send chunks that represent tool calls.
# Chunks that contain function call instructions will have a function_invoke_attempt attribute.
if hasattr(msg, "function_invoke_attempt"):
# This chunk is part of a tool call instruction sequence
streamed_tool_chunks.append(msg)
else:
# This chunk is normal assistant response text
streamed_response_chunks.append(msg)
print(str(msg), end="", flush=True)
else:
# When auto invocation is OFF, the model may send chunks that represent tool calls.
# Chunks that contain function call instructions will have a function_invoke_attempt attribute.
if hasattr(msg, "function_invoke_attempt"):
# This chunk is part of a tool call instruction sequence
streamed_tool_chunks.append(msg)
else:
# This chunk is normal assistant response text
streamed_response_chunks.append(msg)
print(str(msg), end="", flush=True)

print("\n", flush=True)

# If auto function calling was OFF, handle any tool call instructions we captured.
if not auto_invoking and streamed_tool_chunks:
if streamed_tool_chunks:
# Group streamed chunks by `function_invoke_attempt` to handle each invocation attempt separately.
grouped_chunks = {}
for chunk in streamed_tool_chunks:
Expand Down

0 comments on commit 8464649

Please sign in to comment.