103 lines
4.1 KiB
Python
103 lines
4.1 KiB
Python
# 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())
|