forked from microsoft/semantic-kernel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Python] Port MathSkill from C# to python3 (microsoft#841)
### Motivation and Context Port MathSkill from C# to python3. This is essential for advanced skill like plan ### Description The PR includes two part. The first part is an implementation of MathSkill. The implementation follows C# version. It does several things: 1. Parse initial value 2. Get value from context 3. Sum or subtract The unit test covers these scenarios: 1. Add/subtract value 2. Error should throw if string is not correct. Not able to parse. Last part is the init.py and feature matrix. Unit test pass locally. Co-authored-by: Po-Wei Huang <[email protected]> Co-authored-by: Mark Karle <[email protected]> Co-authored-by: Devis Lucato <[email protected]>
- Loading branch information
1 parent
0b1e286
commit 7cd0fea
Showing
4 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
from semantic_kernel.orchestration.sk_context import SKContext | ||
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter | ||
|
||
|
||
class MathSkill: | ||
""" | ||
Description: MathSkill provides a set of functions to make Math calculations. | ||
Usage: | ||
kernel.import_skill("math", new MathSkill()) | ||
Examples: | ||
{{math.Add}} => Returns the sum of initial_value_text and Amount (provided in the SKContext) | ||
""" | ||
|
||
@sk_function( | ||
description="Adds value to a value", | ||
name="Add", | ||
input_description="The value to add") | ||
@sk_function_context_parameter( | ||
name="Amount", | ||
description="Amount to add", | ||
) | ||
def add(self, | ||
initial_value_text: str, | ||
context: SKContext) -> str: | ||
""" | ||
Returns the Addition result of initial and amount values provided. | ||
:param initial_value_text: Initial value as string to add the specified amount | ||
:param context: Contains the context to get the numbers from | ||
:return: The resulting sum as a string | ||
""" | ||
return MathSkill.add_or_subtract(initial_value_text, context, add=True) | ||
|
||
@sk_function( | ||
description="Subtracts value to a value", | ||
name="Subtract", | ||
input_description="The value to subtract") | ||
@sk_function_context_parameter( | ||
name="Amount", | ||
description="Amount to subtract", | ||
) | ||
def subtract(self, | ||
initial_value_text: str, | ||
context: SKContext) -> str: | ||
""" | ||
Returns the difference of numbers provided. | ||
:param initial_value_text: Initial value as string to subtract the specified amount | ||
:param context: Contains the context to get the numbers from | ||
:return: The resulting subtraction as a string | ||
""" | ||
return MathSkill.add_or_subtract(initial_value_text, context, add=False) | ||
|
||
@staticmethod | ||
def add_or_subtract( | ||
initial_value_text: str, | ||
context: SKContext, | ||
add: bool) -> str: | ||
""" | ||
Helper function to perform addition or subtraction based on the add flag. | ||
:param initial_value_text: Initial value as string to add or subtract the specified amount | ||
:param context: Contains the context to get the numbers from | ||
:param add: If True, performs addition, otherwise performs subtraction | ||
:return: The resulting sum or subtraction as a string | ||
""" | ||
try: | ||
initial_value = int(initial_value_text) | ||
except ValueError: | ||
raise ValueError( | ||
f"Initial value provided is not in numeric format: {initial_value_text}") | ||
|
||
context_amount = context["Amount"] | ||
if context_amount is not None: | ||
try: | ||
amount = int(context_amount) | ||
except ValueError: | ||
raise ValueError( | ||
f"Context amount provided is not in numeric format: {context_amount}") | ||
|
||
result = initial_value + amount if add else initial_value - amount | ||
return str(result) | ||
else: | ||
raise ValueError("Context amount should not be None.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
import pytest | ||
|
||
from semantic_kernel import Kernel | ||
from semantic_kernel.core_skills import MathSkill | ||
from semantic_kernel.orchestration.context_variables import ContextVariables | ||
|
||
|
||
def test_can_be_instantiated(): | ||
skill = MathSkill() | ||
assert skill is not None | ||
|
||
|
||
def test_can_be_imported(): | ||
kernel = Kernel() | ||
assert kernel.import_skill(MathSkill(), "math") | ||
assert kernel.skills.has_native_function("math", "add") | ||
assert kernel.skills.has_native_function("math", "subtract") | ||
|
||
|
||
@pytest.mark.parametrize("initial_Value, amount, expectedResult", [ | ||
("10", "10", "20"), | ||
("0", "10", "10"), | ||
("0", "-10", "-10"), | ||
("10", "0", "10"), | ||
("-1", "10", "9"), | ||
("-10", "10", "0"), | ||
("-192", "13", "-179"), | ||
("-192", "-13", "-205") | ||
]) | ||
def test_add_when_valid_parameters_should_succeed(initial_Value, amount, expectedResult): | ||
# Arrange | ||
context = ContextVariables() | ||
context["Amount"] = amount | ||
skill = MathSkill() | ||
|
||
# Act | ||
result = skill.add(initial_Value, context) | ||
|
||
# Assert | ||
assert result == expectedResult | ||
|
||
|
||
@pytest.mark.parametrize("initial_Value, amount, expectedResult", [ | ||
("10", "10", "0"), | ||
("0", "10", "-10"), | ||
("10", "0", "10"), | ||
("100", "-10", "110"), | ||
("100", "102", "-2"), | ||
("-1", "10", "-11"), | ||
("-10", "10", "-20"), | ||
("-192", "13", "-205") | ||
]) | ||
def test_subtract_when_valid_parameters_should_succeed(initial_Value, amount, expectedResult): | ||
# Arrange | ||
context = ContextVariables() | ||
context["Amount"] = amount | ||
skill = MathSkill() | ||
|
||
# Act | ||
result = skill.subtract(initial_Value, context) | ||
|
||
# Assert | ||
assert result == expectedResult | ||
|
||
|
||
@pytest.mark.parametrize("initial_Value", [ | ||
"$0", | ||
"one hundred", | ||
"20..,,2,1", | ||
".2,2.1", | ||
"0.1.0", | ||
"00-099", | ||
"¹²¹", | ||
"2²", | ||
"zero", | ||
"-100 units", | ||
"1 banana" | ||
]) | ||
def test_add_when_invalid_initial_value_should_throw(initial_Value): | ||
# Arrange | ||
context = ContextVariables() | ||
context["Amount"] = "1" | ||
skill = MathSkill() | ||
|
||
# Act | ||
with pytest.raises(ValueError) as exception: | ||
result = skill.add(initial_Value, context) | ||
|
||
# Assert | ||
assert str( | ||
exception.value) == f"Initial value provided is not in numeric format: {initial_Value}" | ||
assert exception.type == ValueError | ||
|
||
|
||
@pytest.mark.parametrize('amount', [ | ||
"$0", | ||
"one hundred", | ||
"20..,,2,1", | ||
".2,2.1", | ||
"0.1.0", | ||
"00-099", | ||
"¹²¹", | ||
"2²", | ||
"zero", | ||
"-100 units", | ||
"1 banana", | ||
]) | ||
def test_add_when_invalid_amount_should_throw(amount): | ||
# Arrange | ||
context = ContextVariables() | ||
context["Amount"] = amount | ||
skill = MathSkill() | ||
|
||
# Act / Assert | ||
with pytest.raises(ValueError) as exception: | ||
result = skill.add("1", context) | ||
|
||
assert str( | ||
exception.value) == f"Context amount provided is not in numeric format: {amount}" | ||
assert exception.type == ValueError | ||
|
||
|
||
@pytest.mark.parametrize("initial_value", [ | ||
"$0", | ||
"one hundred", | ||
"20..,,2,1", | ||
".2,2.1", | ||
"0.1.0", | ||
"00-099", | ||
"¹²¹", | ||
"2²", | ||
"zero", | ||
"-100 units", | ||
"1 banana", | ||
]) | ||
def test_subtract_when_invalid_initial_value_should_throw(initial_value): | ||
# Arrange | ||
context = ContextVariables() | ||
context["Amount"] = "1" | ||
skill = MathSkill() | ||
|
||
# Act / Assert | ||
with pytest.raises(ValueError) as exception: | ||
result = skill.subtract(initial_value, context) | ||
|
||
# Assert | ||
assert str( | ||
exception.value) == f"Initial value provided is not in numeric format: {initial_value}" | ||
assert exception.type == ValueError | ||
|
||
|
||
@pytest.mark.parametrize("amount", [ | ||
"$0", | ||
"one hundred", | ||
"20..,,2,1", | ||
".2,2.1", | ||
"0.1.0", | ||
"00-099", | ||
"¹²¹", | ||
"2²", | ||
"zero", | ||
"-100 units", | ||
"1 banana", | ||
]) | ||
def test_subtract_when_invalid_amount_should_throw(amount): | ||
# Arrange | ||
context = ContextVariables() | ||
context["Amount"] = amount | ||
skill = MathSkill() | ||
|
||
# Act / Assert | ||
with pytest.raises(ValueError) as exception: | ||
result = skill.subtract("1", context) | ||
|
||
# Assert | ||
assert str( | ||
exception.value) == f"Context amount provided is not in numeric format: {amount}" | ||
assert exception.type == ValueError |