-
Notifications
You must be signed in to change notification settings - Fork 4k
Python: Add ACA Python Sessions (Code Interpreter) Core Plugin, samples, and tests #6158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
187b581
Add Python Sessions (Code Interpreter) Core Plugin, samples, and unit…
moonbox3 60f05ad
Black formatting, fix init file.
moonbox3 be0ba81
Merge branch 'main' into python_interpreter_plugin
moonbox3 b724f0a
Update env var to include pool management endpoint to make it more cl…
moonbox3 8692fd5
Merge branch 'python_interpreter_plugin' of github-msft:moonbox3/sema…
moonbox3 921851c
PR feedback
moonbox3 e17f7e7
Black formatting.
moonbox3 06f60d3
Merge branch 'main' into python_interpreter_plugin
moonbox3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 | ||
juliomenendez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 hidden or 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 hidden or 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. | ||
moonbox3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## 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 hidden or 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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.