chore: import upstream snapshot with attribution
This commit is contained in:
+83
@@ -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())
|
||||
+125
@@ -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())
|
||||
+130
@@ -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())
|
||||
+167
@@ -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())
|
||||
+181
@@ -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}}<human>:
|
||||
{{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.<human_end>""", # 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())
|
||||
Reference in New Issue
Block a user