diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentToolboxWithGuardrail.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentToolboxWithGuardrail.ts new file mode 100644 index 000000000000..4543c959247b --- /dev/null +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentToolboxWithGuardrail.ts @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to create an agent that uses a toolbox with a guardrail (RAI policy). + * The toolbox is configured with a web_search tool and an RAI policy that filters responses. + * + * @summary Create an agent with a toolbox that has a guardrail (RAI policy) applied. + * + * @azsdk-weight 100 + */ + +import { DefaultAzureCredential } from "@azure/identity"; +import { AIProjectClient, type MCPTool } from "@azure/ai-projects"; +import "dotenv/config"; + +const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"] || ""; +const deploymentName = process.env["FOUNDRY_MODEL_NAME"] || ""; +const raiPolicyName = + process.env["RAI_POLICY_NAME"] || + "/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//raiPolicies/"; + +export async function main(): Promise { + const credential = new DefaultAzureCredential(); + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); + + console.log("Creating toolbox with guardrail (RAI policy)..."); + const toolboxVersion = await project.beta.toolboxes.createVersion( + "my-toolbox", + [{ type: "web_search" }], + { + description: "Toolbox with guardrail", + policies: { + rai_config: { + rai_policy_name: raiPolicyName, + }, + }, + }, + ); + console.log(`Toolbox version created: ${toolboxVersion.version}`); + + console.log("\nCreating agent with deferred MCP tool and tool_search..."); + const token = (await credential.getToken("https://ai.azure.com/.default")).token; + const toolboxMcpUrl = `${projectEndpoint}/toolboxes/my-toolbox/versions/${toolboxVersion.version}/mcp?api-version=v1`; + + const agent = await project.agents.createVersion("MyGuardrailAgent", { + kind: "prompt", + model: deploymentName, + instructions: "You are a helpful assistant. Use web search when needed to answer questions.", + tools: [ + { + type: "mcp", + server_label: "my-toolbox", + server_url: toolboxMcpUrl, + authorization: token, + headers: { "Foundry-Features": "Toolboxes=V1Preview" }, + require_approval: "never", + } as MCPTool, + ], + }); + console.log(`Agent created (id: ${agent.id}, name: ${agent.name}, version: ${agent.version})`); + + console.log("\nSending request to the agent..."); + try { + const response = await openAIClient.responses.create( + { + input: "What are the latest developments in responsible AI?", + }, + { + body: { + agent_reference: { name: agent.name, type: "agent_reference" }, + }, + }, + ); + console.log(`\nResponse output: ${response.output_text}`); + } catch (e: any) { + if (e.status === 400 && e.message?.includes("blocked by policy: RAI")) { + console.log("\nThe RAI guardrail blocked the tool call as expected."); + console.log(`Details: ${e.message}`); + } else { + throw e; + } + } + + console.log("\nCleaning up resources..."); + await project.agents.deleteVersion(agent.name, agent.version); + await project.beta.toolboxes.deleteVersion("my-toolbox", toolboxVersion.version); + console.log("Agent and toolbox deleted"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); diff --git a/sdk/ai/ai-projects/samples-dev/skills/skillWithToolbox.ts b/sdk/ai/ai-projects/samples-dev/skills/skillWithToolbox.ts new file mode 100644 index 000000000000..ecbbebb58247 --- /dev/null +++ b/sdk/ai/ai-projects/samples-dev/skills/skillWithToolbox.ts @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to expose a Skill to a Prompt Agent via a + * Toolbox, using the AIProjectClient and the OpenAI-compatible client. + * + * It creates a Skill with inline content describing how to compute shipping + * cost, then creates a Toolbox version that references the skill. A Prompt + * Agent is created with an `MCPTool` pointed at the toolbox's versioned + * `/mcp` endpoint. The skill's instructions are injected into the agent's + * context, so when asked a shipping-cost question the agent answers directly + * using the skill's formula. + * + * Skills and Toolboxes are currently preview features. In the JS SDK, you + * access these operations via `project.beta.skills` and + * `project.beta.toolboxes`. + * + * @summary Demonstrates adding a skill to a toolbox and invoking it via a Prompt Agent. + * + * @azsdk-weight 100 + */ + +import type { MCPTool, ToolboxSkillReference } from "@azure/ai-projects"; +import { AIProjectClient, RestError } from "@azure/ai-projects"; +import { DefaultAzureCredential } from "@azure/identity"; +import "dotenv/config"; + +const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"] || ""; +const deploymentName = process.env["FOUNDRY_MODEL_NAME"] || ""; + +const SKILL_NAME = "shipping-cost-skill"; +const TOOLBOX_NAME = "toolbox_with_skill"; +const AGENT_NAME = "SkillToolboxAgent"; + +export async function main(): Promise { + const credential = new DefaultAzureCredential(); + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); + + // --- Clean up any prior runs --- + try { + await project.beta.toolboxes.delete(TOOLBOX_NAME); + } catch (e) { + if (!(e instanceof RestError && e.statusCode === 404)) throw e; + } + try { + await project.beta.skills.delete(SKILL_NAME); + } catch (e) { + if (!(e instanceof RestError && e.statusCode === 404)) throw e; + } + + // --- 1. Create a skill --- + const skill = await project.beta.skills.create(SKILL_NAME, { + inlineContent: { + description: "Compute shipping cost for a package given weight and destination.", + instructions: + "You are a shipping cost calculator. When asked to compute " + + "shipping cost, use this formula: cost (USD) = 5 + 2 * weight_kg " + + "for domestic destinations, and cost (USD) = 15 + 4 * weight_kg " + + "for international destinations. Always state the formula you used.", + metadata: { revision: "1" }, + }, + }); + console.log(`Created skill: ${skill.name} version=${skill.version}`); + + // --- 2. Create a toolbox version with toolbox_search_preview and the skill --- + const skillRef: ToolboxSkillReference = { + type: "skill_reference", + name: skill.name, + version: skill.version, + }; + + const toolboxVersion = await project.beta.toolboxes.createVersion( + TOOLBOX_NAME, + [{ type: "toolbox_search_preview" }], + { + description: "Toolbox exposing a shipping-cost skill.", + skills: [skillRef], + }, + ); + console.log(`Created toolbox: ${toolboxVersion.name} version=${toolboxVersion.version}`); + + // --- 3. Build the MCP tool reference to the toolbox endpoint --- + const toolboxMcpUrl = `${projectEndpoint}/toolboxes/${TOOLBOX_NAME}/versions/${toolboxVersion.version}/mcp?api-version=v1`; + const token = (await credential.getToken("https://ai.azure.com/.default"))!.token; + + const toolboxMcpTool: MCPTool = { + type: "mcp", + server_label: "skill-toolbox", + server_url: toolboxMcpUrl, + authorization: token, + headers: { "Foundry-Features": "Toolboxes=V1Preview" }, + require_approval: "never", + }; + + // --- 4. Create a prompt agent with the toolbox MCP tool --- + const agent = await project.agents.createVersion(AGENT_NAME, { + kind: "prompt", + model: deploymentName, + instructions: + "Answer the user using the `shipping-cost-skill` instructions " + + "available in your context. Do not call `tool_search`; the " + + "skill rules are already part of your knowledge. Apply the " + + "skill's formula exactly as given and state the formula in " + + "your answer.", + temperature: 0, + tools: [toolboxMcpTool], + }); + console.log(`Agent created (id=${agent.id}, name=${agent.name}, version=${agent.version})`); + + // --- 5. Send a query --- + const userInput = "Compute the shipping cost for a 3 kg package shipped domestically."; + console.log(`User: ${userInput}`); + + const response = await openAIClient.responses.create( + { input: userInput }, + { + body: { + agent_reference: { name: agent.name, type: "agent_reference" }, + }, + }, + ); + + for (const item of response.output) { + if (item.type === "mcp_list_tools") { + console.log( + `mcp_list_tools server_label=${item.server_label} tools=${(item.tools || []).map((t: any) => t.name)}`, + ); + } else if (item.type === "mcp_call") { + console.log( + `mcp_call server_label=${item.server_label} name=${item.name} error=${item.error}`, + ); + if ("output" in item && item.output) { + console.log(` output: ${item.output}`); + } + } else if (item.type === "mcp_approval_request") { + console.log(`mcp_approval_request server_label=${item.server_label} name=${item.name}`); + } else { + console.log(`output item type=${item.type}`); + } + } + + console.log(`Response: ${response.output_text}`); + + // --- 6. Clean up --- + await project.beta.toolboxes.delete(TOOLBOX_NAME); + console.log("Toolbox deleted"); + await project.beta.skills.delete(SKILL_NAME); + console.log("Skill deleted"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); diff --git a/sdk/ai/ai-projects/samples/v2/javascript/README.md b/sdk/ai/ai-projects/samples/v2/javascript/README.md index aba173e1f825..4f4f6471e435 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/README.md +++ b/sdk/ai/ai-projects/samples/v2/javascript/README.md @@ -35,6 +35,7 @@ These sample programs show how to use the JavaScript client libraries for Azure | [agents/tools/agentOpenApiConnectionAuth.js][agents_tools_agentopenapiconnectionauth] | Demonstrates how to create an OpenAPI-enabled agent that uses a project connection for authentication and stream responses that include tool invocation details. | | [agents/tools/agentSharepoint.js][agents_tools_agentsharepoint] | This sample demonstrates how to create an agent with SharePoint tool capabilities, search SharePoint content, and process streaming responses with citations. | | [agents/tools/agentToolboxSearch.js][agents_tools_agenttoolboxsearch] | Create an agent with tool_search to dynamically discover deferred MCP tools. | +| [agents/tools/agentToolboxWithGuardrail.js][agents_tools_agenttoolboxwithguardrail] | Create an agent with a toolbox that has a guardrail (RAI policy) applied. | | [agents/tools/agentWebSearch.js][agents_tools_agentwebsearch] | This sample demonstrates how to create an agent with web search capabilities, send a query to search the web, and clean up resources. | | [agents/tools/agentWebSearchCustomSearch.js][agents_tools_agentwebsearchcustomsearch] | This sample demonstrates how to create an agent with a WebSearchTool configured with Bing Custom Search, send a query with streaming, and clean up resources. | | [agents/tools/agentWebSearchPreview.js][agents_tools_agentwebsearchpreview] | This sample demonstrates how to create an agent with the WebSearchPreviewTool, send a query to search the web with streaming, and clean up resources. | @@ -42,6 +43,7 @@ These sample programs show how to use the JavaScript client libraries for Azure | [responses/responseBasic.js][responses_responsebasic] | This sample demonstrates how to create responses with and without conversation context. | | [responses/responseBasicWithoutAIProjectClient.js][responses_responsebasicwithoutaiprojectclient] | This sample demonstrates how to create an OpenAI client directly with Azure credentials and use it for a basic responses operation. See also https://platform.openai.com/docs/api-reference/responses/create | | [responses/responseStream.js][responses_responsestream] | This sample demonstrates how to create a non-streaming response and then use streaming for a follow-up response with conversation context. | +| [skills/skillWithToolbox.js][skills_skillwithtoolbox] | Demonstrates adding a skill to a toolbox and invoking it via a Prompt Agent. | | [agents/agentFunctionTool.js][agents_agentfunctiontool] | Demonstrates how to create an agent with function tools, handle function calls, and provide function results to get the final response. | | [agents/hostedAgents/sessionLogStream.js][agents_hostedagents_sessionlogstream] | Demonstrates streaming session logs from a hosted agent. | | [agents/hostedAgents/createHostedAgentFromCode.js][agents_hostedagents_createhostedagentfromcode] | Upload code to an existing hosted agent, poll until active, then download and verify. | @@ -175,6 +177,7 @@ Take a look at our [API Documentation][apiref] for more information about the AP [agents_tools_agentopenapiconnectionauth]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentOpenApiConnectionAuth.js [agents_tools_agentsharepoint]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentSharepoint.js [agents_tools_agenttoolboxsearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentToolboxSearch.js +[agents_tools_agenttoolboxwithguardrail]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentToolboxWithGuardrail.js [agents_tools_agentwebsearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentWebSearch.js [agents_tools_agentwebsearchcustomsearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentWebSearchCustomSearch.js [agents_tools_agentwebsearchpreview]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentWebSearchPreview.js @@ -182,6 +185,7 @@ Take a look at our [API Documentation][apiref] for more information about the AP [responses_responsebasic]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/responses/responseBasic.js [responses_responsebasicwithoutaiprojectclient]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/responses/responseBasicWithoutAIProjectClient.js [responses_responsestream]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/responses/responseStream.js +[skills_skillwithtoolbox]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/skills/skillWithToolbox.js [agents_agentfunctiontool]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/agentFunctionTool.js [agents_hostedagents_sessionlogstream]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/sessionLogStream.js [agents_hostedagents_createhostedagentfromcode]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/createHostedAgentFromCode.js diff --git a/sdk/ai/ai-projects/samples/v2/javascript/agents/betaAgents.js b/sdk/ai/ai-projects/samples/v2/javascript/agents/betaAgents.js index 270a95c3b8a0..a2d3a13ce101 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/agents/betaAgents.js +++ b/sdk/ai/ai-projects/samples/v2/javascript/agents/betaAgents.js @@ -33,8 +33,8 @@ async function main() { kind: "hosted", cpu: "0.5", memory: "1Gi", - image, - container_protocol_versions: [{ protocol: "responses", version: "v1" }], + container_configuration: { image }, + protocol_versions: [{ protocol: "responses", version: "v1" }], }, { foundryFeatures: "HostedAgents=V1Preview", @@ -95,14 +95,19 @@ async function main() { ); console.log(`Uploaded file: ${uploadResult.path} (${uploadResult.bytes_written} bytes)`); - // List files in the session sandbox - const listing = await project.beta.agents.listSessionFiles( - agentName, - session.agent_session_id, - "/sandbox", - ); - console.log(`Files in /sandbox:`); - for (const entry of listing.entries) { + // List files in the session sandbox (with pagination monitoring) + const files = []; + let pageCount = 0; + const pager = project.beta.agents.listSessionFiles(agentName, session.agent_session_id, { + path: "/sandbox", + }); + for await (const page of pager.byPage()) { + pageCount++; + console.log(` Page ${pageCount}: ${page.length} entries`); + files.push(...page); + } + console.log(`Files in /sandbox (${files.length} total across ${pageCount} page(s)):`); + for (const entry of files) { console.log( ` - ${entry.name} (${entry.is_directory ? "directory" : "file"})`, JSON.stringify(entry), diff --git a/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/createHostedAgentFromCode.js b/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/createHostedAgentFromCode.js index 730cc7ded1a9..048ee7859f13 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/createHostedAgentFromCode.js +++ b/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/createHostedAgentFromCode.js @@ -71,7 +71,7 @@ async function main() { code: { contents: codeZip, contentType: "application/zip", filename: "code.zip" }, }; - const created = await project.beta.agents.createAgentVersionFromCode( + const created = await project.beta.agents.createVersionFromCode( agentName, codeZipSha256, content, diff --git a/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/sessionLogStream.js b/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/sessionLogStream.js index a11a099559a5..dfee3a17e8c5 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/sessionLogStream.js +++ b/sdk/ai/ai-projects/samples/v2/javascript/agents/hostedAgents/sessionLogStream.js @@ -70,8 +70,8 @@ async function main() { kind: "hosted", cpu: "0.5", memory: "1Gi", - image, - container_protocol_versions: [{ protocol: "responses", version: "1.0.0" }], + container_configuration: { image }, + protocol_versions: [{ protocol: "responses", version: "1.0.0" }], }, { foundryFeatures: "HostedAgents=V1Preview", diff --git a/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentToolboxWithGuardrail.js b/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentToolboxWithGuardrail.js new file mode 100644 index 000000000000..664e00736450 --- /dev/null +++ b/sdk/ai/ai-projects/samples/v2/javascript/agents/tools/agentToolboxWithGuardrail.js @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to create an agent that uses a toolbox with a guardrail (RAI policy). + * The toolbox is configured with a web_search tool and an RAI policy that filters responses. + * + * @summary Create an agent with a toolbox that has a guardrail (RAI policy) applied. + */ + +const { DefaultAzureCredential } = require("@azure/identity"); +const { AIProjectClient } = require("@azure/ai-projects"); +require("dotenv/config"); + +const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"] || ""; +const deploymentName = process.env["FOUNDRY_MODEL_NAME"] || ""; +const raiPolicyName = + process.env["RAI_POLICY_NAME"] || + "/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//raiPolicies/"; + +async function main() { + const credential = new DefaultAzureCredential(); + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); + + console.log("Creating toolbox with guardrail (RAI policy)..."); + const toolboxVersion = await project.beta.toolboxes.createVersion( + "my-toolbox", + [{ type: "web_search" }], + { + description: "Toolbox with guardrail", + policies: { + rai_config: { + rai_policy_name: raiPolicyName, + }, + }, + }, + ); + console.log(`Toolbox version created: ${toolboxVersion.version}`); + + console.log("\nCreating agent with deferred MCP tool and tool_search..."); + const token = (await credential.getToken("https://ai.azure.com/.default")).token; + const toolboxMcpUrl = `${projectEndpoint}/toolboxes/my-toolbox/versions/${toolboxVersion.version}/mcp?api-version=v1`; + + const agent = await project.agents.createVersion("MyGuardrailAgent", { + kind: "prompt", + model: deploymentName, + instructions: "You are a helpful assistant. Use web search when needed to answer questions.", + tools: [ + { + type: "mcp", + server_label: "my-toolbox", + server_url: toolboxMcpUrl, + authorization: token, + headers: { "Foundry-Features": "Toolboxes=V1Preview" }, + require_approval: "never", + }, + ], + }); + console.log(`Agent created (id: ${agent.id}, name: ${agent.name}, version: ${agent.version})`); + + console.log("\nSending request to the agent..."); + try { + const response = await openAIClient.responses.create( + { + input: "What are the latest developments in responsible AI?", + }, + { + body: { + agent_reference: { name: agent.name, type: "agent_reference" }, + }, + }, + ); + console.log(`\nResponse output: ${response.output_text}`); + } catch (e) { + if (e.status === 400 && e.message?.includes("blocked by policy: RAI")) { + console.log("\nThe RAI guardrail blocked the tool call as expected."); + console.log(`Details: ${e.message}`); + } else { + throw e; + } + } + + console.log("\nCleaning up resources..."); + await project.agents.deleteVersion(agent.name, agent.version); + await project.beta.toolboxes.deleteVersion("my-toolbox", toolboxVersion.version); + console.log("Agent and toolbox deleted"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); + +module.exports = { main }; diff --git a/sdk/ai/ai-projects/samples/v2/javascript/routine/routineBasics.js b/sdk/ai/ai-projects/samples/v2/javascript/routine/routineBasics.js index 88670a719fe0..82db7bb660ac 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/routine/routineBasics.js +++ b/sdk/ai/ai-projects/samples/v2/javascript/routine/routineBasics.js @@ -38,14 +38,13 @@ async function main() { console.log(`Agent created: ${agent.name} (version: ${agent.version})`); // Create or update a routine - const routine = await project.beta.routines.createOrUpdate( - routineName, - { daily: { type: "schedule", cron_expression: "0 9 * * *", time_zone: "UTC" } }, - { type: "invoke_agent_responses_api", agent_name: agentName }, - { - description: "A routine that invokes an agent daily at 9 AM.", + const routine = await project.beta.routines.createOrUpdate(routineName, { + triggers: { + daily: { type: "schedule", cron_expression: "0 9 * * *", time_zone: "UTC" }, }, - ); + action: { type: "invoke_agent_responses_api", agent_name: agentName }, + description: "A routine that invokes an agent daily at 9 AM.", + }); console.log(`Routine created: ${routine.name}`); // Retrieve the routine diff --git a/sdk/ai/ai-projects/samples/v2/javascript/skills/skillBasic.js b/sdk/ai/ai-projects/samples/v2/javascript/skills/skillBasic.js index f51a40c39c03..37771f120fe6 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/skills/skillBasic.js +++ b/sdk/ai/ai-projects/samples/v2/javascript/skills/skillBasic.js @@ -35,17 +35,17 @@ async function main() { // Create a new skill const created = await project.beta.skills.create(skillName, { - description: "Example skill created by the @azure/ai-projects sample.", - instructions: "You are a helpful assistant that answers questions concisely.", - metadata: { owner: "sample" }, + inlineContent: { + description: "Example skill created by the @azure/ai-projects sample.", + instructions: "You are a helpful assistant that answers questions concisely.", + metadata: { owner: "sample" }, + }, }); - console.log(`Skill created: ${created.name} (id: ${created.skill_id})`); + console.log(`Skill created: ${created.name} (id: ${created.id})`); // Retrieve the skill const fetched = await project.beta.skills.get(skillName); - console.log( - `Retrieved skill: ${fetched.name} (id: ${fetched.skill_id}), ${JSON.stringify(fetched)}`, - ); + console.log(`Retrieved skill: ${fetched.name} (id: ${fetched.id}), ${JSON.stringify(fetched)}`); // List skills const skills = []; @@ -54,7 +54,7 @@ async function main() { } console.log(`Found ${skills.length} skill(s)`); for (const item of skills) { - console.log(` - ${item.name} (id: ${item.skill_id})`); + console.log(` - ${item.name} (id: ${item.id})`); } // Delete the skill diff --git a/sdk/ai/ai-projects/samples/v2/javascript/skills/skillUploadAndDownload.js b/sdk/ai/ai-projects/samples/v2/javascript/skills/skillUploadAndDownload.js index 64c4646157c6..0bc2f996b869 100644 --- a/sdk/ai/ai-projects/samples/v2/javascript/skills/skillUploadAndDownload.js +++ b/sdk/ai/ai-projects/samples/v2/javascript/skills/skillUploadAndDownload.js @@ -45,16 +45,16 @@ async function main() { // Upload a skill package const packageBytes = readFileSync(skillFilePath); - const imported = await project.beta.skills.createFromPackage(packageBytes); - console.log( - `Imported skill from package: ${imported.name} (${imported.skill_id}) has_blob=${imported.has_blob}`, - ); + const imported = await project.beta.skills.createFromFiles(skillName, { + files: [ + { contents: packageBytes, contentType: "application/zip", filename: "canvas-design.zip" }, + ], + }); + console.log(`Imported skill from package: ${imported.name} (${imported.skill_id})`); // Retrieve the uploaded skill const fetched = await project.beta.skills.get(imported.name); - console.log( - `Fetched imported skill: ${fetched.name} (${fetched.skill_id}) has_blob=${fetched.has_blob}`, - ); + console.log(`Fetched imported skill: ${fetched.name} (${fetched.id})`); // Download the skill package const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); diff --git a/sdk/ai/ai-projects/samples/v2/javascript/skills/skillWithToolbox.js b/sdk/ai/ai-projects/samples/v2/javascript/skills/skillWithToolbox.js new file mode 100644 index 000000000000..d429dc013da3 --- /dev/null +++ b/sdk/ai/ai-projects/samples/v2/javascript/skills/skillWithToolbox.js @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to expose a Skill to a Prompt Agent via a + * Toolbox, using the AIProjectClient and the OpenAI-compatible client. + * + * It creates a Skill with inline content describing how to compute shipping + * cost, then creates a Toolbox version that references the skill. A Prompt + * Agent is created with an `MCPTool` pointed at the toolbox's versioned + * `/mcp` endpoint. The skill's instructions are injected into the agent's + * context, so when asked a shipping-cost question the agent answers directly + * using the skill's formula. + * + * Skills and Toolboxes are currently preview features. In the JS SDK, you + * access these operations via `project.beta.skills` and + * `project.beta.toolboxes`. + * + * @summary Demonstrates adding a skill to a toolbox and invoking it via a Prompt Agent. + */ + +const { AIProjectClient, RestError } = require("@azure/ai-projects"); +const { DefaultAzureCredential } = require("@azure/identity"); +require("dotenv/config"); + +const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"] || ""; +const deploymentName = process.env["FOUNDRY_MODEL_NAME"] || ""; + +const SKILL_NAME = "shipping-cost-skill"; +const TOOLBOX_NAME = "toolbox_with_skill"; +const AGENT_NAME = "SkillToolboxAgent"; + +async function main() { + const credential = new DefaultAzureCredential(); + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); + + // --- Clean up any prior runs --- + try { + await project.beta.toolboxes.delete(TOOLBOX_NAME); + } catch (e) { + if (!(e instanceof RestError && e.statusCode === 404)) throw e; + } + try { + await project.beta.skills.delete(SKILL_NAME); + } catch (e) { + if (!(e instanceof RestError && e.statusCode === 404)) throw e; + } + + // --- 1. Create a skill --- + const skill = await project.beta.skills.create(SKILL_NAME, { + inlineContent: { + description: "Compute shipping cost for a package given weight and destination.", + instructions: + "You are a shipping cost calculator. When asked to compute " + + "shipping cost, use this formula: cost (USD) = 5 + 2 * weight_kg " + + "for domestic destinations, and cost (USD) = 15 + 4 * weight_kg " + + "for international destinations. Always state the formula you used.", + metadata: { revision: "1" }, + }, + }); + console.log(`Created skill: ${skill.name} version=${skill.version}`); + + // --- 2. Create a toolbox version with toolbox_search_preview and the skill --- + const skillRef = { + type: "skill_reference", + name: skill.name, + version: skill.version, + }; + + const toolboxVersion = await project.beta.toolboxes.createVersion( + TOOLBOX_NAME, + [{ type: "toolbox_search_preview" }], + { + description: "Toolbox exposing a shipping-cost skill.", + skills: [skillRef], + }, + ); + console.log(`Created toolbox: ${toolboxVersion.name} version=${toolboxVersion.version}`); + + // --- 3. Build the MCP tool reference to the toolbox endpoint --- + const toolboxMcpUrl = `${projectEndpoint}/toolboxes/${TOOLBOX_NAME}/versions/${toolboxVersion.version}/mcp?api-version=v1`; + const token = (await credential.getToken("https://ai.azure.com/.default")).token; + + const toolboxMcpTool = { + type: "mcp", + server_label: "skill-toolbox", + server_url: toolboxMcpUrl, + authorization: token, + headers: { "Foundry-Features": "Toolboxes=V1Preview" }, + require_approval: "never", + }; + + // --- 4. Create a prompt agent with the toolbox MCP tool --- + const agent = await project.agents.createVersion(AGENT_NAME, { + kind: "prompt", + model: deploymentName, + instructions: + "Answer the user using the `shipping-cost-skill` instructions " + + "available in your context. Do not call `tool_search`; the " + + "skill rules are already part of your knowledge. Apply the " + + "skill's formula exactly as given and state the formula in " + + "your answer.", + temperature: 0, + tools: [toolboxMcpTool], + }); + console.log(`Agent created (id=${agent.id}, name=${agent.name}, version=${agent.version})`); + + // --- 5. Send a query --- + const userInput = "Compute the shipping cost for a 3 kg package shipped domestically."; + console.log(`User: ${userInput}`); + + const response = await openAIClient.responses.create( + { input: userInput }, + { + body: { + agent_reference: { name: agent.name, type: "agent_reference" }, + }, + }, + ); + + for (const item of response.output) { + if (item.type === "mcp_list_tools") { + console.log( + `mcp_list_tools server_label=${item.server_label} tools=${(item.tools || []).map((t) => t.name)}`, + ); + } else if (item.type === "mcp_call") { + console.log( + `mcp_call server_label=${item.server_label} name=${item.name} error=${item.error}`, + ); + if ("output" in item && item.output) { + console.log(` output: ${item.output}`); + } + } else if (item.type === "mcp_approval_request") { + console.log(`mcp_approval_request server_label=${item.server_label} name=${item.name}`); + } else { + console.log(`output item type=${item.type}`); + } + } + + console.log(`Response: ${response.output_text}`); + + // --- 6. Clean up --- + await project.beta.toolboxes.delete(TOOLBOX_NAME); + console.log("Toolbox deleted"); + await project.beta.skills.delete(SKILL_NAME); + console.log("Skill deleted"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); + +module.exports = { main }; diff --git a/sdk/ai/ai-projects/samples/v2/typescript/README.md b/sdk/ai/ai-projects/samples/v2/typescript/README.md index f1de39bf0ef0..1931d153b073 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/README.md +++ b/sdk/ai/ai-projects/samples/v2/typescript/README.md @@ -35,6 +35,7 @@ These sample programs show how to use the TypeScript client libraries for Azure | [agents/tools/agentOpenApiConnectionAuth.ts][agents_tools_agentopenapiconnectionauth] | Demonstrates how to create an OpenAPI-enabled agent that uses a project connection for authentication and stream responses that include tool invocation details. | | [agents/tools/agentSharepoint.ts][agents_tools_agentsharepoint] | This sample demonstrates how to create an agent with SharePoint tool capabilities, search SharePoint content, and process streaming responses with citations. | | [agents/tools/agentToolboxSearch.ts][agents_tools_agenttoolboxsearch] | Create an agent with tool_search to dynamically discover deferred MCP tools. | +| [agents/tools/agentToolboxWithGuardrail.ts][agents_tools_agenttoolboxwithguardrail] | Create an agent with a toolbox that has a guardrail (RAI policy) applied. | | [agents/tools/agentWebSearch.ts][agents_tools_agentwebsearch] | This sample demonstrates how to create an agent with web search capabilities, send a query to search the web, and clean up resources. | | [agents/tools/agentWebSearchCustomSearch.ts][agents_tools_agentwebsearchcustomsearch] | This sample demonstrates how to create an agent with a WebSearchTool configured with Bing Custom Search, send a query with streaming, and clean up resources. | | [agents/tools/agentWebSearchPreview.ts][agents_tools_agentwebsearchpreview] | This sample demonstrates how to create an agent with the WebSearchPreviewTool, send a query to search the web with streaming, and clean up resources. | @@ -42,6 +43,7 @@ These sample programs show how to use the TypeScript client libraries for Azure | [responses/responseBasic.ts][responses_responsebasic] | This sample demonstrates how to create responses with and without conversation context. | | [responses/responseBasicWithoutAIProjectClient.ts][responses_responsebasicwithoutaiprojectclient] | This sample demonstrates how to create an OpenAI client directly with Azure credentials and use it for a basic responses operation. See also https://platform.openai.com/docs/api-reference/responses/create | | [responses/responseStream.ts][responses_responsestream] | This sample demonstrates how to create a non-streaming response and then use streaming for a follow-up response with conversation context. | +| [skills/skillWithToolbox.ts][skills_skillwithtoolbox] | Demonstrates adding a skill to a toolbox and invoking it via a Prompt Agent. | | [agents/agentFunctionTool.ts][agents_agentfunctiontool] | Demonstrates how to create an agent with function tools, handle function calls, and provide function results to get the final response. | | [agents/hostedAgents/sessionLogStream.ts][agents_hostedagents_sessionlogstream] | Demonstrates streaming session logs from a hosted agent. | | [agents/hostedAgents/createHostedAgentFromCode.ts][agents_hostedagents_createhostedagentfromcode] | Upload code to an existing hosted agent, poll until active, then download and verify. | @@ -187,6 +189,7 @@ Take a look at our [API Documentation][apiref] for more information about the AP [agents_tools_agentopenapiconnectionauth]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentOpenApiConnectionAuth.ts [agents_tools_agentsharepoint]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentSharepoint.ts [agents_tools_agenttoolboxsearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentToolboxSearch.ts +[agents_tools_agenttoolboxwithguardrail]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentToolboxWithGuardrail.ts [agents_tools_agentwebsearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentWebSearch.ts [agents_tools_agentwebsearchcustomsearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentWebSearchCustomSearch.ts [agents_tools_agentwebsearchpreview]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentWebSearchPreview.ts @@ -194,6 +197,7 @@ Take a look at our [API Documentation][apiref] for more information about the AP [responses_responsebasic]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/responses/responseBasic.ts [responses_responsebasicwithoutaiprojectclient]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/responses/responseBasicWithoutAIProjectClient.ts [responses_responsestream]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/responses/responseStream.ts +[skills_skillwithtoolbox]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillWithToolbox.ts [agents_agentfunctiontool]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/agentFunctionTool.ts [agents_hostedagents_sessionlogstream]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/sessionLogStream.ts [agents_hostedagents_createhostedagentfromcode]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/createHostedAgentFromCode.ts diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/betaAgents.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/betaAgents.ts index 144c1de25080..35fd71ee83f8 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/betaAgents.ts +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/betaAgents.ts @@ -38,10 +38,8 @@ export async function main(): Promise { kind: "hosted", cpu: "0.5", memory: "1Gi", - image, - container_protocol_versions: [ - { protocol: "responses", version: "v1" } as ProtocolVersionRecord, - ], + container_configuration: { image }, + protocol_versions: [{ protocol: "responses", version: "v1" } as ProtocolVersionRecord], } as HostedAgentDefinition, { foundryFeatures: "HostedAgents=V1Preview", @@ -102,14 +100,19 @@ export async function main(): Promise { ); console.log(`Uploaded file: ${uploadResult.path} (${uploadResult.bytes_written} bytes)`); - // List files in the session sandbox - const listing = await project.beta.agents.listSessionFiles( - agentName, - session.agent_session_id, - "/sandbox", - ); - console.log(`Files in /sandbox:`); - for (const entry of listing.entries) { + // List files in the session sandbox (with pagination monitoring) + const files = []; + let pageCount = 0; + const pager = project.beta.agents.listSessionFiles(agentName, session.agent_session_id, { + path: "/sandbox", + }); + for await (const page of pager.byPage()) { + pageCount++; + console.log(` Page ${pageCount}: ${page.length} entries`); + files.push(...page); + } + console.log(`Files in /sandbox (${files.length} total across ${pageCount} page(s)):`); + for (const entry of files) { console.log( ` - ${entry.name} (${entry.is_directory ? "directory" : "file"})`, JSON.stringify(entry), diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/createHostedAgentFromCode.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/createHostedAgentFromCode.ts index 53d1debd72a1..77331fb7e586 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/createHostedAgentFromCode.ts +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/createHostedAgentFromCode.ts @@ -81,7 +81,7 @@ export async function main(): Promise { code: { contents: codeZip, contentType: "application/zip", filename: "code.zip" }, }; - const created = await project.beta.agents.createAgentVersionFromCode( + const created = await project.beta.agents.createVersionFromCode( agentName, codeZipSha256, content, diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/sessionLogStream.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/sessionLogStream.ts index 31b14bb2b82d..2ecda5eeaa9b 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/sessionLogStream.ts +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/hostedAgents/sessionLogStream.ts @@ -85,10 +85,8 @@ export async function main(): Promise { kind: "hosted", cpu: "0.5", memory: "1Gi", - image, - container_protocol_versions: [ - { protocol: "responses", version: "1.0.0" } as ProtocolVersionRecord, - ], + container_configuration: { image }, + protocol_versions: [{ protocol: "responses", version: "1.0.0" } as ProtocolVersionRecord], } as HostedAgentDefinition, { foundryFeatures: "HostedAgents=V1Preview", diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentToolboxWithGuardrail.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentToolboxWithGuardrail.ts new file mode 100644 index 000000000000..16e9fa851261 --- /dev/null +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/agents/tools/agentToolboxWithGuardrail.ts @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to create an agent that uses a toolbox with a guardrail (RAI policy). + * The toolbox is configured with a web_search tool and an RAI policy that filters responses. + * + * @summary Create an agent with a toolbox that has a guardrail (RAI policy) applied. + */ + +import { DefaultAzureCredential } from "@azure/identity"; +import { AIProjectClient, type MCPTool } from "@azure/ai-projects"; +import "dotenv/config"; + +const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"] || ""; +const deploymentName = process.env["FOUNDRY_MODEL_NAME"] || ""; +const raiPolicyName = + process.env["RAI_POLICY_NAME"] || + "/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//raiPolicies/"; + +export async function main(): Promise { + const credential = new DefaultAzureCredential(); + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); + + console.log("Creating toolbox with guardrail (RAI policy)..."); + const toolboxVersion = await project.beta.toolboxes.createVersion( + "my-toolbox", + [{ type: "web_search" }], + { + description: "Toolbox with guardrail", + policies: { + rai_config: { + rai_policy_name: raiPolicyName, + }, + }, + }, + ); + console.log(`Toolbox version created: ${toolboxVersion.version}`); + + console.log("\nCreating agent with deferred MCP tool and tool_search..."); + const token = (await credential.getToken("https://ai.azure.com/.default")).token; + const toolboxMcpUrl = `${projectEndpoint}/toolboxes/my-toolbox/versions/${toolboxVersion.version}/mcp?api-version=v1`; + + const agent = await project.agents.createVersion("MyGuardrailAgent", { + kind: "prompt", + model: deploymentName, + instructions: "You are a helpful assistant. Use web search when needed to answer questions.", + tools: [ + { + type: "mcp", + server_label: "my-toolbox", + server_url: toolboxMcpUrl, + authorization: token, + headers: { "Foundry-Features": "Toolboxes=V1Preview" }, + require_approval: "never", + } as MCPTool, + ], + }); + console.log(`Agent created (id: ${agent.id}, name: ${agent.name}, version: ${agent.version})`); + + console.log("\nSending request to the agent..."); + try { + const response = await openAIClient.responses.create( + { + input: "What are the latest developments in responsible AI?", + }, + { + body: { + agent_reference: { name: agent.name, type: "agent_reference" }, + }, + }, + ); + console.log(`\nResponse output: ${response.output_text}`); + } catch (e: any) { + if (e.status === 400 && e.message?.includes("blocked by policy: RAI")) { + console.log("\nThe RAI guardrail blocked the tool call as expected."); + console.log(`Details: ${e.message}`); + } else { + throw e; + } + } + + console.log("\nCleaning up resources..."); + await project.agents.deleteVersion(agent.name, agent.version); + await project.beta.toolboxes.deleteVersion("my-toolbox", toolboxVersion.version); + console.log("Agent and toolbox deleted"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/routine/routineBasics.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/routine/routineBasics.ts index cd6f93b452d9..ed1846737f5c 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/src/routine/routineBasics.ts +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/routine/routineBasics.ts @@ -38,14 +38,13 @@ export async function main(): Promise { console.log(`Agent created: ${agent.name} (version: ${agent.version})`); // Create or update a routine - const routine = await project.beta.routines.createOrUpdate( - routineName, - { daily: { type: "schedule", cron_expression: "0 9 * * *", time_zone: "UTC" } }, - { type: "invoke_agent_responses_api", agent_name: agentName }, - { - description: "A routine that invokes an agent daily at 9 AM.", + const routine = await project.beta.routines.createOrUpdate(routineName, { + triggers: { + daily: { type: "schedule", cron_expression: "0 9 * * *", time_zone: "UTC" }, }, - ); + action: { type: "invoke_agent_responses_api", agent_name: agentName }, + description: "A routine that invokes an agent daily at 9 AM.", + }); console.log(`Routine created: ${routine.name}`); // Retrieve the routine diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillBasic.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillBasic.ts index 8d766ad3d528..e2bc92596f5d 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillBasic.ts +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillBasic.ts @@ -35,17 +35,17 @@ export async function main(): Promise { // Create a new skill const created = await project.beta.skills.create(skillName, { - description: "Example skill created by the @azure/ai-projects sample.", - instructions: "You are a helpful assistant that answers questions concisely.", - metadata: { owner: "sample" }, + inlineContent: { + description: "Example skill created by the @azure/ai-projects sample.", + instructions: "You are a helpful assistant that answers questions concisely.", + metadata: { owner: "sample" }, + }, }); - console.log(`Skill created: ${created.name} (id: ${created.skill_id})`); + console.log(`Skill created: ${created.name} (id: ${created.id})`); // Retrieve the skill const fetched = await project.beta.skills.get(skillName); - console.log( - `Retrieved skill: ${fetched.name} (id: ${fetched.skill_id}), ${JSON.stringify(fetched)}`, - ); + console.log(`Retrieved skill: ${fetched.name} (id: ${fetched.id}), ${JSON.stringify(fetched)}`); // List skills const skills = []; @@ -54,7 +54,7 @@ export async function main(): Promise { } console.log(`Found ${skills.length} skill(s)`); for (const item of skills) { - console.log(` - ${item.name} (id: ${item.skill_id})`); + console.log(` - ${item.name} (id: ${item.id})`); } // Delete the skill diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillUploadAndDownload.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillUploadAndDownload.ts index d4d94d4f05a6..5728469bd3cf 100644 --- a/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillUploadAndDownload.ts +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillUploadAndDownload.ts @@ -47,16 +47,16 @@ export async function main(): Promise { // Upload a skill package const packageBytes = readFileSync(skillFilePath); - const imported = await project.beta.skills.createFromPackage(packageBytes); - console.log( - `Imported skill from package: ${imported.name} (${imported.skill_id}) has_blob=${imported.has_blob}`, - ); + const imported = await project.beta.skills.createFromFiles(skillName, { + files: [ + { contents: packageBytes, contentType: "application/zip", filename: "canvas-design.zip" }, + ], + }); + console.log(`Imported skill from package: ${imported.name} (${imported.skill_id})`); // Retrieve the uploaded skill const fetched = await project.beta.skills.get(imported.name); - console.log( - `Fetched imported skill: ${fetched.name} (${fetched.skill_id}) has_blob=${fetched.has_blob}`, - ); + console.log(`Fetched imported skill: ${fetched.name} (${fetched.id})`); // Download the skill package const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); diff --git a/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillWithToolbox.ts b/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillWithToolbox.ts new file mode 100644 index 000000000000..b948fe3ab5e1 --- /dev/null +++ b/sdk/ai/ai-projects/samples/v2/typescript/src/skills/skillWithToolbox.ts @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to expose a Skill to a Prompt Agent via a + * Toolbox, using the AIProjectClient and the OpenAI-compatible client. + * + * It creates a Skill with inline content describing how to compute shipping + * cost, then creates a Toolbox version that references the skill. A Prompt + * Agent is created with an `MCPTool` pointed at the toolbox's versioned + * `/mcp` endpoint. The skill's instructions are injected into the agent's + * context, so when asked a shipping-cost question the agent answers directly + * using the skill's formula. + * + * Skills and Toolboxes are currently preview features. In the JS SDK, you + * access these operations via `project.beta.skills` and + * `project.beta.toolboxes`. + * + * @summary Demonstrates adding a skill to a toolbox and invoking it via a Prompt Agent. + */ + +import type { MCPTool, ToolboxSkillReference } from "@azure/ai-projects"; +import { AIProjectClient, RestError } from "@azure/ai-projects"; +import { DefaultAzureCredential } from "@azure/identity"; +import "dotenv/config"; + +const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"] || ""; +const deploymentName = process.env["FOUNDRY_MODEL_NAME"] || ""; + +const SKILL_NAME = "shipping-cost-skill"; +const TOOLBOX_NAME = "toolbox_with_skill"; +const AGENT_NAME = "SkillToolboxAgent"; + +export async function main(): Promise { + const credential = new DefaultAzureCredential(); + const project = new AIProjectClient(projectEndpoint, credential); + const openAIClient = project.getOpenAIClient(); + + // --- Clean up any prior runs --- + try { + await project.beta.toolboxes.delete(TOOLBOX_NAME); + } catch (e) { + if (!(e instanceof RestError && e.statusCode === 404)) throw e; + } + try { + await project.beta.skills.delete(SKILL_NAME); + } catch (e) { + if (!(e instanceof RestError && e.statusCode === 404)) throw e; + } + + // --- 1. Create a skill --- + const skill = await project.beta.skills.create(SKILL_NAME, { + inlineContent: { + description: "Compute shipping cost for a package given weight and destination.", + instructions: + "You are a shipping cost calculator. When asked to compute " + + "shipping cost, use this formula: cost (USD) = 5 + 2 * weight_kg " + + "for domestic destinations, and cost (USD) = 15 + 4 * weight_kg " + + "for international destinations. Always state the formula you used.", + metadata: { revision: "1" }, + }, + }); + console.log(`Created skill: ${skill.name} version=${skill.version}`); + + // --- 2. Create a toolbox version with toolbox_search_preview and the skill --- + const skillRef: ToolboxSkillReference = { + type: "skill_reference", + name: skill.name, + version: skill.version, + }; + + const toolboxVersion = await project.beta.toolboxes.createVersion( + TOOLBOX_NAME, + [{ type: "toolbox_search_preview" }], + { + description: "Toolbox exposing a shipping-cost skill.", + skills: [skillRef], + }, + ); + console.log(`Created toolbox: ${toolboxVersion.name} version=${toolboxVersion.version}`); + + // --- 3. Build the MCP tool reference to the toolbox endpoint --- + const toolboxMcpUrl = `${projectEndpoint}/toolboxes/${TOOLBOX_NAME}/versions/${toolboxVersion.version}/mcp?api-version=v1`; + const token = (await credential.getToken("https://ai.azure.com/.default"))!.token; + + const toolboxMcpTool: MCPTool = { + type: "mcp", + server_label: "skill-toolbox", + server_url: toolboxMcpUrl, + authorization: token, + headers: { "Foundry-Features": "Toolboxes=V1Preview" }, + require_approval: "never", + }; + + // --- 4. Create a prompt agent with the toolbox MCP tool --- + const agent = await project.agents.createVersion(AGENT_NAME, { + kind: "prompt", + model: deploymentName, + instructions: + "Answer the user using the `shipping-cost-skill` instructions " + + "available in your context. Do not call `tool_search`; the " + + "skill rules are already part of your knowledge. Apply the " + + "skill's formula exactly as given and state the formula in " + + "your answer.", + temperature: 0, + tools: [toolboxMcpTool], + }); + console.log(`Agent created (id=${agent.id}, name=${agent.name}, version=${agent.version})`); + + // --- 5. Send a query --- + const userInput = "Compute the shipping cost for a 3 kg package shipped domestically."; + console.log(`User: ${userInput}`); + + const response = await openAIClient.responses.create( + { input: userInput }, + { + body: { + agent_reference: { name: agent.name, type: "agent_reference" }, + }, + }, + ); + + for (const item of response.output) { + if (item.type === "mcp_list_tools") { + console.log( + `mcp_list_tools server_label=${item.server_label} tools=${(item.tools || []).map((t: any) => t.name)}`, + ); + } else if (item.type === "mcp_call") { + console.log( + `mcp_call server_label=${item.server_label} name=${item.name} error=${item.error}`, + ); + if ("output" in item && item.output) { + console.log(` output: ${item.output}`); + } + } else if (item.type === "mcp_approval_request") { + console.log(`mcp_approval_request server_label=${item.server_label} name=${item.name}`); + } else { + console.log(`output item type=${item.type}`); + } + } + + console.log(`Response: ${response.output_text}`); + + // --- 6. Clean up --- + await project.beta.toolboxes.delete(TOOLBOX_NAME); + console.log("Toolbox deleted"); + await project.beta.skills.delete(SKILL_NAME); + console.log("Skill deleted"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +});