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: Adding core HttpSkill (microsoft#504)
### Motivation and Context HttpSkill python version ### Description - Porting the HttpSkill from C# to Python. - Adding corresponding unit tests. Co-authored-by: Abby Harrison <[email protected]>
- Loading branch information
1 parent
7d11dac
commit cef3fc3
Showing
4 changed files
with
217 additions
and
2 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
from semantic_kernel.core_skills.file_io_skill import FileIOSkill | ||
from semantic_kernel.core_skills.http_skill import HttpSkill | ||
from semantic_kernel.core_skills.text_memory_skill import TextMemorySkill | ||
from semantic_kernel.core_skills.text_skill import TextSkill | ||
from semantic_kernel.core_skills.time_skill import TimeSkill | ||
|
||
__all__ = ["TextMemorySkill", "TextSkill", "FileIOSkill", "TimeSkill"] | ||
__all__ = ["TextMemorySkill", "TextSkill", "FileIOSkill", "TimeSkill", "HttpSkill"] |
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,106 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
import json | ||
|
||
import aiohttp | ||
|
||
from semantic_kernel.orchestration.sk_context import SKContext | ||
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter | ||
|
||
|
||
class HttpSkill: | ||
""" | ||
A skill that provides HTTP functionality. | ||
Usage: | ||
kernel.import_skill(HttpSkill(), "http") | ||
Examples: | ||
{{http.getAsync $url}} | ||
{{http.postAsync $url}} | ||
{{http.putAsync $url}} | ||
{{http.deleteAsync $url}} | ||
""" | ||
|
||
@sk_function(description="Makes a GET request to a uri", name="getAsync") | ||
async def get_async(self, url: str) -> str: | ||
""" | ||
Sends an HTTP GET request to the specified URI and returns | ||
the response body as a string. | ||
params: | ||
uri: The URI to send the request to. | ||
returns: | ||
The response body as a string. | ||
""" | ||
if not url: | ||
raise ValueError("url cannot be `None` or empty") | ||
|
||
async with aiohttp.ClientSession() as session: | ||
async with session.get(url, raise_for_status=True) as response: | ||
return await response.text() | ||
|
||
@sk_function(description="Makes a POST request to a uri", name="postAsync") | ||
@sk_function_context_parameter(name="body", description="The body of the request") | ||
async def post_async(self, url: str, context: SKContext) -> str: | ||
""" | ||
Sends an HTTP POST request to the specified URI and returns | ||
the response body as a string. | ||
params: | ||
url: The URI to send the request to. | ||
context: Contains the body of the request | ||
returns: | ||
The response body as a string. | ||
""" | ||
if not url: | ||
raise ValueError("url cannot be `None` or empty") | ||
|
||
_, body = context.variables.get("body") | ||
|
||
headers = {"Content-Type": "application/json"} | ||
data = json.dumps(body) | ||
async with aiohttp.ClientSession() as session: | ||
async with session.post( | ||
url, headers=headers, data=data, raise_for_status=True | ||
) as response: | ||
return await response.text() | ||
|
||
@sk_function(description="Makes a PUT request to a uri", name="putAsync") | ||
@sk_function_context_parameter(name="body", description="The body of the request") | ||
async def put_async(self, url: str, context: SKContext) -> str: | ||
""" | ||
Sends an HTTP PUT request to the specified URI and returns | ||
the response body as a string. | ||
params: | ||
url: The URI to send the request to. | ||
returns: | ||
The response body as a string. | ||
""" | ||
if not url: | ||
raise ValueError("url cannot be `None` or empty") | ||
|
||
_, body = context.variables.get("body") | ||
|
||
headers = {"Content-Type": "application/json"} | ||
data = json.dumps(body) | ||
async with aiohttp.ClientSession() as session: | ||
async with session.put( | ||
url, headers=headers, data=data, raise_for_status=True | ||
) as response: | ||
return await response.text() | ||
|
||
@sk_function(description="Makes a DELETE request to a uri", name="deleteAsync") | ||
async def delete_async(self, url: str) -> str: | ||
""" | ||
Sends an HTTP DELETE request to the specified URI and returns | ||
the response body as a string. | ||
params: | ||
uri: The URI to send the request to. | ||
returns: | ||
The response body as a string. | ||
""" | ||
if not url: | ||
raise ValueError("url cannot be `None` or empty") | ||
async with aiohttp.ClientSession() as session: | ||
async with session.delete(url, raise_for_status=True) as response: | ||
return await response.text() |
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,108 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
from unittest.mock import patch | ||
|
||
import pytest | ||
|
||
from semantic_kernel import Kernel | ||
from semantic_kernel.core_skills import HttpSkill | ||
from semantic_kernel.orchestration.context_variables import ContextVariables | ||
from semantic_kernel.orchestration.sk_context import SKContext | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_it_can_be_instantiated(): | ||
skill = HttpSkill() | ||
assert skill is not None | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_it_can_be_imported(): | ||
kernel = Kernel() | ||
skill = HttpSkill() | ||
assert kernel.import_skill(skill, "http") | ||
assert kernel.skills.has_native_function("http", "getAsync") | ||
assert kernel.skills.has_native_function("http", "postAsync") | ||
|
||
|
||
@patch("aiohttp.ClientSession.get") | ||
@pytest.mark.asyncio | ||
async def test_get(mock_get): | ||
mock_get.return_value.__aenter__.return_value.text.return_value = "Hello" | ||
mock_get.return_value.__aenter__.return_value.status = 200 | ||
|
||
skill = HttpSkill() | ||
response = await skill.get_async("https://example.org/get") | ||
assert response == "Hello" | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_get_none_url(): | ||
skill = HttpSkill() | ||
with pytest.raises(ValueError): | ||
await skill.get_async(None) | ||
|
||
|
||
@patch("aiohttp.ClientSession.post") | ||
@pytest.mark.asyncio | ||
async def test_post(mock_post): | ||
mock_post.return_value.__aenter__.return_value.text.return_value = "Hello World !" | ||
mock_post.return_value.__aenter__.return_value.status = 200 | ||
|
||
skill = HttpSkill() | ||
context_variables = ContextVariables() | ||
context_variables.set("body", "{message: 'Hello, world!'}") | ||
context = SKContext(context_variables, None, None, None) | ||
response = await skill.post_async("https://example.org/post", context) | ||
assert response == "Hello World !" | ||
|
||
|
||
@patch("aiohttp.ClientSession.post") | ||
@pytest.mark.asyncio | ||
async def test_post_nobody(mock_post): | ||
mock_post.return_value.__aenter__.return_value.text.return_value = "Hello World !" | ||
mock_post.return_value.__aenter__.return_value.status = 200 | ||
|
||
skill = HttpSkill() | ||
context_variables = ContextVariables() | ||
context = SKContext(context_variables, None, None, None) | ||
response = await skill.post_async("https://example.org/post", context) | ||
assert response == "Hello World !" | ||
|
||
|
||
@patch("aiohttp.ClientSession.put") | ||
@pytest.mark.asyncio | ||
async def test_put(mock_put): | ||
mock_put.return_value.__aenter__.return_value.text.return_value = "Hello World !" | ||
mock_put.return_value.__aenter__.return_value.status = 200 | ||
|
||
skill = HttpSkill() | ||
context_variables = ContextVariables() | ||
context_variables.set("body", "{message: 'Hello, world!'}") | ||
context = SKContext(context_variables, None, None, None) | ||
response = await skill.put_async("https://example.org/put", context) | ||
assert response == "Hello World !" | ||
|
||
|
||
@patch("aiohttp.ClientSession.put") | ||
@pytest.mark.asyncio | ||
async def test_put_nobody(mock_put): | ||
mock_put.return_value.__aenter__.return_value.text.return_value = "Hello World !" | ||
mock_put.return_value.__aenter__.return_value.status = 200 | ||
|
||
skill = HttpSkill() | ||
context_variables = ContextVariables() | ||
context = SKContext(context_variables, None, None, None) | ||
response = await skill.put_async("https://example.org/put", context) | ||
assert response == "Hello World !" | ||
|
||
|
||
@patch("aiohttp.ClientSession.delete") | ||
@pytest.mark.asyncio | ||
async def test_delete(mock_delete): | ||
mock_delete.return_value.__aenter__.return_value.text.return_value = "Hello World !" | ||
mock_delete.return_value.__aenter__.return_value.status = 200 | ||
|
||
skill = HttpSkill() | ||
response = await skill.delete_async("https://example.org/delete") | ||
assert response == "Hello World !" |