diff --git a/python/samples/kernel-syntax-examples/openai_function_calling_stepwise_planner.py b/python/samples/kernel-syntax-examples/openai_function_calling_stepwise_planner.py index 967eef91bd..709cb82d57 100644 --- a/python/samples/kernel-syntax-examples/openai_function_calling_stepwise_planner.py +++ b/python/samples/kernel-syntax-examples/openai_function_calling_stepwise_planner.py @@ -4,9 +4,7 @@ import asyncio import os import semantic_kernel as sk -from semantic_kernel.connectors.ai.open_ai import ( - OpenAIChatCompletion, -) +from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.core_plugins.math_plugin import MathPlugin from semantic_kernel.core_plugins.time_plugin import TimePlugin from semantic_kernel.planners.function_calling_stepwise_planner.function_calling_stepwise_planner import ( diff --git a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py index 455c070b18..7e79c502b6 100644 --- a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py +++ b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py @@ -1,10 +1,12 @@ # Copyright (c) Microsoft. All rights reserved. +from __future__ import annotations + import asyncio import logging import os from copy import copy -from typing import Optional +from typing import Any, Optional import yaml @@ -89,6 +91,8 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): self, kernel: Kernel, question: str, + arguments: KernelArguments | None = None, + **kwargs: Any, ) -> FunctionCallingStepwisePlannerResult: """ Execute the function calling stepwise planner @@ -96,6 +100,8 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): Args: kernel: The kernel instance question: The input question + arguments: (optional) The kernel arguments + kwargs: (optional) Additional keyword arguments Returns: FunctionCallingStepwisePlannerResult: The result of the function calling stepwise planner @@ -106,6 +112,9 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): if not question: raise PlannerInvalidConfigurationError("Input question cannot be empty") + if not arguments: + arguments = KernelArguments(**kwargs) + try: chat_completion = kernel.get_service(service_id=self.service_id) except Exception as exc: @@ -132,12 +141,13 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): cloned_kernel.import_plugin_from_object(UserInteraction(), "UserInteraction") # Create and invoke a kernel function to generate the initial plan - initial_plan = await self._generate_plan(question, cloned_kernel) + initial_plan = await self._generate_plan(question=question, kernel=cloned_kernel, arguments=arguments) - chat_history_for_steps = await self._build_chat_history_for_step(question, initial_plan, cloned_kernel) + chat_history_for_steps = await self._build_chat_history_for_step( + goal=question, initial_plan=initial_plan, kernel=cloned_kernel, arguments=arguments, service=chat_completion + ) prompt_execution_settings.tool_choice = "auto" prompt_execution_settings.tools = get_tool_call_object(kernel, {"exclude_plugin": [self.service_id]}) - arguments = KernelArguments() for i in range(self.options.max_iterations): # sleep for a bit to avoid rate limiting if i > 0: @@ -186,20 +196,23 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): goal: str, initial_plan: str, kernel: Kernel, + arguments: KernelArguments, + service: OpenAIChatCompletion | AzureChatCompletion, ) -> ChatHistory: """Build the chat history for the stepwise planner""" chat_history = ChatHistory() - arguments = KernelArguments( + additional_arguments = KernelArguments( goal=goal, initial_plan=initial_plan, ) + arguments.update(additional_arguments) kernel_prompt_template = KernelPromptTemplate( prompt_template_config=PromptTemplateConfig( template=self.step_prompt, ) ) - system_message = await kernel_prompt_template.render(kernel, arguments) - chat_history.add_system_message(system_message) + prompt = await kernel_prompt_template.render(kernel, arguments) + chat_history = ChatHistory.from_rendered_prompt(prompt, service.get_chat_message_content_type()) return chat_history def _create_config_from_yaml(self, kernel: Kernel) -> "KernelFunction": @@ -222,7 +235,7 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): self, question: str, kernel: Kernel, - arguments: KernelArguments = None, + arguments: KernelArguments, ) -> str: """Generate the plan for the given question using the kernel""" generate_plan_function = self._create_config_from_yaml(kernel) @@ -234,5 +247,6 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): available_functions=functions_manual, goal=question, ) + generated_plan_args.update(arguments) generate_plan_result = await kernel.invoke(generate_plan_function, generated_plan_args) return str(generate_plan_result) diff --git a/python/tests/unit/planners/function_calling_stepwise_planner/test_unit_function_calling_stepwise_planner.py b/python/tests/unit/planners/function_calling_stepwise_planner/test_unit_function_calling_stepwise_planner.py index b19eae032a..7c536a7dd1 100644 --- a/python/tests/unit/planners/function_calling_stepwise_planner/test_unit_function_calling_stepwise_planner.py +++ b/python/tests/unit/planners/function_calling_stepwise_planner/test_unit_function_calling_stepwise_planner.py @@ -5,7 +5,9 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest +from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion from semantic_kernel.exceptions.planner_exceptions import PlannerInvalidConfigurationError +from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.functions.kernel_function import KernelFunction from semantic_kernel.kernel import Kernel from semantic_kernel.planners.function_calling_stepwise_planner.function_calling_stepwise_planner import ( @@ -53,8 +55,14 @@ async def test_build_chat_history_for_step(): planner = FunctionCallingStepwisePlanner(service_id="test_service", options=None) kernel_mock = AsyncMock(Kernel) kernel_mock.get_service.return_value = AsyncMock() - chat_history = await planner._build_chat_history_for_step("goal", "initial_plan", kernel_mock) + service_mock = AsyncMock(spec=OpenAIChatCompletion) + arguments_mock = KernelArguments(goal="Test", initial_plan="Initial Plan") + service_mock.get_chat_message_content_type.return_value = "OpenAIChatMessageContent" + chat_history = await planner._build_chat_history_for_step( + "goal", "initial_plan", kernel_mock, arguments_mock, service_mock + ) assert chat_history is not None + assert chat_history[0].role == "user" @pytest.mark.asyncio @@ -66,6 +74,8 @@ async def test_generate_plan(): plugins_mock = MagicMock() kernel_mock.plugins = MagicMock(plugins=plugins_mock) + mock_arguments = KernelArguments() + with patch( "semantic_kernel.planners.function_calling_stepwise_planner.FunctionCallingStepwisePlanner._create_config_from_yaml", return_value=AsyncMock(spec=KernelFunction), @@ -74,7 +84,7 @@ async def test_generate_plan(): return_value=AsyncMock(return_value=MagicMock()), ): question = "Why is the sky blue?" - result = await planner._generate_plan(question, kernel_mock) + result = await planner._generate_plan(question, kernel_mock, mock_arguments) mock_create_yaml_config.assert_called_once_with(kernel_mock) assert result is not None