Skip to content

Commit 1a73963

Browse files
authored
Add a custom code interpreter code sample (microsoft-foundry#398)
1 parent b139fc2 commit 1a73963

8 files changed

Lines changed: 1563 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AZURE_AI_PROJECT_ENDPOINT=
2+
AZURE_AI_CONNECTION_ID=
3+
AZURE_AI_MODEL_DEPLOYMENT_NAME=
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.venv/
2+
.env
3+
__pycache__/
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Custom Code Interpreter with Session Pool MCP server
2+
3+
This provides example Bicep code for setting up a Container Apps dynamic session pool
4+
with a custom code interpreter image, as well as Python client code demonstrating
5+
how to use it with a Foundry Hosted Agent.
6+
7+
You will need the following installed to run the sample code:
8+
9+
- The `az` CLI
10+
- Python3
11+
- A Python3 package manager like `uv` or `pip` + `venv`
12+
- If you are using `pip`, make sure `ensurepip` is installed. On Debian/Ubuntu
13+
systems, this would mean running `apt install python3.12-venv`.
14+
15+
## Running code sample
16+
17+
### Enable MCP server for dynamic sessions
18+
19+
This is required to enable the preview feature.
20+
21+
```console
22+
az feature register --namespace Microsoft.App --name SessionPoolsSupportMCP
23+
az provider register -n Microsoft.App
24+
```
25+
26+
### Create a dynamic session pool with a code interpreter image
27+
28+
Using the `az` CLI, deploy with the provided Bicep template file:
29+
30+
```console
31+
az deployment group create \
32+
--name custom-code-interpreter \
33+
--subscription <your_subscription> \
34+
--resource-group <your_resource_group> \
35+
--template-file ./infra.bicep
36+
```
37+
38+
> [!NOTE] This can take a while! Allocating the dynamic session pool
39+
> can take up to 1 hour, depending on the number of standby instances
40+
> requested.
41+
42+
### Use the custom code interpreter in an agent
43+
44+
Copy the [`.env.sample`](./.env.sample) file to `.env` and fill in the values with
45+
the output of the above deployment, which you can find in the Web Portal under the
46+
resource group.
47+
48+
Finally, install Python dependencies and run the script:
49+
50+
```console
51+
# Using uv
52+
53+
uv sync
54+
uv run ./main.py
55+
56+
# Using pip
57+
58+
python3 -m venv .venv
59+
./.venv/bin/pip3 install -r requirements.txt
60+
./.venv/bin/python3 ./main.py
61+
```
62+
63+
## Limitations
64+
65+
File input/output and use of file stores are not directly supported in APIs, so you must use URLs (such as data URLs for small files and Azure Blob Service SAS URLs for large ones) to get data in and out.
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
@description('Suffix for resource names to ensure uniqueness')
2+
@minLength(3)
3+
param suffix string = uniqueString(resourceGroup().id)
4+
5+
@description('Container Apps environment name')
6+
@minLength(3)
7+
param environmentName string = 'aca-env-${suffix}'
8+
9+
@description('Session pool name')
10+
@minLength(3)
11+
param sessionPoolName string = 'sp-${suffix}'
12+
13+
@description('The amount of CPU to provide to each container instance, in vCPU counts')
14+
@minValue(1)
15+
@maxValue(16)
16+
param cpu int = 1
17+
18+
@description('The amount of RAM to provide to each container instance, in GiB')
19+
@minValue(1)
20+
@maxValue(16)
21+
param memory int = 2
22+
23+
@description('Location of all ACA resources.')
24+
@allowed([
25+
'eastus'
26+
'swedencentral'
27+
'northeurope'
28+
])
29+
param location string = 'swedencentral'
30+
31+
@description('Use managed identity for deployment script principal')
32+
param useManagedIdentity bool = true
33+
34+
@description('An image that implements the code interpreter HTTP API')
35+
param image string = 'mcr.microsoft.com/k8se/services/codeinterpreter:0.9.18-python3.12'
36+
37+
@description('Model deployment name')
38+
param modelDeploymentName string = 'my-gpt-4o-mini'
39+
40+
@description('Model to deploy')
41+
param modelName string = 'gpt-4o-mini'
42+
43+
resource environment 'Microsoft.App/managedEnvironments@2025-10-02-preview' = {
44+
name: environmentName
45+
location: location
46+
properties: {
47+
workloadProfiles: [
48+
{
49+
name: 'Consumption'
50+
workloadProfileType: 'Consumption'
51+
}
52+
]
53+
}
54+
}
55+
56+
resource sessionPool 'Microsoft.App/sessionPools@2025-10-02-preview' = {
57+
name: sessionPoolName
58+
location: location
59+
properties: {
60+
environmentId: environment.id
61+
poolManagementType: 'Dynamic'
62+
containerType: 'CustomContainer'
63+
scaleConfiguration: {
64+
maxConcurrentSessions: 10
65+
readySessionInstances: 5
66+
}
67+
dynamicPoolConfiguration: {
68+
lifecycleConfiguration: {
69+
cooldownPeriodInSeconds: 600
70+
lifecycleType: 'Timed'
71+
}
72+
}
73+
customContainerTemplate: {
74+
containers: [
75+
{
76+
name: 'jupyterpython'
77+
image: image
78+
env: [
79+
{
80+
name: 'SYS_RUNTIME_SANDBOX'
81+
value: 'AzureContainerApps-DynamicSessions'
82+
}
83+
{
84+
name: 'AZURE_CODE_EXEC_ENV'
85+
value: 'AzureContainerApps-DynamicSessions-Py3.12'
86+
}
87+
{
88+
name: 'AZURECONTAINERAPPS_SESSIONS_SANDBOX_VERSION'
89+
value: '7758'
90+
}
91+
{
92+
name: 'JUPYTER_TOKEN'
93+
value: 'AzureContainerApps-DynamicSessions'
94+
}
95+
]
96+
resources: {
97+
cpu: cpu
98+
memory: '${memory}Gi'
99+
}
100+
probes: [
101+
{
102+
type: 'Liveness'
103+
httpGet: {
104+
path: '/health'
105+
port: 6000
106+
}
107+
failureThreshold: 4
108+
}
109+
{
110+
type: 'Startup'
111+
httpGet: {
112+
path: '/health'
113+
port: 6000
114+
}
115+
failureThreshold: 30
116+
periodSeconds: 2
117+
}
118+
]
119+
}
120+
]
121+
ingress: {
122+
targetPort: 6000
123+
}
124+
}
125+
mcpServerSettings: {
126+
isMcpServerEnabled: true
127+
}
128+
sessionNetworkConfiguration: {
129+
status: 'egressEnabled'
130+
}
131+
}
132+
}
133+
134+
resource scriptPrincipal 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = if (useManagedIdentity){
135+
name: 'deployScriptIdentity-${suffix}'
136+
location: location
137+
}
138+
139+
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useManagedIdentity) {
140+
name: guid(scriptPrincipal!.id, 'apps-sessionpool-contributor')
141+
scope: resourceGroup()
142+
properties: {
143+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7669afb-68b2-44b4-9c5f-6d2a47fddda0') // Container Apps SessionPools Contributor
144+
principalId: scriptPrincipal!.properties.principalId
145+
principalType: 'ServicePrincipal'
146+
}
147+
}
148+
149+
resource deployScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
150+
name: 'getmcpkey-${suffix}'
151+
location: location
152+
kind: 'AzureCLI'
153+
identity: useManagedIdentity ? {
154+
type: 'UserAssigned'
155+
userAssignedIdentities: {
156+
'${scriptPrincipal!.id}': {}
157+
}
158+
} : null
159+
properties: {
160+
azCliVersion: '2.77.0'
161+
scriptContent: '''
162+
az rest --method post --url "$SESSION_POOL_ID/fetchMCPServerCredentials?api-version=2025-02-02-preview" | jq -c '{"key": .apiKey}' > $AZ_SCRIPTS_OUTPUT_PATH
163+
'''
164+
timeout: 'PT30M'
165+
retentionInterval: 'P1D'
166+
cleanupPreference: 'OnSuccess'
167+
environmentVariables: [
168+
{
169+
name: 'SESSION_POOL_ID'
170+
value: sessionPool.id
171+
}
172+
]
173+
}
174+
}
175+
176+
resource aiAccount 'Microsoft.CognitiveServices/accounts@2025-10-01-preview' = {
177+
name: 'aia-${suffix}'
178+
location: location
179+
kind: 'AIServices'
180+
sku: {
181+
name: 'S0'
182+
}
183+
properties: {
184+
customSubDomainName: 'myaiaccount-${suffix}'
185+
allowProjectManagement: true
186+
}
187+
188+
resource project 'projects' = {
189+
name: 'aip-${suffix}s'
190+
properties: {
191+
description: 'This is my AI project.'
192+
}
193+
194+
resource mcpConn 'connections' = {
195+
name: 'aic-${suffix}'
196+
properties: {
197+
authType: 'CustomKeys'
198+
category: 'RemoteTool'
199+
credentials: {
200+
keys: {
201+
'x-ms-apikey': deployScript.properties.outputs.key
202+
}
203+
}
204+
target: sessionPool.properties.mcpServerSettings.mcpServerEndpoint
205+
}
206+
}
207+
}
208+
209+
resource model 'deployments' = {
210+
name: modelDeploymentName
211+
sku: {
212+
name: 'GlobalStandard'
213+
capacity: 1
214+
}
215+
properties: {
216+
model: {
217+
format: 'OpenAI'
218+
name: modelName
219+
}
220+
}
221+
}
222+
}
223+
224+
@description('Outputs the ID of the project connection for the Code Interpreter MCP Tool')
225+
output AZURE_AI_CONNECTION_ID string = aiAccount::project::mcpConn.id
226+
227+
@description('Model deployment name')
228+
output AZURE_AI_MODEL_DEPLOYMENT_NAME string = aiAccount::model.name
229+
230+
@description('AI Project Endpoint')
231+
output AZURE_AI_PROJECT_ENDPOINT string = aiAccount::project.properties.endpoints['AI Foundry API']
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import os
2+
3+
import dotenv
4+
from azure.ai.projects import AIProjectClient
5+
from azure.ai.projects.models import PromptAgentDefinition, MCPTool
6+
from azure.identity import DefaultAzureCredential
7+
8+
dotenv.load_dotenv()
9+
10+
project_client = AIProjectClient(
11+
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
12+
credential=DefaultAzureCredential(),
13+
)
14+
openai_client = project_client.get_openai_client()
15+
16+
tools = [
17+
MCPTool(
18+
# This is just a placeholder. Connection details are in
19+
# the project connection referenced by `project_connection_id`.
20+
server_url="https://localhost",
21+
server_label="python_tool",
22+
require_approval="never",
23+
allowed_tools=[
24+
"launchShell",
25+
"runPythonCodeInRemoteEnvironment",
26+
],
27+
project_connection_id=os.environ["AZURE_AI_CONNECTION_ID"],
28+
),
29+
]
30+
31+
EXAMPLE_DATA_FILE_URL = "https://raw.githubusercontent.com/Azure-Samples/azureai-samples/refs/heads/main/scenarios/Agents/data/nifty_500_quarterly_results.csv"
32+
33+
with project_client:
34+
agent = project_client.agents.create_version(
35+
agent_name="MyAgent",
36+
definition=PromptAgentDefinition(
37+
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
38+
instructions="""\
39+
You are a helpful agent that can use a Python code interpreter to assist users. Use the `python_tool` MCP
40+
server to perform any calculations or numerical analyses. ALWAYS call the `launchShell` tool first before
41+
calling the `runPythonCodeInRemoteEnvironment` tool. If you need to provide any non-text data to the user,
42+
always print a data URI with the contents. NEVER provide a path to a file in the remote environment to the user.
43+
""",
44+
temperature=0,
45+
tools=tools,
46+
),
47+
)
48+
print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})")
49+
50+
# Use the agent to analyze a CSV file and produce a histogram
51+
response = openai_client.responses.create(
52+
input=f"Please analyze the CSV file at {EXAMPLE_DATA_FILE_URL}. Could you please create bar chart in the TRANSPORTATION sector for the operating profit and provide a file to me?",
53+
extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
54+
)
55+
print(f"[Response {response.id}]: {response.output_text}")
56+
57+
# Clean up resources by deleting the agent version
58+
# This prevents accumulation of unused agent versions in your project
59+
project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
60+
print("Agent deleted")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "code-interpreter-custom"
3+
version = "0.1.0"
4+
description = "Basic example for using custom code interpreter session pools."
5+
readme = "README.md"
6+
requires-python = ">=3.12"
7+
dependencies = [
8+
"aiohttp>=3.13.2",
9+
"azure-ai-projects==2.0.0b2",
10+
"azure-identity>=1.25.1",
11+
"dotenv>=0.9.9",
12+
"openai>=2.8.1",
13+
]

0 commit comments

Comments
 (0)