chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,83 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from azure.identity import AzureCliCredential
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import SessionsPythonTool
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
kernel = Kernel()
service_id = "sessions-tool"
credential = AzureCliCredential()
chat_service = AzureChatCompletion(service_id=service_id, credential=credential)
kernel.add_service(chat_service)
sessions_tool = SessionsPythonTool(credential=credential)
kernel.add_plugin(sessions_tool, "SessionsTool")
kernel.add_plugin(TimePlugin(), "Time")
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
)
req_settings = AzureChatPromptExecutionSettings(service_id=service_id, tool_choice="auto")
req_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(filters={"excluded_plugins": ["ChatBot"]})
arguments = KernelArguments(settings=req_settings)
history = ChatHistory()
async def chat() -> bool:
try:
user_input = input("User:> ")
except KeyboardInterrupt:
print("\n\nExiting chat...")
return False
except EOFError:
print("\n\nExiting chat...")
return False
if user_input == "exit":
print("\n\nExiting chat...")
return False
arguments["chat_history"] = history
arguments["user_input"] = user_input
answer = await kernel.invoke(
function=chat_function,
arguments=arguments,
)
print(f"Mosscap:> {answer}")
history.add_user_message(user_input)
history.add_assistant_message(str(answer))
return True
async def main() -> None:
print(
"Welcome to the chat bot!\
\n Type 'exit' to exit.\
\n Try a Python code execution question to see the function calling in action (i.e. what is 1+1?)."
)
chatting = True
while chatting:
chatting = await chat()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,125 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
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
from semantic_kernel.prompt_template import PromptTemplateConfig
#####################################################################
# 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_template_config=PromptTemplateConfig(
template="{{$chat_history}}{{$user_input}}", allow_dangerously_set_content=True
),
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
# - Services.DEEPSEEK
# 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(filters={"excluded_plugins": ["ChatBot"]})
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_message(result.value[0]) # Capture the full context of the response
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())
@@ -0,0 +1,130 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
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
#####################################################################
# 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
# - Services.DEEPSEEK
# 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.
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 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?')."
)
while True:
try:
user_input = input("User:> ")
except (KeyboardInterrupt, EOFError):
print("\n\nExiting chat...")
break
if user_input.lower().strip() == "exit":
print("\n\nExiting chat...")
break
arguments["user_input"] = user_input
arguments["chat_history"] = history
# Directly handle streaming of the assistant's response here
print("Mosscap:> ", end="", flush=True)
streamed_response_chunks: list[StreamingChatMessageContent] = []
async for message in kernel.invoke_stream(
chat_function,
return_function_results=False,
arguments=arguments,
):
msg = message[0]
# We only expect assistant messages here.
if not isinstance(msg, StreamingChatMessageContent) or msg.role != AuthorRole.ASSISTANT:
continue
streamed_response_chunks.append(msg)
print(str(msg), end="", flush=True)
print("\n", flush=True)
if streamed_response_chunks:
result = "".join([str(content) for content in streamed_response_chunks])
history.add_user_message(user_input)
history.add_assistant_message(result)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,167 @@
# 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.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
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 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. 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.
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
# - Services.DEEPSEEK
# 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=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)
# 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.")
def print_tool_calls(message: ChatMessageContent) -> None:
"""
A helper function to pretty print the tool calls found in a ChatMessageContent message.
This is useful when auto tool invocation is disabled and the model returns calls that you must handle.
"""
items = message.items
formatted_tool_calls = []
for i, item in enumerate(items, start=1):
if isinstance(item, FunctionCallContent):
tool_call_id = item.id
function_name = item.name
function_arguments = item.arguments
formatted_str = (
f"tool_call {i} id: {tool_call_id}\n"
f"tool_call {i} function name: {function_name}\n"
f"tool_call {i} arguments: {function_arguments}"
)
formatted_tool_calls.append(formatted_str)
if len(formatted_tool_calls) > 0:
print("\n[Tool calls returned by the model]:\n" + "\n\n".join(formatted_tool_calls))
else:
print("\n[No tool calls returned by the model]")
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)
# If function calls are returned, we show them on the console.
if result and result.value:
# Extract function calls from the returned content
function_calls = [item for item in result.value[-1].items if isinstance(item, FunctionCallContent)]
if len(function_calls) > 0:
print_tool_calls(result.value[0])
# At this point, you'd handle these calls manually if desired.
# For now, we just print them.
return True
# If no function calls to handle, just print the assistant's response
if result:
print(f"Mosscap:> {result}")
# 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())
@@ -0,0 +1,181 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from functools import reduce
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.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
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:
pass
#####################################################################
# This sample demonstrates how to build a conversational chatbot #
# using Semantic Kernel, featuring dynamic 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. If auto function calling is #
# disabled, then the tool calls will be printed to the console. #
#####################################################################
# 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",
)
# Configure the chat completion service and request settings.
chat_completion_service, request_settings = get_chat_completion_service_and_request_settings(Services.AZURE_OPENAI)
# Configure the function choice behavior to Auto with auto_invoke=False.
# This means the model may return tool calls that must be manually handled.
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(auto_invoke=False)
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.")
def print_tool_calls(message: ChatMessageContent) -> None:
"""
A helper function to pretty print the tool calls found in a ChatMessageContent message.
This is useful when auto tool invocation is disabled and the model returns calls that you must handle.
"""
items = message.items
formatted_tool_calls = []
for i, item in enumerate(items, start=1):
if isinstance(item, FunctionCallContent):
tool_call_id = item.id
function_name = item.name
function_arguments = item.arguments
formatted_str = (
f"tool_call {i} id: {tool_call_id}\n"
f"tool_call {i} function name: {function_name}\n"
f"tool_call {i} arguments: {function_arguments}"
)
formatted_tool_calls.append(formatted_str)
if len(formatted_tool_calls) > 0:
print("\n[Tool calls returned by the model]:\n" + "\n\n".join(formatted_tool_calls))
else:
print("\n[No tool calls returned by the model]")
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?')."
)
while True:
# Get user input
try:
user_input = input("User:> ")
except (KeyboardInterrupt, EOFError):
print("\n\nExiting chat...")
break
if user_input.lower().strip() == "exit":
print("\n\nExiting chat...")
break
# Prepare arguments for the model invocation
arguments["user_input"] = user_input
arguments["chat_history"] = history
print("Mosscap:> ", end="", flush=True)
# Lists to store streamed chunks
streamed_tool_chunks: list[StreamingChatMessageContent] = []
streamed_response_chunks: list[StreamingChatMessageContent] = []
async for message in kernel.invoke_stream(
chat_function,
return_function_results=False,
arguments=arguments,
):
msg = message[0]
# Expecting assistant messages only
if not isinstance(msg, StreamingChatMessageContent) or msg.role != AuthorRole.ASSISTANT:
continue
# If auto_invoking is False, the model may send tool calls in separate chunks.
if hasattr(msg, "function_invoke_attempt"):
# This chunk is part of a tool call instruction
streamed_tool_chunks.append(msg)
else:
# Normal assistant response text
streamed_response_chunks.append(msg)
print(str(msg), end="", flush=True)
print("\n", flush=True)
# If we have tool call instructions
if streamed_tool_chunks:
# Group streamed tool chunks by `function_invoke_attempt`
grouped_chunks = {}
for chunk in streamed_tool_chunks:
key = getattr(chunk, "function_invoke_attempt", None)
if key is not None:
grouped_chunks.setdefault(key, []).append(chunk)
# Process each group of chunks
for attempt, chunks in grouped_chunks.items():
try:
combined_content = reduce(lambda first, second: first + second, chunks)
if hasattr(combined_content, "content"):
print(f"[function_invoke_attempt {attempt} content]:\n{combined_content.content}")
print("[Auto function calling is OFF] Here are the returned tool calls:")
print_tool_calls(combined_content)
except Exception as e:
print(f"Error processing chunks for function_invoke_attempt {attempt}: {e}")
# Update the chat history with user input and assistant response, if any
if streamed_response_chunks:
result = "".join([str(content) for content in streamed_response_chunks])
history.add_user_message(user_input)
history.add_assistant_message(str(result))
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,212 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from functools import reduce
from typing import TYPE_CHECKING
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior, FunctionChoiceType
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings
from semantic_kernel.contents import ChatHistory
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.core_plugins import MathPlugin, TimePlugin
from semantic_kernel.functions import KernelArguments
if TYPE_CHECKING:
from semantic_kernel.functions import KernelFunction
# In this sample, we're working with the `FunctionChoiceBehavior.Required` type for auto function calling.
# This type mandates that the model calls a specific function or a set of functions to handle the user input.
# By default, the `maximum_auto_invoke_attempts` is set to 1. This can be adjusted by setting this attribute
# in the `FunctionChoiceBehavior.Required` class.
#
# Note that if the maximum auto invoke attempts exceed the number of functions the model calls, it may repeat calling a
# function and ultimately return a tool call response. For example, if we specify required plugins as `math-Multiply`
# and `math-Add`, and set the maximum auto invoke attempts to 5, and query `What is 3+4*5?`, the model will first call
# the `math-Multiply` function, then the `math-Add` function, satisfying 2 of the 5 max auto invoke attempts.
# The remaining 3 attempts will continue calling `math-Add` because the execution settings are still configured with a
# tool_choice `required` and the supplied tools. The final result will be a tool call response.
#
# This behavior is true for both streaming and non-streaming responses.
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.
Start all your answers with the current time.
"""
# This concept example shows how to handle both streaming and non-streaming responses
# To toggle the behavior, set the following flag accordingly:
stream = False
kernel = Kernel()
# Note: the underlying gpt-35/gpt-4 model version needs to be at least version 0613 to support tools.
service_id = "chat"
kernel.add_service(OpenAIChatCompletion(service_id=service_id))
plugins_directory = os.path.join(__file__, "../../../../../prompt_template_samples/")
# adding plugins to the kernel
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
)
# enabling or disabling function calling is done by setting the function_choice_behavior parameter for the
# prompt execution settings. When the function_call parameter is set to "required" the model will decide which
# function to use, if any. If you only want to use a specific function, configure the filters dict with either:
# 'excluded_plugins', 'included_plugins', 'excluded_functions', or 'included_functions'. For example, the
# format for that is 'PluginName-FunctionName', (i.e. 'math-Add').
# if the model or api version does not support this you will get an error.
# Note: by default, the number of responses for auto invoking `required` tool calls is limited to 1.
# The value may be configured to be more than one depending upon your scenario.
execution_settings = OpenAIChatPromptExecutionSettings(
service_id=service_id,
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_choice_behavior=FunctionChoiceBehavior.Required(
auto_invoke=False,
filters={"included_functions": ["time-time", "time-date"]},
),
)
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.")
arguments = KernelArguments(settings=execution_settings)
def print_tool_calls(message: ChatMessageContent) -> None:
# A helper method to pretty print the tool calls from the message.
# This is only triggered if auto invoke tool calls is disabled.
items = message.items
formatted_tool_calls = []
for i, item in enumerate(items, start=1):
if isinstance(item, FunctionCallContent):
tool_call_id = item.id
function_name = item.name
function_arguments = item.arguments
formatted_str = (
f"tool_call {i} id: {tool_call_id}\n"
f"tool_call {i} function name: {function_name}\n"
f"tool_call {i} arguments: {function_arguments}"
)
formatted_tool_calls.append(formatted_str)
if len(formatted_tool_calls) > 0:
print("Tool calls:\n" + "\n\n".join(formatted_tool_calls))
else:
print("The model used its own knowledge and didn't return any tool calls.")
async def handle_streaming(
kernel: Kernel,
chat_function: "KernelFunction",
arguments: KernelArguments,
) -> None:
response = kernel.invoke_stream(
chat_function,
return_function_results=False,
arguments=arguments,
)
print("Mosscap:> ", end="")
streamed_chunks: list[StreamingChatMessageContent] = []
result_content = []
async for message in response:
if (
(
not execution_settings.function_choice_behavior.auto_invoke_kernel_functions
or execution_settings.function_choice_behavior.type_ == FunctionChoiceType.REQUIRED
)
and isinstance(message[0], StreamingChatMessageContent)
and message[0].role == AuthorRole.ASSISTANT
):
streamed_chunks.append(message[0])
elif isinstance(message[0], StreamingChatMessageContent) and message[0].role == AuthorRole.ASSISTANT:
result_content.append(message[0])
print(str(message[0]), end="")
if streamed_chunks:
streaming_chat_message = reduce(lambda first, second: first + second, streamed_chunks)
if hasattr(streaming_chat_message, "content") and streaming_chat_message.content:
print(streaming_chat_message.content)
print("Printing returned tool calls...")
print_tool_calls(streaming_chat_message)
print("\n")
if result_content:
return "".join([str(content) for content in result_content])
return None
async def chat() -> bool:
try:
user_input = input("User:> ")
except KeyboardInterrupt:
print("\n\nExiting chat...")
return False
except EOFError:
print("\n\nExiting chat...")
return False
if user_input == "exit":
print("\n\nExiting chat...")
return False
arguments["user_input"] = user_input
arguments["chat_history"] = history
if stream:
result = await handle_streaming(kernel, chat_function, arguments=arguments)
else:
result = await kernel.invoke(chat_function, arguments=arguments)
# If tools are used, and auto invoke tool calls is False, the response will be of type
# ChatMessageContent with information about the tool calls, which need to be sent
# back to the model to get the final response.
function_calls = [item for item in result.value[-1].items if isinstance(item, FunctionCallContent)]
if not execution_settings.function_choice_behavior.auto_invoke_kernel_functions and len(function_calls) > 0:
print_tool_calls(result.value[0])
return True
print(f"Mosscap:> {result}")
history.add_user_message(user_input)
history.add_assistant_message(str(result))
return True
async def main() -> None:
chatting = True
print(
"Welcome to the chat bot!\
\n Type 'exit' to exit.\
\n Try a question to see the function calling in action (i.e. what is the current time?)."
)
while chatting:
chatting = await chat()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,231 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from functools import reduce
from typing import TYPE_CHECKING
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings
from semantic_kernel.contents import ChatHistory
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.core_plugins import MathPlugin, TimePlugin
from semantic_kernel.filters.auto_function_invocation.auto_function_invocation_context import (
AutoFunctionInvocationContext,
)
from semantic_kernel.filters.filter_types import FilterTypes
from semantic_kernel.functions import KernelArguments
if TYPE_CHECKING:
from semantic_kernel.functions import KernelFunction
##################################################################
# Sample Notes:
# In this sample, we're showing how to configure a JSON config file with `function_choice_behavior` settings.
# The `function_choice_behavior` settings are used to control the auto function calling behavior of the model.
# The related config is located in the `resources` folder under the title `function_choice_json/ChatBot`.
# The execution settings look like:
# "execution_settings": {
# "chat": {
# "function_choice_behavior": {
# "type": "auto",
# "maximum_auto_invoke_attempts": 5,
# "functions": [
# "time.date",
# "time.time",
# "math.Add"
# ]
# }
# }
# }
# This is another way of configuring the function choice behavior for the model like:
# FunctionChoiceBehavior.Auto(filters={"included_functions": ["time.date", "time.time", "math.Add"]})
# The `maximum_auto_invoke_attempts` attribute is used to control the number of times the model will attempt to call a
# function. If wanting to disable auto function calling, set this attribute to 0 or configure the
# `auto_invoke_kernel_functions` attribute to False.
##################################################################
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.
"""
kernel = Kernel()
# Note: the underlying gpt-35/gpt-4 model version needs to be at least version 0613 to support tools.
service_id = "chat"
kernel.add_service(OpenAIChatCompletion(service_id=service_id))
# adding plugins to the kernel
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
)
plugin_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
"resources",
)
chat_plugin = kernel.add_plugin(plugin_name="function_choice_json", parent_directory=plugin_path)
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.")
# To control auto function calling you can do it two ways:
# 1. Configure the attribute `auto_invoke_kernel_functions` as False
# 2. Configure the `maximum_auto_invoke_attempts` as 0.
# These can be done directly on the FunctionChoiceBehavior.Auto/Required/None object or via the JSON/yaml config.
execution_settings: OpenAIChatPromptExecutionSettings = chat_plugin["ChatBot"].prompt_execution_settings[service_id]
arguments = KernelArguments()
# We will hook up a filter to show which function is being called.
# A filter is a piece of custom code that runs at certain points in the process
# this sample has a filter that is called during Auto Function Invocation
# this filter will be called for each function call in the response.
# You can name the function itself with arbitrary names, but the signature needs to be:
# `context, next`
# You are then free to run code before the call to the next filter or the function itself.
# if you want to terminate the function calling sequence. set context.terminate to True
@kernel.filter(FilterTypes.AUTO_FUNCTION_INVOCATION)
async def auto_function_invocation_filter(context: AutoFunctionInvocationContext, next):
"""A filter that will be called for each function call in the response."""
print("\nAuto function invocation filter")
print(f"Function: {context.function.fully_qualified_name}")
# if we don't call next, it will skip this function, and go to the next one
await next(context)
def print_tool_calls(message: ChatMessageContent) -> None:
# A helper method to pretty print the tool calls from the message.
# This is only triggered if auto invoke tool calls is disabled.
items = message.items
formatted_tool_calls = []
for i, item in enumerate(items, start=1):
if isinstance(item, FunctionCallContent):
tool_call_id = item.id
function_name = item.name
function_arguments = item.arguments
formatted_str = (
f"tool_call {i} id: {tool_call_id}\n"
f"tool_call {i} function name: {function_name}\n"
f"tool_call {i} arguments: {function_arguments}"
)
formatted_tool_calls.append(formatted_str)
print("Tool calls:\n" + "\n\n".join(formatted_tool_calls))
async def handle_streaming(
kernel: Kernel,
chat_function: "KernelFunction",
arguments: KernelArguments,
) -> str | None:
response = kernel.invoke_stream(
chat_function,
return_function_results=False,
arguments=arguments,
)
print("Mosscap:> ", end="")
streamed_chunks: list[StreamingChatMessageContent] = []
result_content: list[StreamingChatMessageContent] = []
async for message in response:
if (
not execution_settings.function_choice_behavior.auto_invoke_kernel_functions
and isinstance(message[0], StreamingChatMessageContent)
and message[0].role == AuthorRole.ASSISTANT
):
streamed_chunks.append(message[0])
elif isinstance(message[0], StreamingChatMessageContent) and message[0].role == AuthorRole.ASSISTANT:
result_content.append(message[0])
print(str(message[0]), end="")
if streamed_chunks:
streaming_chat_message = reduce(lambda first, second: first + second, streamed_chunks)
if hasattr(streaming_chat_message, "content"):
print(streaming_chat_message.content)
print("Auto tool calls is disabled, printing returned tool calls...")
print_tool_calls(streaming_chat_message)
print("\n")
if result_content:
return "".join([str(content) for content in result_content])
return None
async def chat() -> bool:
try:
user_input = input("User:> ")
except KeyboardInterrupt:
print("\n\nExiting chat...")
return False
except EOFError:
print("\n\nExiting chat...")
return False
if user_input == "exit":
print("\n\nExiting chat...")
return False
arguments["user_input"] = user_input
arguments["chat_history"] = history
stream = False
if stream:
result = await handle_streaming(kernel, chat_function, arguments=arguments)
else:
result = await kernel.invoke(chat_plugin["ChatBot"], arguments=arguments)
# If tools are used, and auto invoke tool calls is False, the response will be of type
# ChatMessageContent with information about the tool calls, which need to be sent
# back to the model to get the final response.
function_calls = [item for item in result.value[-1].items if isinstance(item, FunctionCallContent)]
if not execution_settings.function_choice_behavior.auto_invoke_kernel_functions and len(function_calls) > 0:
print_tool_calls(result.value[0])
return True
print(f"Mosscap:> {result}")
history.add_user_message(user_input)
history.add_assistant_message(str(result))
return True
async def main() -> None:
chatting = True
print(
"Welcome to the chat bot!\
\n Type 'exit' to exit.\
\n Try a math question to see the function calling in action (i.e. what is 3+3?)."
)
while chatting:
chatting = await chat()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,228 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from functools import reduce
from typing import TYPE_CHECKING
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings
from semantic_kernel.contents import ChatHistory
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.core_plugins import MathPlugin, TimePlugin
from semantic_kernel.filters.auto_function_invocation.auto_function_invocation_context import (
AutoFunctionInvocationContext,
)
from semantic_kernel.filters.filter_types import FilterTypes
from semantic_kernel.functions import KernelArguments
if TYPE_CHECKING:
from semantic_kernel.functions import KernelFunction
##################################################################
# Sample Notes:
# In this sample, we're showing how to configure a yaml config file with `function_choice_behavior` settings.
# The `function_choice_behavior` settings are used to control the auto function calling behavior of the model.
# The related config is located in the `resources` folder under the title `function_choice_yaml/ChatBot`.
# The execution settings look like:
# execution_settings:
# chat:
# function_choice_behavior:
# type: auto
# maximum_auto_invoke_attempts: 5
# functions:
# - time.date
# - time.time
# - math.Add
# This is another way of configuring the function choice behavior for the model like:
# FunctionChoiceBehavior.Auto(filters={"included_functions": ["time.date", "time.time", "math.Add"]})
# The `maximum_auto_invoke_attempts` attribute is used to control the number of times the model will attempt to call a
# function. If wanting to disable auto function calling, set this attribute to 0 or configure the
# `auto_invoke_kernel_functions` attribute to False.
##################################################################
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.
"""
kernel = Kernel()
# Note: the underlying gpt-35/gpt-4 model version needs to be at least version 0613 to support tools.
service_id = "chat"
kernel.add_service(OpenAIChatCompletion(service_id=service_id))
# adding plugins to the kernel
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
)
plugin_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
"resources",
)
chat_plugin = kernel.add_plugin(plugin_name="function_choice_yaml", parent_directory=plugin_path)
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.")
# To control auto function calling you can do it two ways:
# 1. Configure the attribute `auto_invoke_kernel_functions` as False
# 2. Configure the `maximum_auto_invoke_attempts` as 0.
# These can be done directly on the FunctionChoiceBehavior.Auto/Required/None object or via the JSON/yaml config.
execution_settings: OpenAIChatPromptExecutionSettings = chat_plugin["ChatBot"].prompt_execution_settings[service_id]
arguments = KernelArguments()
# We will hook up a filter to show which function is being called.
# A filter is a piece of custom code that runs at certain points in the process
# this sample has a filter that is called during Auto Function Invocation
# this filter will be called for each function call in the response.
# You can name the function itself with arbitrary names, but the signature needs to be:
# `context, next`
# You are then free to run code before the call to the next filter or the function itself.
# if you want to terminate the function calling sequence. set context.terminate to True
@kernel.filter(FilterTypes.AUTO_FUNCTION_INVOCATION)
async def auto_function_invocation_filter(context: AutoFunctionInvocationContext, next):
"""A filter that will be called for each function call in the response."""
print("\nAuto function invocation filter")
print(f"Function: {context.function.fully_qualified_name}")
# if we don't call next, it will skip this function, and go to the next one
await next(context)
def print_tool_calls(message: ChatMessageContent) -> None:
# A helper method to pretty print the tool calls from the message.
# This is only triggered if auto invoke tool calls is disabled.
items = message.items
formatted_tool_calls = []
for i, item in enumerate(items, start=1):
if isinstance(item, FunctionCallContent):
tool_call_id = item.id
function_name = item.name
function_arguments = item.arguments
formatted_str = (
f"tool_call {i} id: {tool_call_id}\n"
f"tool_call {i} function name: {function_name}\n"
f"tool_call {i} arguments: {function_arguments}"
)
formatted_tool_calls.append(formatted_str)
print("Tool calls:\n" + "\n\n".join(formatted_tool_calls))
async def handle_streaming(
kernel: Kernel,
chat_function: "KernelFunction",
arguments: KernelArguments,
) -> str | None:
response = kernel.invoke_stream(
chat_function,
return_function_results=False,
arguments=arguments,
)
print("Mosscap:> ", end="")
streamed_chunks: list[StreamingChatMessageContent] = []
result_content: list[StreamingChatMessageContent] = []
async for message in response:
if (
not execution_settings.function_choice_behavior.auto_invoke_kernel_functions
and isinstance(message[0], StreamingChatMessageContent)
and message[0].role == AuthorRole.ASSISTANT
):
streamed_chunks.append(message[0])
elif isinstance(message[0], StreamingChatMessageContent) and message[0].role == AuthorRole.ASSISTANT:
result_content.append(message[0])
print(str(message[0]), end="")
if streamed_chunks:
streaming_chat_message = reduce(lambda first, second: first + second, streamed_chunks)
if hasattr(streaming_chat_message, "content"):
print(streaming_chat_message.content)
print("Auto tool calls is disabled, printing returned tool calls...")
print_tool_calls(streaming_chat_message)
print("\n")
if result_content:
return "".join([str(content) for content in result_content])
return None
async def chat() -> bool:
try:
user_input = input("User:> ")
except KeyboardInterrupt:
print("\n\nExiting chat...")
return False
except EOFError:
print("\n\nExiting chat...")
return False
if user_input == "exit":
print("\n\nExiting chat...")
return False
arguments["user_input"] = user_input
arguments["chat_history"] = history
stream = False
if stream:
result = await handle_streaming(kernel, chat_function, arguments=arguments)
else:
result = await kernel.invoke(chat_plugin["ChatBot"], arguments=arguments)
# If tools are used, and auto invoke tool calls is False, the response will be of type
# ChatMessageContent with information about the tool calls, which need to be sent
# back to the model to get the final response.
function_calls = [item for item in result.value[-1].items if isinstance(item, FunctionCallContent)]
if len(function_calls) > 0:
print_tool_calls(result.value[0])
return True
print(f"Mosscap:> {result}")
history.add_user_message(user_input)
history.add_assistant_message(str(result))
return True
async def main() -> None:
chatting = True
print(
"Welcome to the chat bot!\
\n Type 'exit' to exit.\
\n Try a math question to see the function calling in action (i.e. what is 3+3?)."
)
while chatting:
chatting = await chat()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,405 @@
# Copyright (c) Microsoft. All rights reserved.
import ast
import asyncio
import json
import math
from collections.abc import AsyncGenerator
from html import escape
from typing import Annotated, Any, Literal
from huggingface_hub import AsyncInferenceClient
from pydantic import HttpUrl
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai import (
OpenAIChatCompletion,
)
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase
from semantic_kernel.contents import (
ChatHistory,
ChatMessageContent,
FunctionCallContent,
StreamingChatMessageContent,
TextContent,
)
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.filters.auto_function_invocation.auto_function_invocation_context import (
AutoFunctionInvocationContext,
)
from semantic_kernel.filters.filter_types import FilterTypes
from semantic_kernel.functions import KernelArguments, kernel_function
kernel = Kernel()
@kernel.filter(FilterTypes.AUTO_FUNCTION_INVOCATION)
async def auto_function_invocation_filter(context: AutoFunctionInvocationContext, next):
"""A filter that will be called for each function call in the response."""
print("\033[92m\n Function called by Nexus Raven model\033[0m")
print(f" \033[96mFunction: {context.function.fully_qualified_name}")
print(f" Arguments: {context.arguments}")
await next(context)
print(f" Result: {context.function_result}\n\033[0m")
#########################################################################
# Step 0: Define a custom AI Service, with Prompt Execution settings. ###
# This uses huggingface_hub package, so install that if needed. ###
#########################################################################
class NexusRavenPromptExecutionSettings(PromptExecutionSettings):
do_sample: bool = True
max_new_tokens: int | None = None
stop_sequences: Any = None
temperature: float | None = None
top_p: float | None = None
def prepare_settings_dict(self, **kwargs) -> dict[str, Any]:
"""Prepare the settings dictionary."""
return self.model_dump(
include={"max_new_tokens", "temperature", "top_p", "do_sample", "stop_sequences"},
exclude_unset=False,
exclude_none=True,
by_alias=True,
)
class NexusRavenCompletion(TextCompletionClientBase, ChatCompletionClientBase):
"""To use this class, you should have installed the ``huggingface_hub`` package, and
the environment variable ``HUGGINGFACEHUB_API_TOKEN`` set with your API token,
or given as a named parameter to the constructor."""
client: AsyncInferenceClient
def __init__(
self,
service_id: str,
ai_model_id: str,
endpoint_url: HttpUrl,
api_token: str | None = None,
client: AsyncInferenceClient | None = None,
):
if not client:
client = AsyncInferenceClient(model=endpoint_url, token=api_token)
super().__init__(service_id=service_id, ai_model_id=ai_model_id, client=client)
async def get_chat_message_contents(
self,
chat_history: "ChatHistory",
settings: "PromptExecutionSettings",
**kwargs: Any,
) -> list["ChatMessageContent"]:
"""Creates a chat message content with a function call content within.
Uses the text content and and parses it into a function call content."""
result = await self.get_text_contents(
prompt=chat_history.messages[-1].content,
settings=settings,
)
messages = []
for part in result[0].text.split(";"):
try:
function_call, function_result = await self._execute_function_calls(part, chat_history, **kwargs)
if function_call:
messages.extend([
ChatMessageContent(
role="assistant", items=[function_call], metadata={"ai_model_id": self.ai_model_id}
),
ChatMessageContent(
role="tool",
items=[function_result],
name="nexus",
metadata={"ai_model_id": self.ai_model_id},
),
])
else:
messages.append(ChatMessageContent(role="assistant", content=part, ai_model_id=self.ai_model_id))
except Exception as e:
messages.append(
ChatMessageContent(
role="assistant",
items=[
TextContent(
text=f"An error occurred while executing the function call: {e}",
ai_model_id=self.ai_model_id,
)
],
)
)
return messages
return messages
async def _execute_function_calls(
self, result: str, chat_history: ChatHistory, **kwargs: Any
) -> tuple[FunctionCallContent, FunctionResultContent] | None:
function_call = result.strip().split("\n")[0].strip("Call:").strip()
if not function_call:
return None
parsed_fc = ast.parse(function_call, mode="eval")
if not isinstance(parsed_fc.body, ast.Call):
return None
idx = 0
call_stack = {}
queue = []
current = parsed_fc.body
queue.append((idx, parsed_fc.body))
idx += 1
while queue:
current_idx, current = queue.pop(0)
dependent_on = []
args = {}
for keyword in current.keywords:
if isinstance(keyword.value, ast.Call):
queue.append((idx, keyword.value))
dependent_on.append(idx)
args[keyword.arg] = (idx, keyword.value)
idx += 1
else:
args[keyword.arg] = keyword.value.value
call = {
"idx": current_idx,
"func": current.func.id.replace("_", "-", 1),
"args": args,
"dependent_on": dependent_on,
"fcc": None,
"result": None,
}
call_stack[current_idx] = call
while any(call["result"] is None for call in call_stack.values()):
await asyncio.gather(*[
self._execute_function_call(call, chat_history, kwargs.get("kernel"))
for call in call_stack.values()
if not any(isinstance(arg, tuple) for arg in call["args"].values()) and call["result"] is None
])
for call in call_stack.values():
if call["result"] is None:
for name, arg in call["args"].items():
if isinstance(arg, tuple) and call_stack[arg[0]]["result"] is not None:
function_result: FunctionResultContent = call_stack[arg[0]]["result"]
call["args"][name] = function_result.result
return call_stack[0]["fcc"], call_stack[0]["result"]
async def _execute_function_call(
self, call_def: dict[str, Any], chat_history: ChatHistory, kernel: Kernel
) -> FunctionResultContent:
"""Execute a function call."""
call_def["fcc"] = FunctionCallContent(
name=call_def["func"], arguments=json.dumps(call_def["args"]), id=str(call_def["idx"])
)
result = await kernel.invoke_function_call(call_def["fcc"], chat_history)
if not result:
call_def["result"] = chat_history.messages[-1].items[0]
else:
call_def["result"] = result.function_result
async def get_text_contents(self, prompt: str, settings: NexusRavenPromptExecutionSettings) -> list[TextContent]:
result = await self.client.text_generation(prompt, **settings.prepare_settings_dict(), stream=False)
return [TextContent(text=result.strip(), ai_model_id=self.ai_model_id)]
async def get_streaming_text_contents(self, prompt: str, settings: NexusRavenPromptExecutionSettings):
raise NotImplementedError("Streaming text contents not implemented.")
def get_streaming_chat_message_contents(
self,
chat_history: "ChatHistory",
settings: "PromptExecutionSettings",
**kwargs: Any,
) -> AsyncGenerator[list["StreamingChatMessageContent"], Any]:
raise NotImplementedError("Streaming chat message contents not implemented.")
def get_prompt_execution_settings_class(self) -> type[PromptExecutionSettings]:
return NexusRavenPromptExecutionSettings
##########################################################
# Step 1: Define the functions you want to articulate. ###
##########################################################
class MathPlugin:
@kernel_function
def cylinder_volume(
self,
radius: Annotated[float, "The radius of the base of the cylinder."],
height: Annotated[float, "The height of the cylinder."],
):
"""Calculate the volume of a cylinder."""
if radius < 0 or height < 0:
raise ValueError("Radius and height must be non-negative.")
return math.pi * (radius**2) * height
@kernel_function
def add(
self,
input: Annotated[float, "the first number to add"],
amount: Annotated[float, "the second number to add"],
) -> Annotated[float, "the output is a number"]:
"""Returns the Addition result of the values provided."""
return MathPlugin.calculator(input, amount, "add")
@kernel_function
def subtract(
self,
input: Annotated[float, "the first number"],
amount: Annotated[float, "the number to subtract"],
) -> float:
"""Returns the difference of numbers provided."""
return MathPlugin.calculator(input, amount, "subtract")
@kernel_function
def multiply(
self,
input: Annotated[float, "the first number"],
amount: Annotated[float, "the number to multiply with"],
) -> float:
"""Returns the product of numbers provided."""
return MathPlugin.calculator(input, amount, "multiply")
@kernel_function
def divide(
self,
input: Annotated[float, "the first number"],
amount: Annotated[float, "the number to divide by"],
) -> float:
"""Returns the quotient of numbers provided."""
return MathPlugin.calculator(input, amount, "divide")
@staticmethod
def calculator(
input_a: float,
input_b: float,
operation: Literal["add", "subtract", "multiply", "divide"],
):
"""Computes a calculation."""
match operation:
case "add":
return input_a + input_b
case "subtract":
return input_a - input_b
case "multiply":
return input_a * input_b
case "divide":
return input_a / input_b
#############################################################
# Step 2: Let's define some utils for building the prompt ###
#############################################################
@kernel_function
def format_functions_for_prompt():
filters = {"excluded_plugins": ["kernel"]}
functions = kernel.get_list_of_function_metadata(filters)
formatted_functions = []
for func in functions:
args_strings = []
for arg in func.parameters:
arg_string = f"{arg.name}: {arg.type_}"
if arg.default_value:
arg_string += f" = {arg.default_value}"
args_strings.append(arg_string)
func_string = f"{func.fully_qualified_name.replace('-', '_')}({', '.join(args_strings)})"
formatted_functions.append(
escape(
f"OPTION:\n<func_start>{func_string}<func_end>\n<docstring_start>\n{func.description}\n<docstring_end>"
)
)
return formatted_functions
#########################################################################
# Step 3: Let's define the two prompts, one for Nexus, one for OpenAI ###
# and add everything to the kernel! ###
#########################################################################
kernel.add_function(
"kernel",
function_name="function_call",
prompt="""{{chat_history}}&lt;human&gt;:
{{kernel-format_functions_for_prompt}}
\n\nUser Query: Question: {{user_query}}
Please pick a function from the above options that best answers the user query and fill in the appropriate arguments.&lt;human_end&gt;""", # noqa: E501
template_format="handlebars",
prompt_execution_settings=NexusRavenPromptExecutionSettings(
service_id="nexus",
temperature=0.001,
max_new_tokens=500,
do_sample=False,
stop_sequences=["\nReflection:", "\nThought:"],
),
)
kernel.add_function(
"kernel",
function_name="chat",
prompt="""You are a chatbot that gets fed questions and answers, you write out the response to the question based on the answer, but you do not supply underlying math formulas nor do you try to do math yourself, just a nice sentence that repeats the question and gives the answer. {{chat_history}}""", # noqa: E501
template_format="handlebars",
prompt_execution_settings=OpenAIChatPromptExecutionSettings(
service_id="openai",
temperature=0.0,
max_tokens=1000,
),
)
kernel.add_plugin(MathPlugin(), "math")
kernel.add_function("kernel", format_functions_for_prompt)
kernel.add_service(
NexusRavenCompletion(service_id="nexus", ai_model_id="raven", endpoint_url="http://nexusraven.nexusflow.ai")
)
kernel.add_service(OpenAIChatCompletion(service_id="openai"))
############################################
# Step 4: The main function and a runner ###
############################################
async def run_question(user_input: str, chat_history: ChatHistory):
arguments = KernelArguments(
user_query=user_input,
chat_history=chat_history,
kernel=kernel,
)
result = await kernel.invoke(plugin_name="kernel", function_name="function_call", arguments=arguments)
chat_history.add_user_message(user_input)
for msg in result.value:
chat_history.add_message(msg)
final_result = await kernel.invoke(plugin_name="kernel", function_name="chat", arguments=arguments)
chat_history.add_message(final_result.value[0])
async def main():
chat_history = ChatHistory()
user_input_example = (
"my cake is 3 centimers high and 20 centimers in radius, can you subtract 200 from that number?"
)
print("Welcome to the chatbot!")
print(
"This chatbot uses local function calling with Nexus Raven, and OpenAI for the final answer, "
"it has some math skills so feel free to ask anything about that."
)
print(f'For example: "{user_input_example}".')
print("You can type 'exit' to quit the chatbot.")
while True:
try:
user_input = input("What is your question: ")
except Exception:
break
if user_input == "exit":
break
if not user_input:
user_input = user_input_example
await run_question(user_input, chat_history)
print(chat_history.messages[-1].content)
print("Thanks for chatting with me!")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,102 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import logging
import sys
import time
from typing import Annotated
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.kernel import Kernel
# This sample demonstrates how the kernel will execute functions in parallel.
# The output of this sample should look similar to the following:
#
# [2024-09-11 10:15:35.070 INFO] processing 2 tool calls in parallel.
# The employee with ID 123 is named John Doe and they are 30 years old.
# Time elapsed: 11.96s
#
# The mock plugin simulates a long-running operation to fetch the employee's name and age.
# When you run the sample, you should see the total execution time is less than the sum
# of the two function calls because the kernel executes the functions in parallel.
# This concept example shows how to handle both streaming and non-streaming responses
# To toggle the behavior, set the following flag accordingly:
stream = True
def set_up_logging():
"""Set up logging to verify the kernel execute the functions in parallel"""
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
handler.setFormatter(
logging.Formatter("[%(asctime)s.%(msecs)03d %(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"),
)
# Print only the logs from the chat completion client to reduce the output of the sample
handler.addFilter(lambda record: record.name == "semantic_kernel.connectors.ai.chat_completion_client_base")
root_logger.addHandler(handler)
class EmployeePlugin:
"""A mock plugin to simulate a plugin that fetches employee information"""
@kernel_function(name="get_name", description="Find the name of the employee by the id")
async def get_name(
self, id: Annotated[str, "The ID of the employee"]
) -> Annotated[str, "The name of the employee"]:
# Simulate a long-running operation
await asyncio.sleep(10)
return "John Doe"
@kernel_function(name="get_age", description="Get the age of the employee by the id")
async def get_age(self, id: Annotated[str, "The ID of the employee"]) -> Annotated[int, "The age of the employee"]:
# Simulate a long-running operation
await asyncio.sleep(10)
return 30
async def main():
kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="open_ai"))
kernel.add_plugin(EmployeePlugin(), "EmployeePlugin")
# With this query, the model will call the get_name and get_age functions in parallel.
# Note that for certain queries, the model may choose to call the functions sequentially.
# For example, if the available functions are `get_email_by_id` and `get_name_by_email`,
# the model will not be able to call them in parallel because the second function depends
# on the result of the first function.
query = "What is the name and age of the employee of ID 123?"
arguments = KernelArguments(
settings=PromptExecutionSettings(
# Set the function_choice_behavior to auto to let the model
# decide which function to use, and let the kernel automatically
# execute the functions.
function_choice_behavior=FunctionChoiceBehavior.Auto(),
)
)
start = time.perf_counter()
if stream:
async for result in kernel.invoke_prompt_stream(query, arguments=arguments):
print(str(result[0]), end="")
print()
else:
result = await kernel.invoke_prompt(query, arguments=arguments)
print(result)
print(f"Time elapsed: {time.perf_counter() - start:.2f}s")
if __name__ == "__main__":
set_up_logging()
asyncio.run(main())