-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Python: Add ACA Python Sessions (Code Interpreter) Core Plugin, sampl…
…es, and tests (#6158) ### Motivation and Context Adding a new core plugin to Semantic Kernel Python that leverages the Azure Container Apps Python Sessions Container. This container allows one, with the proper resource, to run Python in a safe, managed environment. <!-- 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 This PR introduces: - The Python Sessions (code interpreter) plugin to execute code, upload a file to the container, list files, and download files. - It includes a README.md with the steps to set up the ACA resource. - New samples to show use as a plugin and auto function calling - Unit tests <!-- 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 😄
- Loading branch information
Showing
11 changed files
with
951 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
125 changes: 125 additions & 0 deletions
125
.../samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py
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,125 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
import asyncio | ||
import datetime | ||
|
||
from azure.core.credentials import AccessToken | ||
from azure.core.exceptions import ClientAuthenticationError | ||
from azure.identity import DefaultAzureCredential | ||
|
||
from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior | ||
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import ( | ||
AzureChatPromptExecutionSettings, | ||
) | ||
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion | ||
from semantic_kernel.contents.chat_history import ChatHistory | ||
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import ( | ||
SessionsPythonTool, | ||
) | ||
from semantic_kernel.core_plugins.time_plugin import TimePlugin | ||
from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException | ||
from semantic_kernel.functions.kernel_arguments import KernelArguments | ||
from semantic_kernel.kernel import Kernel | ||
from semantic_kernel.utils.settings import ( | ||
azure_container_apps_settings_from_dot_env_as_dict, | ||
azure_openai_settings_from_dot_env_as_dict, | ||
) | ||
|
||
auth_token: AccessToken | None = None | ||
|
||
ACA_TOKEN_ENDPOINT = "https://acasessions.io/.default" | ||
|
||
|
||
async def auth_callback() -> str: | ||
"""Auth callback for the SessionsPythonTool. | ||
This is a sample auth callback that shows how to use Azure's DefaultAzureCredential | ||
to get an access token. | ||
""" | ||
global auth_token | ||
current_utc_timestamp = int(datetime.datetime.now(datetime.timezone.utc).timestamp()) | ||
|
||
if not auth_token or auth_token.expires_on < current_utc_timestamp: | ||
credential = DefaultAzureCredential() | ||
|
||
try: | ||
auth_token = credential.get_token(ACA_TOKEN_ENDPOINT) | ||
except ClientAuthenticationError as cae: | ||
err_messages = getattr(cae, "messages", []) | ||
raise FunctionExecutionException( | ||
f"Failed to retrieve the client auth token with messages: {' '.join(err_messages)}" | ||
) from cae | ||
|
||
return auth_token.token | ||
|
||
|
||
kernel = Kernel() | ||
|
||
service_id = "sessions-tool" | ||
chat_service = AzureChatCompletion( | ||
service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) | ||
) | ||
kernel.add_service(chat_service) | ||
|
||
sessions_tool = SessionsPythonTool( | ||
**azure_container_apps_settings_from_dot_env_as_dict(), | ||
auth_callback=auth_callback, | ||
) | ||
|
||
kernel.add_plugin(sessions_tool, "SessionsTool") | ||
kernel.add_plugin(TimePlugin(), "Time") | ||
|
||
chat_function = kernel.add_function( | ||
prompt="{{$chat_history}}{{$user_input}}", | ||
plugin_name="ChatBot", | ||
function_name="Chat", | ||
) | ||
|
||
req_settings = AzureChatPromptExecutionSettings(service_id=service_id, tool_choice="auto") | ||
|
||
filter = {"excluded_plugins": ["ChatBot"]} | ||
req_settings.function_call_behavior = FunctionCallBehavior.EnableFunctions(auto_invoke=True, filters=filter) | ||
|
||
arguments = KernelArguments(settings=req_settings) | ||
|
||
history = ChatHistory() | ||
|
||
|
||
async def chat() -> bool: | ||
try: | ||
user_input = input("User:> ") | ||
except KeyboardInterrupt: | ||
print("\n\nExiting chat...") | ||
return False | ||
except EOFError: | ||
print("\n\nExiting chat...") | ||
return False | ||
|
||
if user_input == "exit": | ||
print("\n\nExiting chat...") | ||
return False | ||
|
||
arguments["chat_history"] = history | ||
arguments["user_input"] = user_input | ||
answer = await kernel.invoke( | ||
function=chat_function, | ||
arguments=arguments, | ||
) | ||
print(f"Mosscap:> {answer}") | ||
history.add_user_message(user_input) | ||
history.add_assistant_message(str(answer)) | ||
return True | ||
|
||
|
||
async def main() -> None: | ||
print( | ||
"Welcome to the chat bot!\ | ||
\n Type 'exit' to exit.\ | ||
\n Try a Python code execution question to see the function calling in action (i.e. what is 1+1?)." | ||
) | ||
chatting = True | ||
while chatting: | ||
chatting = await chat() | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
70 changes: 70 additions & 0 deletions
70
python/samples/concepts/plugins/azure_python_code_interpreter.py
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,70 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
import asyncio | ||
import datetime | ||
|
||
from azure.core.credentials import AccessToken | ||
from azure.core.exceptions import ClientAuthenticationError | ||
from azure.identity import DefaultAzureCredential | ||
|
||
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion | ||
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import ( | ||
SessionsPythonTool, | ||
) | ||
from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException | ||
from semantic_kernel.kernel import Kernel | ||
from semantic_kernel.utils.settings import ( | ||
azure_container_apps_settings_from_dot_env_as_dict, | ||
azure_openai_settings_from_dot_env_as_dict, | ||
) | ||
|
||
auth_token: AccessToken | None = None | ||
|
||
ACA_TOKEN_ENDPOINT = "https://acasessions.io/.default" | ||
|
||
|
||
async def auth_callback() -> str: | ||
"""Auth callback for the SessionsPythonTool. | ||
This is a sample auth callback that shows how to use Azure's DefaultAzureCredential | ||
to get an access token. | ||
""" | ||
global auth_token | ||
current_utc_timestamp = int(datetime.datetime.now(datetime.timezone.utc).timestamp()) | ||
|
||
if not auth_token or auth_token.expires_on < current_utc_timestamp: | ||
credential = DefaultAzureCredential() | ||
|
||
try: | ||
auth_token = credential.get_token(ACA_TOKEN_ENDPOINT) | ||
except ClientAuthenticationError as cae: | ||
err_messages = getattr(cae, "messages", []) | ||
raise FunctionExecutionException( | ||
f"Failed to retrieve the client auth token with messages: {' '.join(err_messages)}" | ||
) from cae | ||
|
||
return auth_token.token | ||
|
||
|
||
async def main(): | ||
kernel = Kernel() | ||
|
||
service_id = "python-code-interpreter" | ||
chat_service = AzureChatCompletion( | ||
service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) | ||
) | ||
kernel.add_service(chat_service) | ||
|
||
python_code_interpreter = SessionsPythonTool( | ||
**azure_container_apps_settings_from_dot_env_as_dict(), auth_callback=auth_callback | ||
) | ||
|
||
sessions_tool = kernel.add_plugin(python_code_interpreter, "PythonCodeInterpreter") | ||
|
||
code = "import json\n\ndef add_numbers(a, b):\n return a + b\n\nargs = '{\"a\": 1, \"b\": 1}'\nargs_dict = json.loads(args)\nprint(add_numbers(args_dict['a'], args_dict['b']))" # noqa: E501 | ||
result = await kernel.invoke(sessions_tool["execute_code"], code=code) | ||
|
||
print(result) | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
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
132 changes: 132 additions & 0 deletions
132
python/semantic_kernel/core_plugins/sessions_python_tool/README.md
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,132 @@ | ||
# Getting Started with the Sessions Python Plugin | ||
|
||
## Authentication to ARM (management.azure.com) | ||
|
||
For any call to ARM (management.azure.com), use the access token retrieved from the below call: | ||
|
||
```az account get-access-token --resource <https://management.azure.com/>``` | ||
|
||
## Generate a Session Pool | ||
|
||
a. Call the following API to generate a Session Pool: | ||
|
||
```PUT <https://management.azure.com/subscriptions/{{SubscriptionId}}/resourceGroups/{{ResourceGroup}}/providers/Microsoft.App/sessionPools/{{SessionPoolName}}?api-version=2023-08-01-preview>``` | ||
|
||
Body properties: | ||
|
||
- location: Azure Region | ||
- properties: | ||
- poolManagementType: | ||
- Today there are two Pool Management Types supported: | ||
- "Manual" | ||
- In this model, the user will call generateSessions API which supports batch mode (to generate 100s of sessions in one API call, and then user is free to update/specialize the session as needed or execute code in the session) | ||
- "Dynamic" | ||
- In this mode, the pool management is handled by the platform. Currently, the dynamic mode is only implemented for Python code execution scenario, which has its own APIs to execute code. | ||
- maxConcurrentSessions: | ||
- Maximum number of active sessions allowed | ||
- name: | ||
- Name of the sessions pool | ||
- dynamicPoolConfiguration: Specifies the type of sessions generated by the platform | ||
- poolType: Type of images used for the pool | ||
- Valid values ["JupyterPython", "FunctionsPython"] | ||
- executionType: | ||
- Valid values ["Timed"] | ||
- coolDownPeriodSeconds: | ||
- Integer representing the maximum time allowed before the platform scales down the container | ||
- sessionPoolSecrets: Secrets associated with the Session Pool | ||
- name: Name of the secret | ||
- value: Secret Value | ||
|
||
Example Generation of Session Pool: | ||
|
||
```json | ||
{ | ||
"location": "koreacentral", | ||
"properties": { | ||
"poolManagementType": "Dynamic", | ||
"maxConcurrentSessions": 10, | ||
"name": "{{SessionPoolName}}", | ||
"dynamicPoolConfiguration": { | ||
"poolType": "JupyterPython", | ||
"executionType": "Timed", | ||
"coolDownPeriodInSecond": 310 | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Curl Example: | ||
|
||
```curl | ||
curl -X PUT "https://management.azure.com/subscriptions/{{SubscriptionId}}/resourceGroups/{{ResourceGroup}}/providers/Microsoft.App/sessionPools/{{SessionPoolName}}?api-version=2023-08-01-preview" \ | ||
-H "Content-Type: application/json" \ | ||
-H "Authorization: Bearer $token" \ | ||
-d '{"location": "koreacentral","properties": { "poolManagementType": "Dynamic", "maxConcurrentSessions": 10, "name": "{{SessionPoolName}}", "dynamicPoolConfiguration": { "poolType": "JupyterPython", "executionType": "Timed", "coolDownPeriodInSecond": 310 } } }' | ||
``` | ||
|
||
If all goes well, you should receive a 200 Status Code. The response will contain a `poolManagementEndpoint` which is required to configure the Python Plugin below. | ||
|
||
## Configuring the Python Plugin | ||
|
||
To successfully use the Python Plugin in Semantic Kernel, you must install the Poetry `azure` extras by running `poetry install -E azure`. | ||
|
||
Next, in the .env file, add the `poolManagementEndpoint` value from above to the variable `ACA_POOL_MANAGEMENT_ENDPOINT`. The `poolManagementEndpoint` should look something like: | ||
|
||
```html | ||
https://eastus.acasessions.io/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/sessionPools/{{sessionPool}}/python/execute | ||
``` | ||
|
||
It is possible to add the code interpreter plugin as follows: | ||
|
||
```python | ||
kernel = Kernel() | ||
|
||
service_id = "azure_oai" | ||
chat_service = AzureChatCompletion( | ||
service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) | ||
) | ||
kernel.add_service(chat_service) | ||
|
||
python_code_interpreter = SessionsPythonTool( | ||
**azure_container_apps_settings_from_dot_env_as_dict(), auth_callback=auth_callback | ||
) | ||
|
||
sessions_tool = kernel.add_plugin(python_code_interpreter, "PythonCodeInterpreter") | ||
|
||
code = "import json\n\ndef add_numbers(a, b):\n return a + b\n\nargs = '{\"a\": 1, \"b\": 1}'\nargs_dict = json.loads(args)\nprint(add_numbers(args_dict['a'], args_dict['b']))" | ||
result = await kernel.invoke(sessions_tool["execute_code"], code=code) | ||
|
||
print(result) | ||
``` | ||
|
||
Instead of hard-coding a well-formatted Python code string, you may use automatic function calling inside of SK and allow the model to form the Python and call the plugin. | ||
|
||
The authentication callback must return a valid token for the session pool. One possible way of doing this with a `DefaultAzureCredential` is as follows: | ||
|
||
```python | ||
async def auth_callback() -> str: | ||
"""Auth callback for the SessionsPythonTool. | ||
This is a sample auth callback that shows how to use Azure's DefaultAzureCredential | ||
to get an access token. | ||
""" | ||
global auth_token | ||
current_utc_timestamp = int(datetime.datetime.now(datetime.timezone.utc).timestamp()) | ||
|
||
if not auth_token or auth_token.expires_on < current_utc_timestamp: | ||
credential = DefaultAzureCredential() | ||
|
||
try: | ||
auth_token = credential.get_token(ACA_TOKEN_ENDPOINT) | ||
except ClientAuthenticationError as cae: | ||
err_messages = getattr(cae, "messages", []) | ||
raise FunctionExecutionException( | ||
f"Failed to retrieve the client auth token with messages: {' '.join(err_messages)}" | ||
) from cae | ||
|
||
return auth_token.token | ||
``` | ||
|
||
Currently, there are two concept examples that show this plugin in more detail: | ||
|
||
- [Plugin example](../../../samples/concepts/plugins/azure_python_code_interpreter.py): shows the basic usage of calling the code execute function on the plugin. | ||
- [Function Calling example](../../../samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py): shows a simple chat application that leverages the Python code interpreter plugin for function calling. |
10 changes: 10 additions & 0 deletions
10
python/semantic_kernel/core_plugins/sessions_python_tool/__init__.py
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,10 @@ | ||
# Copyright (c) Microsoft. All rights reserved. | ||
|
||
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import ( | ||
SessionsPythonTool, | ||
) | ||
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_settings import ( | ||
SessionsPythonSettings, | ||
) | ||
|
||
__all__ = ["SessionsPythonTool", "SessionsPythonSettings"] |
Oops, something went wrong.