chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from enum import Enum
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
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.contents import ChatHistory
|
||||
from semantic_kernel.functions import kernel_function
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
|
||||
from semantic_kernel.processes.local_runtime.local_event import KernelProcessEvent
|
||||
from semantic_kernel.processes.local_runtime.local_kernel_process import start
|
||||
from semantic_kernel.processes.process_builder import ProcessBuilder
|
||||
|
||||
|
||||
class CommonEvents(Enum):
|
||||
UserInputReceived = "UserInputReceived"
|
||||
AssistantResponseGenerated = "AssistantResponseGenerated"
|
||||
|
||||
|
||||
class ChatBotEvents(Enum):
|
||||
StartProcess = "startProcess"
|
||||
IntroComplete = "introComplete"
|
||||
AssistantResponseGenerated = "assistantResponseGenerated"
|
||||
Exit = "exit"
|
||||
|
||||
|
||||
class UserInputState(KernelBaseModel):
|
||||
user_inputs: list[str] = []
|
||||
current_input_index: int = 0
|
||||
|
||||
|
||||
class UserInputStep(KernelProcessStep[UserInputState]):
|
||||
GET_USER_INPUT: ClassVar[str] = "get_user_input"
|
||||
|
||||
def create_default_state(self) -> "UserInputState":
|
||||
"""Creates the default UserInputState."""
|
||||
return UserInputState()
|
||||
|
||||
def populate_user_inputs(self):
|
||||
"""Method to be overridden by the user to populate with custom user messages."""
|
||||
pass
|
||||
|
||||
async def activate(self, state: KernelProcessStepState[UserInputState]):
|
||||
"""Activates the step and sets the state."""
|
||||
state.state = state.state or self.create_default_state()
|
||||
self.state = state.state
|
||||
self.populate_user_inputs()
|
||||
|
||||
@kernel_function(name=GET_USER_INPUT)
|
||||
async def get_user_input(self, context: KernelProcessStepContext):
|
||||
"""Gets the user input."""
|
||||
if not self.state:
|
||||
raise ValueError("State has not been initialized")
|
||||
|
||||
user_message = input("USER: ")
|
||||
|
||||
# print(f"USER: {user_message}")
|
||||
|
||||
if "exit" in user_message:
|
||||
await context.emit_event(process_event=ChatBotEvents.Exit, data=None)
|
||||
return
|
||||
|
||||
self.state.current_input_index += 1
|
||||
|
||||
# Emit the user input event
|
||||
await context.emit_event(process_event=CommonEvents.UserInputReceived, data=user_message)
|
||||
|
||||
|
||||
class ScriptedInputStep(UserInputStep):
|
||||
def populate_user_inputs(self):
|
||||
"""Override the method to populate user inputs for the chat step."""
|
||||
if self.state is not None:
|
||||
self.state.user_inputs.append("Hello")
|
||||
self.state.user_inputs.append("How tall is the tallest mountain?")
|
||||
self.state.user_inputs.append("How low is the lowest valley?")
|
||||
self.state.user_inputs.append("How wide is the widest river?")
|
||||
self.state.user_inputs.append("exit")
|
||||
|
||||
@kernel_function
|
||||
async def get_user_input(self, context: KernelProcessStepContext):
|
||||
"""Gets the user input."""
|
||||
if not self.state:
|
||||
raise ValueError("State has not been initialized")
|
||||
|
||||
user_message = self.state.user_inputs[self.state.current_input_index]
|
||||
|
||||
print(f"USER: {user_message}")
|
||||
|
||||
if "exit" in user_message:
|
||||
await context.emit_event(process_event=ChatBotEvents.Exit, data=None)
|
||||
return
|
||||
|
||||
self.state.current_input_index += 1
|
||||
|
||||
# Emit the user input event
|
||||
await context.emit_event(process_event=CommonEvents.UserInputReceived, data=user_message)
|
||||
|
||||
|
||||
class IntroStep(KernelProcessStep):
|
||||
@kernel_function
|
||||
async def print_intro_message(self):
|
||||
print("Welcome to Processes in Semantic Kernel.\n")
|
||||
|
||||
|
||||
class ChatBotState(KernelBaseModel):
|
||||
"""The state object for ChatBotResponseStep."""
|
||||
|
||||
chat_messages: list = []
|
||||
|
||||
|
||||
SERVICE_ID = "default"
|
||||
|
||||
|
||||
class ChatBotResponseStep(KernelProcessStep[ChatBotState]):
|
||||
GET_CHAT_RESPONSE: ClassVar[str] = "get_chat_response"
|
||||
|
||||
state: ChatBotState = Field(default_factory=ChatBotState)
|
||||
|
||||
async def activate(self, state: "KernelProcessStepState[ChatBotState]"):
|
||||
"""Activates the step and initializes the state object."""
|
||||
self.state = state.state or ChatBotState()
|
||||
self.state.chat_messages = self.state.chat_messages or []
|
||||
|
||||
@kernel_function(name=GET_CHAT_RESPONSE)
|
||||
async def get_chat_response(self, context: "KernelProcessStepContext", user_message: str, kernel: "Kernel"):
|
||||
"""Generates a response from the chat completion service."""
|
||||
# Add user message to the state
|
||||
self.state.chat_messages.append({"role": "user", "message": user_message})
|
||||
|
||||
# Get chat completion service and generate a response
|
||||
chat_service: ChatCompletionClientBase = kernel.get_service(service_id=SERVICE_ID)
|
||||
settings = chat_service.instantiate_prompt_execution_settings(service_id=SERVICE_ID)
|
||||
|
||||
chat_history = ChatHistory()
|
||||
chat_history.add_user_message(user_message)
|
||||
response = await chat_service.get_chat_message_contents(chat_history=chat_history, settings=settings)
|
||||
|
||||
if response is None:
|
||||
raise ValueError("Failed to get a response from the chat completion service.")
|
||||
|
||||
answer = response[0].content
|
||||
|
||||
print(f"ASSISTANT: {answer}")
|
||||
|
||||
# Update state with the response
|
||||
self.state.chat_messages.append(answer)
|
||||
|
||||
# Emit an event: assistantResponse
|
||||
await context.emit_event(process_event=ChatBotEvents.AssistantResponseGenerated, data=answer)
|
||||
|
||||
|
||||
kernel = Kernel()
|
||||
|
||||
|
||||
async def step01_processes(scripted: bool = True):
|
||||
kernel.add_service(OpenAIChatCompletion(service_id="default"))
|
||||
|
||||
process = ProcessBuilder(name="ChatBot")
|
||||
|
||||
# Define the steps on the process builder based on their types, not concrete objects
|
||||
intro_step = process.add_step(IntroStep)
|
||||
user_input_step = process.add_step(ScriptedInputStep if scripted else UserInputStep)
|
||||
response_step = process.add_step(ChatBotResponseStep)
|
||||
|
||||
# Define the input event that starts the process and where to send it
|
||||
process.on_input_event(event_id=ChatBotEvents.StartProcess).send_event_to(target=intro_step)
|
||||
|
||||
# Define the event that triggers the next step in the process
|
||||
intro_step.on_function_result(function_name=IntroStep.print_intro_message.__name__).send_event_to(
|
||||
target=user_input_step
|
||||
)
|
||||
|
||||
# Define the event that triggers the process to stop
|
||||
user_input_step.on_event(event_id=ChatBotEvents.Exit).stop_process()
|
||||
# For the user step, send the user input to the response step
|
||||
user_input_step.on_event(event_id=CommonEvents.UserInputReceived).send_event_to(
|
||||
target=response_step, parameter_name="user_message"
|
||||
)
|
||||
|
||||
# For the response step, send the response back to the user input step
|
||||
response_step.on_event(event_id=ChatBotEvents.AssistantResponseGenerated).send_event_to(target=user_input_step)
|
||||
|
||||
# Build the kernel process
|
||||
kernel_process = process.build()
|
||||
|
||||
# Start the process
|
||||
await start(
|
||||
process=kernel_process,
|
||||
kernel=kernel,
|
||||
initial_event=KernelProcessEvent(id=ChatBotEvents.StartProcess, data=None),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# if you want to run this sample with your won input, set the below parameter to False
|
||||
asyncio.run(step01_processes(scripted=False))
|
||||
Reference in New Issue
Block a user