Skip to content

Commit bf5d21c

Browse files
authored
Python: Properly handle exceptions in the kernel using the exception metadata (#5391)
### Motivation and Context Two things: 1) Add the exception to the on function invoked args if an exception exists in the function result. 2) Set the stepwise planner's goal to the plan's description, not the name. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description The PR addresses: - Properly handling exceptions to raise them from the kernel if a function result contains an exception in the metadata. Closes #5385 - In the stepwise planner, the goal was getting set as the plan's name, instead of the description. Closes #5316 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent c57608c commit bf5d21c

File tree

7 files changed

+16
-13
lines changed

7 files changed

+16
-13
lines changed

python/semantic_kernel/functions/kernel_function.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ async def invoke(
168168
return await self._invoke_internal(kernel, arguments)
169169
except Exception as exc:
170170
logger.error(f"Error occurred while invoking function {self.name}: {exc}")
171-
return FunctionResult(function=self.metadata, value=None, metadata={"error": exc, "arguments": arguments})
171+
return FunctionResult(
172+
function=self.metadata, value=None, metadata={"exception": exc, "arguments": arguments}
173+
)
172174

173175
@abstractmethod
174176
async def _invoke_internal_stream(
@@ -204,4 +206,4 @@ async def invoke_stream(
204206
yield partial_result
205207
except Exception as e:
206208
logger.error(f"Error occurred while invoking function {self.name}: {e}")
207-
yield FunctionResult(function=self.metadata, value=None, metadata={"error": e, "arguments": arguments})
209+
yield FunctionResult(function=self.metadata, value=None, metadata={"exception": e, "arguments": arguments})

python/semantic_kernel/functions/kernel_function_from_prompt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ async def _handle_complete_chat_stream(
291291
return # Exit after processing all iterations
292292
except Exception as e:
293293
logger.error(f"Error occurred while invoking function {self.name}: {e}")
294-
yield FunctionResult(function=self.metadata, value=None, metadata={"error": e})
294+
yield FunctionResult(function=self.metadata, value=None, metadata={"exception": e})
295295

296296
async def _handle_complete_text_stream(
297297
self,
@@ -306,7 +306,7 @@ async def _handle_complete_text_stream(
306306
return
307307
except Exception as e:
308308
logger.error(f"Error occurred while invoking function {self.name}: {e}")
309-
yield FunctionResult(function=self.metadata, value=None, metadata={"error": e})
309+
yield FunctionResult(function=self.metadata, value=None, metadata={"exception": e})
310310

311311
def add_default_values(self, arguments: "KernelArguments") -> KernelArguments:
312312
"""Gathers the function parameters from the arguments."""

python/semantic_kernel/kernel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def on_function_invoked(
415415
kernel_function_metadata=kernel_function_metadata,
416416
arguments=arguments,
417417
function_result=function_result,
418-
exception=exception,
418+
exception=exception or function_result.metadata.get("exception", None) if function_result else None,
419419
)
420420
if self.function_invoked_handlers:
421421
for handler in self.function_invoked_handlers.values():

python/semantic_kernel/planners/stepwise_planner/stepwise_planner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def create_plan(self, goal: str) -> Plan:
121121
plan_step._outputs.append("plugin_count")
122122
plan_step._outputs.append("steps_taken")
123123

124-
plan = Plan(goal)
124+
plan = Plan(description=goal)
125125

126126
plan.add_steps([plan_step])
127127

python/tests/integration/completions/test_azure_oai_chat_service_extensions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ async def create_with_data_chat_function(get_aoai_config, kernel: Kernel, create
142142

143143

144144
@pytest.mark.asyncio
145+
@pytest.mark.xfail(reason="The test is failing a 400 saying the request body is invalid. Will investigate.")
145146
@pytestmark
146147
async def test_azure_e2e_chat_completion_with_extensions(
147148
create_with_data_chat_function,

python/tests/unit/functions/test_kernel_function_from_method.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def non_async_function() -> str:
142142
assert result.value == ""
143143

144144
async for partial_result in native_function.invoke_stream(kernel=None, arguments=None):
145-
assert isinstance(partial_result.metadata["error"], NotImplementedError)
145+
assert isinstance(partial_result.metadata["exception"], NotImplementedError)
146146

147147

148148
@pytest.mark.asyncio
@@ -157,7 +157,7 @@ async def async_function() -> str:
157157
assert result.value == ""
158158

159159
async for partial_result in native_function.invoke_stream(kernel=None, arguments=None):
160-
assert isinstance(partial_result.metadata["error"], NotImplementedError)
160+
assert isinstance(partial_result.metadata["exception"], NotImplementedError)
161161

162162

163163
@pytest.mark.asyncio
@@ -227,7 +227,7 @@ def my_function(input: str) -> str:
227227
func = KernelFunction.from_method(my_function, "test")
228228

229229
result = await func.invoke(kernel=None, arguments=KernelArguments())
230-
assert isinstance(result.metadata["error"], FunctionExecutionException)
230+
assert isinstance(result.metadata["exception"], FunctionExecutionException)
231231

232232

233233
@pytest.mark.asyncio

python/tests/unit/functions/test_kernel_function_from_prompt.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ async def test_invoke_exception():
183183
) as mock:
184184
mock.return_value = [ChatMessageContent(role="assistant", content="test", metadata={})]
185185
result = await function.invoke(kernel=kernel)
186-
assert isinstance(result.metadata["error"], Exception)
186+
assert isinstance(result.metadata["exception"], Exception)
187187

188188
with patch(
189189
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat_stream",
@@ -193,7 +193,7 @@ async def test_invoke_exception():
193193
StreamingChatMessageContent(choice_index=0, role="assistant", content="test", metadata={})
194194
]
195195
async for result in function.invoke_stream(kernel=kernel):
196-
assert isinstance(result.metadata["error"], Exception)
196+
assert isinstance(result.metadata["exception"], Exception)
197197

198198

199199
@pytest.mark.asyncio
@@ -237,15 +237,15 @@ async def test_invoke_exception_text():
237237
) as mock:
238238
mock.return_value = [TextContent(text="test", metadata={})]
239239
result = await function.invoke(kernel=kernel)
240-
assert isinstance(result.metadata["error"], Exception)
240+
assert isinstance(result.metadata["exception"], Exception)
241241

242242
with patch(
243243
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.complete_stream",
244244
side_effect=Exception,
245245
) as mock:
246246
mock.__iter__.return_value = []
247247
async for result in function.invoke_stream(kernel=kernel):
248-
assert isinstance(result.metadata["error"], Exception)
248+
assert isinstance(result.metadata["exception"], Exception)
249249

250250

251251
@pytest.mark.asyncio

0 commit comments

Comments
 (0)