diff --git a/sdk/ai/ai-projects/review/ai-projects-node.api.md b/sdk/ai/ai-projects/review/ai-projects-node.api.md index 400ee0e93cbd..66bbeaac6681 100644 --- a/sdk/ai/ai-projects/review/ai-projects-node.api.md +++ b/sdk/ai/ai-projects/review/ai-projects-node.api.md @@ -1841,7 +1841,7 @@ export interface MemoryStoresOperations { list: (options?: MemoryStoresListMemoryStoresOptionalParams) => PagedAsyncIterableIterator; searchMemories: (name: string, scope: string, options?: MemoryStoresSearchMemoriesOptionalParams) => Promise; update: (name: string, options?: MemoryStoresUpdateMemoryStoreOptionalParams) => Promise; - updateMemories: (name: string, scope: string, options?: MemoryStoresUpdateMemoriesOptionalParams) => PollerLike, MemoryStoreUpdateResult>; + updateMemories: (name: string, scope: string, options?: MemoryStoresUpdateMemoriesOptionalParams) => MemoryStoreUpdateMemoriesPoller; } // @public @@ -1867,6 +1867,20 @@ export interface MemoryStoresUpdateMemoryStoreOptionalParams extends OperationOp metadata?: Record; } +// @public (undocumented) +export type MemoryStoreUpdateMemoriesPoller = PollerLike & { + readonly updateId?: string; + readonly updateStatus?: MemoryStoreUpdateStatus; + readonly supersededBy?: string; +}; + +// @public (undocumented) +export type MemoryStoreUpdateOperationState = OperationState_2 & { + updateId?: string; + updateStatus?: MemoryStoreUpdateStatus; + supersededBy?: string; +}; + // @public export interface MemoryStoreUpdateResponse { error?: ApiError; diff --git a/sdk/ai/ai-projects/sample.env b/sdk/ai/ai-projects/sample.env index d8f31b0b4fe2..6e114cefeacf 100644 --- a/sdk/ai/ai-projects/sample.env +++ b/sdk/ai/ai-projects/sample.env @@ -1,4 +1,6 @@ AZURE_AI_PROJECT_ENDPOINT="" +AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME="" +AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME="" MODEL_DEPLOYMENT_NAME="" MODEL_API_KEY="" MODEL_ENDPOINT="" @@ -22,4 +24,3 @@ IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME=" MCP_PROJECT_CONNECTION_ID="" TRIPADVISOR_PROJECT_CONNECTION_ID="" - diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentAgentToAgent.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentAgentToAgent.ts index c9a7abf82f20..688aa0d7f091 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentAgentToAgent.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentAgentToAgent.ts @@ -19,7 +19,7 @@ import * as readline from "readline"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const a2aProjectConnectionId = process.env["A2A_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentAiSearch.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentAiSearch.ts index f6c6b993993b..6fa4d5cab2bb 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentAiSearch.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentAiSearch.ts @@ -18,7 +18,7 @@ import * as readline from "readline"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const aiSearchConnectionId = process.env["AZURE_AI_SEARCH_CONNECTION_ID"] || ""; const aiSearchIndexName = process.env["AI_SEARCH_INDEX_NAME"] || ""; diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingCustomSearch.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingCustomSearch.ts index 19e67fb36d1c..badaa599aa60 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingCustomSearch.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingCustomSearch.ts @@ -18,7 +18,7 @@ import * as readline from "readline"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const bingCustomSearchProjectConnectionId = process.env["BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingGrounding.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingGrounding.ts index 2af334d68ba8..8857b1e23d6a 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingGrounding.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentBingGrounding.ts @@ -17,7 +17,7 @@ import { AIProjectClient } from "@azure/ai-projects"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const bingProjectConnectionId = process.env["BING_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentBrowserAutomation.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentBrowserAutomation.ts index 9a77b3c8cdf4..1f346031132f 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentBrowserAutomation.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentBrowserAutomation.ts @@ -17,7 +17,7 @@ import { AIProjectClient } from "@azure/ai-projects"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const browserAutomationProjectConnectionId = process.env["BROWSER_AUTOMATION_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentMemorySearch.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentMemorySearch.ts new file mode 100644 index 000000000000..0147a74fdd1c --- /dev/null +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentMemorySearch.ts @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to add conversational memory to an agent by using the + * Memory Search tool. The agent stores memories in a memory store and can recall them + * in later conversations. + * + * @summary Create an agent with Memory Search, capture memories from a conversation, + * and retrieve them in a new conversation. + * + * @azsdk-weight 100 + */ + +import { DefaultAzureCredential } from "@azure/identity"; +import { + AIProjectClient, + MemoryStoreDefaultDefinition, + MemoryStoreDefaultOptions, + MemorySearchTool, +} from "@azure/ai-projects"; +import "dotenv/config"; + +const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; +const agentModelDeployment = + process.env["MODEL_DEPLOYMENT_NAME"] || ""; +const chatModelDeployment = + process.env["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"] || ""; +const embeddingModelDeployment = + process.env["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"] || + ""; + +const memoryStoreName = "my_memory_store_123"; +const scope = "user_123"; + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function main(): Promise { + const project = new AIProjectClient(projectEndpoint, new DefaultAzureCredential()); + const openAIClient = await project.getOpenAIClient(); + + let conversationId: string | undefined; + let followUpConversationId: string | undefined; + let agentVersion: + | { + name: string; + version: string; + } + | undefined; + + try { + // Clean up an existing memory store if it already exists + try { + await project.memoryStores.delete(memoryStoreName); + console.log(`Memory store '${memoryStoreName}' deleted`); + } catch (error: any) { + if (error?.statusCode !== 404) { + throw error; + } + } + + // Create a memory store with chat and embedding models + const memoryStore = await project.memoryStores.create( + memoryStoreName, + { + kind: "default", + chat_model: chatModelDeployment, + embedding_model: embeddingModelDeployment, + options: { + user_profile_enabled: true, + chat_summary_enabled: true, + } satisfies MemoryStoreDefaultOptions, + } satisfies MemoryStoreDefaultDefinition, + { + description: "Memory store for agent conversations", + }, + ); + const chatModelUsed = + "chat_model" in memoryStore.definition + ? memoryStore.definition.chat_model + : chatModelDeployment; + console.log( + `Created memory store: ${memoryStore.name} (${memoryStore.id}) using chat model '${chatModelUsed}'`, + ); + + // Configure Memory Search tool to attach to the agent + const memorySearchTool: MemorySearchTool = { + type: "memory_search", + memory_store_name: memoryStore.name, + scope, + update_delay: 1, // wait briefly after conversation inactivity before updating memories + }; + + // Create an agent that will use the Memory Search tool + const agent = await project.agents.createVersion("MemorySearchAgent", { + kind: "prompt", + model: agentModelDeployment, + instructions: + "You are a helpful assistant that remembers user preferences using the memory search tool.", + tools: [memorySearchTool], + }); + agentVersion = { + name: agent.name, + version: agent.version, + }; + console.log(`Agent created (id: ${agent.id}, name: ${agent.name}, version: ${agent.version})`); + + // Start a conversation and provide details the agent should remember + const conversation = await openAIClient.conversations.create(); + conversationId = conversation.id; + console.log(`Conversation started (${conversation.id}). Sending a message to seed memories...`); + + const firstResponse = await openAIClient.responses.create( + { + input: "I prefer dark roast coffee and usually drink it in the morning.", + conversation: conversation.id, + }, + { + body: { agent: { name: agent.name, type: "agent_reference" } }, + }, + ); + console.log(`Initial response: ${firstResponse.output_text}`); + + // Allow time for the memory store to update from this conversation + console.log("Waiting for the memory store to capture the new memory..."); + await delay(60000); + + // Create a follow-up conversation and ask the agent to recall the stored memory + const followUpConversation = await openAIClient.conversations.create(); + followUpConversationId = followUpConversation.id; + console.log(`Follow-up conversation started (${followUpConversation.id}).`); + + const followUpResponse = await openAIClient.responses.create( + { + input: "Can you remind me of my usual coffee order?", + conversation: followUpConversation.id, + }, + { + body: { agent: { name: agent.name, type: "agent_reference" } }, + }, + ); + console.log(`Follow-up response: ${followUpResponse.output_text}`); + } finally { + console.log("\nCleaning up resources..."); + if (conversationId) { + await openAIClient.conversations.delete(conversationId); + console.log(`Conversation ${conversationId} deleted`); + } + if (followUpConversationId) { + await openAIClient.conversations.delete(followUpConversationId); + console.log(`Conversation ${followUpConversationId} deleted`); + } + if (agentVersion) { + await project.agents.deleteVersion(agentVersion.name, agentVersion.version); + console.log("Agent deleted"); + } + try { + await project.memoryStores.delete(memoryStoreName); + console.log("Memory store deleted"); + } catch (error: any) { + if (error?.statusCode !== 404) { + throw error; + } + } + } + + console.log("\nMemory Search agent sample completed!"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApi.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApi.ts index b62167bdc3c3..1ad78e3c404b 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApi.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApi.ts @@ -25,7 +25,7 @@ import * as path from "path"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const weatherSpecPath = path.resolve(__dirname, "../assets", "weather_openapi.json"); function loadOpenApiSpec(specPath: string): unknown { diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApiConnectionAuth.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApiConnectionAuth.ts index 1a1402476cf1..cd7d79c13318 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApiConnectionAuth.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentOpenApiConnectionAuth.ts @@ -25,7 +25,7 @@ import * as path from "path"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const tripAdvisorProjectConnectionId = process.env["TRIPADVISOR_PROJECT_CONNECTION_ID"] || ""; const tripAdvisorSpecPath = path.resolve(__dirname, "../assets", "tripadvisor_openapi.json"); diff --git a/sdk/ai/ai-projects/samples-dev/agents/tools/agentSharepoint.ts b/sdk/ai/ai-projects/samples-dev/agents/tools/agentSharepoint.ts index 18968483ec87..a5aed4733e39 100644 --- a/sdk/ai/ai-projects/samples-dev/agents/tools/agentSharepoint.ts +++ b/sdk/ai/ai-projects/samples-dev/agents/tools/agentSharepoint.ts @@ -17,7 +17,7 @@ import { AIProjectClient } from "@azure/ai-projects"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const sharepointProjectConnectionId = process.env["SHAREPOINT_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/README.md b/sdk/ai/ai-projects/samples/v2-beta/javascript/README.md index e1d7eae76721..3b68ccf06194 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/README.md +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/README.md @@ -27,8 +27,9 @@ These sample programs show how to use the JavaScript client libraries for Azure | [agents/tools/agentImageGeneration.js][agents_tools_agentimagegeneration] | This sample demonstrates how to create an agent with ImageGenTool configured for image generation, make requests to generate images from text prompts, extract base64-encoded image data from the response, decode and save the generated image to a local file, and clean up created resources. | | [agents/tools/agentMcp.js][agents_tools_agentmcp] | This sample demonstrates how to create an agent with MCP tool capabilities, send requests that trigger MCP approval workflows, handle approval requests, and clean up resources. | | [agents/tools/agentMcpConnectionAuth.js][agents_tools_agentmcpconnectionauth] | This sample demonstrates how to create an agent with MCP tool capabilities using project connection authentication, send requests that trigger MCP approval workflows, handle approval requests, and clean up resources. | +| [agents/tools/agentMemorySearch.js][agents_tools_agentmemorysearch] | Create an agent with Memory Search, capture memories from a conversation, and retrieve them in a new conversation. | | [agents/tools/agentOpenApi.js][agents_tools_agentopenapi] | This sample demonstrates how to create an agent with OpenAPI tool capabilities, load OpenAPI specifications from local assets, and process streaming responses that may include tool outputs. | -| [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/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/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. | | [responses/responseBasic.js][responses_responsebasic] | This sample demonstrates how to create responses with and without conversation context. | @@ -98,6 +99,7 @@ Take a look at our [API Documentation][apiref] for more information about the AP [agents_tools_agentimagegeneration]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentImageGeneration.js [agents_tools_agentmcp]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentMcp.js [agents_tools_agentmcpconnectionauth]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentMcpConnectionAuth.js +[agents_tools_agentmemorysearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentMemorySearch.js [agents_tools_agentopenapi]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApi.js [agents_tools_agentopenapiconnectionauth]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApiConnectionAuth.js [agents_tools_agentsharepoint]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentSharepoint.js diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAgentToAgent.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAgentToAgent.js index 26a093aac658..57b923bf1a50 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAgentToAgent.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAgentToAgent.js @@ -17,7 +17,7 @@ const readline = require("readline"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const a2aProjectConnectionId = process.env["A2A_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAiSearch.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAiSearch.js index 65876cacef3d..f65da47e92c4 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAiSearch.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentAiSearch.js @@ -16,7 +16,7 @@ const readline = require("readline"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const aiSearchConnectionId = process.env["AZURE_AI_SEARCH_CONNECTION_ID"] || ""; const aiSearchIndexName = process.env["AI_SEARCH_INDEX_NAME"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingCustomSearch.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingCustomSearch.js index 2c0717a39eed..06f604778aaa 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingCustomSearch.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingCustomSearch.js @@ -16,7 +16,7 @@ const readline = require("readline"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const bingCustomSearchProjectConnectionId = process.env["BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingGrounding.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingGrounding.js index 368adcee489a..cf9c57014b8d 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingGrounding.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBingGrounding.js @@ -3,7 +3,7 @@ /** * This sample demonstrates how to create an AI agent with Bing grounding capabilities - * using the `bing_grounding` tool type and synchronous Azure AI Projects client. The agent can search + * using the "bing_grounding" tool type and synchronous Azure AI Projects client. The agent can search * the web for current information and provide grounded responses with URL citations. * * @summary This sample demonstrates how to create an agent with Bing grounding tool capabilities, @@ -15,7 +15,7 @@ const { AIProjectClient } = require("@azure/ai-projects"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const bingProjectConnectionId = process.env["BING_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBrowserAutomation.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBrowserAutomation.js index e1cd88fd13f7..2ca6595d1f53 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBrowserAutomation.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentBrowserAutomation.js @@ -15,7 +15,7 @@ const { AIProjectClient } = require("@azure/ai-projects"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const browserAutomationProjectConnectionId = process.env["BROWSER_AUTOMATION_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentMemorySearch.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentMemorySearch.js new file mode 100644 index 000000000000..1a110f8d91bb --- /dev/null +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentMemorySearch.js @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to add conversational memory to an agent by using the + * Memory Search tool. The agent stores memories in a memory store and can recall them + * in later conversations. + * + * @summary Create an agent with Memory Search, capture memories from a conversation, + * and retrieve them in a new conversation. + */ + +const { DefaultAzureCredential } = require("@azure/identity"); +const { AIProjectClient } = require("@azure/ai-projects"); +require("dotenv/config"); + +const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; +const agentModelDeployment = + process.env["MODEL_DEPLOYMENT_NAME"] || ""; +const chatModelDeployment = + process.env["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"] || ""; +const embeddingModelDeployment = + process.env["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"] || + ""; + +const memoryStoreName = "my_memory_store_123"; +const scope = "user_123"; + +function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function main() { + const project = new AIProjectClient(projectEndpoint, new DefaultAzureCredential()); + const openAIClient = await project.getOpenAIClient(); + + let conversationId; + let followUpConversationId; + let agentVersion; + + try { + // Clean up an existing memory store if it already exists + try { + await project.memoryStores.delete(memoryStoreName); + console.log(`Memory store '${memoryStoreName}' deleted`); + } catch (error) { + if (error?.statusCode !== 404) { + throw error; + } + } + + // Create a memory store with chat and embedding models + const memoryStore = await project.memoryStores.create( + memoryStoreName, + { + kind: "default", + chat_model: chatModelDeployment, + embedding_model: embeddingModelDeployment, + options: { + user_profile_enabled: true, + chat_summary_enabled: true, + }, + }, + { + description: "Memory store for agent conversations", + }, + ); + const chatModelUsed = + "chat_model" in memoryStore.definition + ? memoryStore.definition.chat_model + : chatModelDeployment; + console.log( + `Created memory store: ${memoryStore.name} (${memoryStore.id}) using chat model '${chatModelUsed}'`, + ); + + // Configure Memory Search tool to attach to the agent + const memorySearchTool = { + type: "memory_search", + memory_store_name: memoryStore.name, + scope, + update_delay: 1, // wait briefly after conversation inactivity before updating memories + }; + + // Create an agent that will use the Memory Search tool + const agent = await project.agents.createVersion("MemorySearchAgent", { + kind: "prompt", + model: agentModelDeployment, + instructions: + "You are a helpful assistant that remembers user preferences using the memory search tool.", + tools: [memorySearchTool], + }); + agentVersion = { + name: agent.name, + version: agent.version, + }; + console.log(`Agent created (id: ${agent.id}, name: ${agent.name}, version: ${agent.version})`); + + // Start a conversation and provide details the agent should remember + const conversation = await openAIClient.conversations.create(); + conversationId = conversation.id; + console.log(`Conversation started (${conversation.id}). Sending a message to seed memories...`); + + const firstResponse = await openAIClient.responses.create( + { + input: "I prefer dark roast coffee and usually drink it in the morning.", + conversation: conversation.id, + }, + { + body: { agent: { name: agent.name, type: "agent_reference" } }, + }, + ); + console.log(`Initial response: ${firstResponse.output_text}`); + + // Allow time for the memory store to update from this conversation + console.log("Waiting for the memory store to capture the new memory..."); + await delay(60000); + + // Create a follow-up conversation and ask the agent to recall the stored memory + const followUpConversation = await openAIClient.conversations.create(); + followUpConversationId = followUpConversation.id; + console.log(`Follow-up conversation started (${followUpConversation.id}).`); + + const followUpResponse = await openAIClient.responses.create( + { + input: "Can you remind me of my usual coffee order?", + conversation: followUpConversation.id, + }, + { + body: { agent: { name: agent.name, type: "agent_reference" } }, + }, + ); + console.log(`Follow-up response: ${followUpResponse.output_text}`); + } finally { + console.log("\nCleaning up resources..."); + if (conversationId) { + await openAIClient.conversations.delete(conversationId); + console.log(`Conversation ${conversationId} deleted`); + } + if (followUpConversationId) { + await openAIClient.conversations.delete(followUpConversationId); + console.log(`Conversation ${followUpConversationId} deleted`); + } + if (agentVersion) { + await project.agents.deleteVersion(agentVersion.name, agentVersion.version); + console.log("Agent deleted"); + } + try { + await project.memoryStores.delete(memoryStoreName); + console.log("Memory store deleted"); + } catch (error) { + if (error?.statusCode !== 404) { + throw error; + } + } + } + + console.log("\nMemory Search agent sample completed!"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); + +module.exports = { main }; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApi.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApi.js index acaed85db564..ef8d4313010a 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApi.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApi.js @@ -18,7 +18,7 @@ const path = require("path"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const weatherSpecPath = path.resolve(__dirname, "../assets", "weather_openapi.json"); function loadOpenApiSpec(specPath) { diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApiConnectionAuth.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApiConnectionAuth.js index 410ac3e32331..3bc7cd9dcf4e 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApiConnectionAuth.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentOpenApiConnectionAuth.js @@ -18,7 +18,7 @@ const path = require("path"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const tripAdvisorProjectConnectionId = process.env["TRIPADVISOR_PROJECT_CONNECTION_ID"] || ""; const tripAdvisorSpecPath = path.resolve(__dirname, "../assets", "tripadvisor_openapi.json"); diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentSharepoint.js b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentSharepoint.js index 23aa9f2444d6..96e6a25f22a9 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentSharepoint.js +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/agents/tools/agentSharepoint.js @@ -15,7 +15,7 @@ const { AIProjectClient } = require("@azure/ai-projects"); require("dotenv/config"); const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const sharepointProjectConnectionId = process.env["SHAREPOINT_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/javascript/sample.env b/sdk/ai/ai-projects/samples/v2-beta/javascript/sample.env index 376e41be505f..6e114cefeacf 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/javascript/sample.env +++ b/sdk/ai/ai-projects/samples/v2-beta/javascript/sample.env @@ -1,4 +1,6 @@ AZURE_AI_PROJECT_ENDPOINT="" +AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME="" +AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME="" MODEL_DEPLOYMENT_NAME="" MODEL_API_KEY="" MODEL_ENDPOINT="" @@ -21,3 +23,4 @@ FABRIC_PROJECT_CONNECTION_ID="" IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME="" MCP_PROJECT_CONNECTION_ID="" +TRIPADVISOR_PROJECT_CONNECTION_ID="" diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/README.md b/sdk/ai/ai-projects/samples/v2-beta/typescript/README.md index c5a7bdf6feb3..06b7ca9157b0 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/README.md +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/README.md @@ -27,8 +27,9 @@ These sample programs show how to use the TypeScript client libraries for Azure | [agents/tools/agentImageGeneration.ts][agents_tools_agentimagegeneration] | This sample demonstrates how to create an agent with ImageGenTool configured for image generation, make requests to generate images from text prompts, extract base64-encoded image data from the response, decode and save the generated image to a local file, and clean up created resources. | | [agents/tools/agentMcp.ts][agents_tools_agentmcp] | This sample demonstrates how to create an agent with MCP tool capabilities, send requests that trigger MCP approval workflows, handle approval requests, and clean up resources. | | [agents/tools/agentMcpConnectionAuth.ts][agents_tools_agentmcpconnectionauth] | This sample demonstrates how to create an agent with MCP tool capabilities using project connection authentication, send requests that trigger MCP approval workflows, handle approval requests, and clean up resources. | +| [agents/tools/agentMemorySearch.ts][agents_tools_agentmemorysearch] | Create an agent with Memory Search, capture memories from a conversation, and retrieve them in a new conversation. | | [agents/tools/agentOpenApi.ts][agents_tools_agentopenapi] | This sample demonstrates how to create an agent with OpenAPI tool capabilities, load OpenAPI specifications from local assets, and process streaming responses that may include tool outputs. | -| [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/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/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. | | [responses/responseBasic.ts][responses_responsebasic] | This sample demonstrates how to create responses with and without conversation context. | @@ -110,6 +111,7 @@ Take a look at our [API Documentation][apiref] for more information about the AP [agents_tools_agentimagegeneration]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentImageGeneration.ts [agents_tools_agentmcp]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentMcp.ts [agents_tools_agentmcpconnectionauth]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentMcpConnectionAuth.ts +[agents_tools_agentmemorysearch]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentMemorySearch.ts [agents_tools_agentopenapi]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApi.ts [agents_tools_agentopenapiconnectionauth]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/ai/ai-projects/samples/v2-beta/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-beta/typescript/src/agents/tools/agentSharepoint.ts diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/sample.env b/sdk/ai/ai-projects/samples/v2-beta/typescript/sample.env index 376e41be505f..6e114cefeacf 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/sample.env +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/sample.env @@ -1,4 +1,6 @@ AZURE_AI_PROJECT_ENDPOINT="" +AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME="" +AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME="" MODEL_DEPLOYMENT_NAME="" MODEL_API_KEY="" MODEL_ENDPOINT="" @@ -21,3 +23,4 @@ FABRIC_PROJECT_CONNECTION_ID="" IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME="" MCP_PROJECT_CONNECTION_ID="" +TRIPADVISOR_PROJECT_CONNECTION_ID="" diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAgentToAgent.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAgentToAgent.ts index aec32555ead6..20dd4df5d655 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAgentToAgent.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAgentToAgent.ts @@ -17,7 +17,7 @@ import * as readline from "readline"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const a2aProjectConnectionId = process.env["A2A_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAiSearch.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAiSearch.ts index a9e6a67b8b2e..eeffe0e966cd 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAiSearch.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentAiSearch.ts @@ -16,7 +16,7 @@ import * as readline from "readline"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const aiSearchConnectionId = process.env["AZURE_AI_SEARCH_CONNECTION_ID"] || ""; const aiSearchIndexName = process.env["AI_SEARCH_INDEX_NAME"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingCustomSearch.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingCustomSearch.ts index b07445c41b24..4fd47b336142 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingCustomSearch.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingCustomSearch.ts @@ -16,7 +16,7 @@ import * as readline from "readline"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const bingCustomSearchProjectConnectionId = process.env["BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingGrounding.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingGrounding.ts index 3a6f117862a2..8857b1e23d6a 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingGrounding.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBingGrounding.ts @@ -3,7 +3,7 @@ /** * This sample demonstrates how to create an AI agent with Bing grounding capabilities - * using the bing_grounding tool type and synchronous Azure AI Projects client. The agent can search + * using the "bing_grounding" tool type and synchronous Azure AI Projects client. The agent can search * the web for current information and provide grounded responses with URL citations. * * @summary This sample demonstrates how to create an agent with Bing grounding tool capabilities, @@ -17,7 +17,7 @@ import { AIProjectClient } from "@azure/ai-projects"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const bingProjectConnectionId = process.env["BING_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBrowserAutomation.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBrowserAutomation.ts index 5f264b0f0480..696070c244be 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBrowserAutomation.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentBrowserAutomation.ts @@ -15,7 +15,7 @@ import { AIProjectClient } from "@azure/ai-projects"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const browserAutomationProjectConnectionId = process.env["BROWSER_AUTOMATION_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentMemorySearch.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentMemorySearch.ts new file mode 100644 index 000000000000..180b6e032bb5 --- /dev/null +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentMemorySearch.ts @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This sample demonstrates how to add conversational memory to an agent by using the + * Memory Search tool. The agent stores memories in a memory store and can recall them + * in later conversations. + * + * @summary Create an agent with Memory Search, capture memories from a conversation, + * and retrieve them in a new conversation. + */ + +import { DefaultAzureCredential } from "@azure/identity"; +import { + AIProjectClient, + MemoryStoreDefaultDefinition, + MemoryStoreDefaultOptions, + MemorySearchTool, +} from "@azure/ai-projects"; +import "dotenv/config"; + +const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; +const agentModelDeployment = + process.env["MODEL_DEPLOYMENT_NAME"] || ""; +const chatModelDeployment = + process.env["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"] || ""; +const embeddingModelDeployment = + process.env["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"] || + ""; + +const memoryStoreName = "my_memory_store_123"; +const scope = "user_123"; + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function main(): Promise { + const project = new AIProjectClient(projectEndpoint, new DefaultAzureCredential()); + const openAIClient = await project.getOpenAIClient(); + + let conversationId: string | undefined; + let followUpConversationId: string | undefined; + let agentVersion: + | { + name: string; + version: string; + } + | undefined; + + try { + // Clean up an existing memory store if it already exists + try { + await project.memoryStores.delete(memoryStoreName); + console.log(`Memory store '${memoryStoreName}' deleted`); + } catch (error: any) { + if (error?.statusCode !== 404) { + throw error; + } + } + + // Create a memory store with chat and embedding models + const memoryStore = await project.memoryStores.create( + memoryStoreName, + { + kind: "default", + chat_model: chatModelDeployment, + embedding_model: embeddingModelDeployment, + options: { + user_profile_enabled: true, + chat_summary_enabled: true, + } satisfies MemoryStoreDefaultOptions, + } satisfies MemoryStoreDefaultDefinition, + { + description: "Memory store for agent conversations", + }, + ); + const chatModelUsed = + "chat_model" in memoryStore.definition + ? memoryStore.definition.chat_model + : chatModelDeployment; + console.log( + `Created memory store: ${memoryStore.name} (${memoryStore.id}) using chat model '${chatModelUsed}'`, + ); + + // Configure Memory Search tool to attach to the agent + const memorySearchTool: MemorySearchTool = { + type: "memory_search", + memory_store_name: memoryStore.name, + scope, + update_delay: 1, // wait briefly after conversation inactivity before updating memories + }; + + // Create an agent that will use the Memory Search tool + const agent = await project.agents.createVersion("MemorySearchAgent", { + kind: "prompt", + model: agentModelDeployment, + instructions: + "You are a helpful assistant that remembers user preferences using the memory search tool.", + tools: [memorySearchTool], + }); + agentVersion = { + name: agent.name, + version: agent.version, + }; + console.log(`Agent created (id: ${agent.id}, name: ${agent.name}, version: ${agent.version})`); + + // Start a conversation and provide details the agent should remember + const conversation = await openAIClient.conversations.create(); + conversationId = conversation.id; + console.log(`Conversation started (${conversation.id}). Sending a message to seed memories...`); + + const firstResponse = await openAIClient.responses.create( + { + input: "I prefer dark roast coffee and usually drink it in the morning.", + conversation: conversation.id, + }, + { + body: { agent: { name: agent.name, type: "agent_reference" } }, + }, + ); + console.log(`Initial response: ${firstResponse.output_text}`); + + // Allow time for the memory store to update from this conversation + console.log("Waiting for the memory store to capture the new memory..."); + await delay(60000); + + // Create a follow-up conversation and ask the agent to recall the stored memory + const followUpConversation = await openAIClient.conversations.create(); + followUpConversationId = followUpConversation.id; + console.log(`Follow-up conversation started (${followUpConversation.id}).`); + + const followUpResponse = await openAIClient.responses.create( + { + input: "Can you remind me of my usual coffee order?", + conversation: followUpConversation.id, + }, + { + body: { agent: { name: agent.name, type: "agent_reference" } }, + }, + ); + console.log(`Follow-up response: ${followUpResponse.output_text}`); + } finally { + console.log("\nCleaning up resources..."); + if (conversationId) { + await openAIClient.conversations.delete(conversationId); + console.log(`Conversation ${conversationId} deleted`); + } + if (followUpConversationId) { + await openAIClient.conversations.delete(followUpConversationId); + console.log(`Conversation ${followUpConversationId} deleted`); + } + if (agentVersion) { + await project.agents.deleteVersion(agentVersion.name, agentVersion.version); + console.log("Agent deleted"); + } + try { + await project.memoryStores.delete(memoryStoreName); + console.log("Memory store deleted"); + } catch (error: any) { + if (error?.statusCode !== 404) { + throw error; + } + } + } + + console.log("\nMemory Search agent sample completed!"); +} + +main().catch((err) => { + console.error("The sample encountered an error:", err); +}); diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApi.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApi.ts index 2b78fc0571ba..ff5d62d6467b 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApi.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApi.ts @@ -23,7 +23,7 @@ import * as path from "path"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const weatherSpecPath = path.resolve(__dirname, "../assets", "weather_openapi.json"); function loadOpenApiSpec(specPath: string): unknown { diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApiConnectionAuth.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApiConnectionAuth.ts index 6d9947462cb7..cc17e1968de6 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApiConnectionAuth.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentOpenApiConnectionAuth.ts @@ -23,7 +23,7 @@ import * as path from "path"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const tripAdvisorProjectConnectionId = process.env["TRIPADVISOR_PROJECT_CONNECTION_ID"] || ""; const tripAdvisorSpecPath = path.resolve(__dirname, "../assets", "tripadvisor_openapi.json"); diff --git a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentSharepoint.ts b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentSharepoint.ts index b6fb5e54d83f..f8d60bf2881b 100644 --- a/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentSharepoint.ts +++ b/sdk/ai/ai-projects/samples/v2-beta/typescript/src/agents/tools/agentSharepoint.ts @@ -15,7 +15,7 @@ import { AIProjectClient } from "@azure/ai-projects"; import "dotenv/config"; const projectEndpoint = process.env["AZURE_AI_PROJECT_ENDPOINT"] || ""; -const deploymentName = process.env["AZURE_AI_MODEL_DEPLOYMENT_NAME"] || ""; +const deploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || ""; const sharepointProjectConnectionId = process.env["SHAREPOINT_PROJECT_CONNECTION_ID"] || ""; diff --git a/sdk/ai/ai-projects/src/api/memoryStores/index.ts b/sdk/ai/ai-projects/src/api/memoryStores/index.ts index ac5a33a4bf9a..a93ded24210a 100644 --- a/sdk/ai/ai-projects/src/api/memoryStores/index.ts +++ b/sdk/ai/ai-projects/src/api/memoryStores/index.ts @@ -23,3 +23,7 @@ export { MemoryStoresUpdateMemoryStoreOptionalParams, MemoryStoresCreateMemoryStoreOptionalParams, } from "./options.js"; +export { + MemoryStoreUpdateMemoriesPoller, + MemoryStoreUpdateOperationState, +} from "./memoryStoreUpdateMemoriesPoller.js"; diff --git a/sdk/ai/ai-projects/src/api/memoryStores/memoryStoreUpdateMemoriesPoller.ts b/sdk/ai/ai-projects/src/api/memoryStores/memoryStoreUpdateMemoriesPoller.ts new file mode 100644 index 000000000000..3d3a20f6142e --- /dev/null +++ b/sdk/ai/ai-projects/src/api/memoryStores/memoryStoreUpdateMemoriesPoller.ts @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { AIProjectContext as Client } from "../index.js"; +import { + MemoryStoreUpdateResult, + MemoryStoreUpdateStatus, + MemoryStoreUpdateResponse, + memoryStoreUpdateResponseDeserializer, + MemoryStoreOperationUsage, +} from "../../models/models.js"; +import { + PollerLike, + OperationState, + createHttpPoller, + RunningOperation, + OperationResponse, +} from "@azure/core-lro"; +import { AbortSignalLike } from "@azure/abort-controller"; +import { PathUncheckedResponse, createRestError } from "@azure-rest/core-client"; + +export type MemoryStoreUpdateOperationState = OperationState & { + updateId?: string; + updateStatus?: MemoryStoreUpdateStatus; + supersededBy?: string; +}; + +export type MemoryStoreUpdateMemoriesPoller = PollerLike< + MemoryStoreUpdateOperationState, + MemoryStoreUpdateResult +> & { + readonly updateId?: string; + readonly updateStatus?: MemoryStoreUpdateStatus; + readonly supersededBy?: string; +}; + +export interface CreateMemoryStoreUpdateMemoriesPollerOptions { + updateIntervalInMs?: number; + abortSignal?: AbortSignalLike; + restoreFrom?: string; +} + +const terminalUpdateStatuses: MemoryStoreUpdateStatus[] = ["completed", "superseded"]; + +function createDefaultUsage(): MemoryStoreOperationUsage { + return { + embedding_tokens: 0, + input_tokens: 0, + input_tokens_details: { cached_tokens: 0 }, + output_tokens: 0, + output_tokens_details: { reasoning_tokens: 0 }, + total_tokens: 0, + }; +} + +function toOperationResponse( + response: PathUncheckedResponse, + expectedStatuses: string[], +): OperationResponse { + if (!expectedStatuses.includes(response.status)) { + throw createRestError(response); + } + + return { + flatResponse: response, + rawResponse: { + ...response, + statusCode: Number.parseInt(response.status), + body: response.body, + }, + }; +} + +function deserializeUpdateResponse( + response: PathUncheckedResponse, +): MemoryStoreUpdateResponse | undefined { + try { + return memoryStoreUpdateResponseDeserializer(response.body); + } catch { + return undefined; + } +} + +function applyUpdateState( + state: MemoryStoreUpdateOperationState, + response: PathUncheckedResponse, +): void { + const parsed = deserializeUpdateResponse(response); + state.updateId ??= parsed?.update_id; + state.updateStatus = parsed?.status; + state.supersededBy = parsed?.superseded_by; + + if (!parsed) { + return; + } + + if (parsed.status === "failed") { + state.status = "failed"; + if (!state.error) { + state.error = parsed.error + ? new Error(parsed.error.message ?? "Memory update failed") + : new Error("Memory update failed"); + } + return; + } + + if (terminalUpdateStatuses.includes(parsed.status)) { + state.status = "succeeded"; + state.result = parsed.result ?? + state.result ?? { + memory_operations: [], + usage: createDefaultUsage(), + }; + return; + } + + state.status = "running"; +} + +function buildRunningOperation( + client: Client, + expectedStatuses: string[], + getInitialResponse?: () => PromiseLike, + options?: CreateMemoryStoreUpdateMemoriesPollerOptions, +): RunningOperation { + const pollAbortController = new AbortController(); + return { + sendInitialRequest: async () => { + if (!getInitialResponse) { + throw new Error("getInitialResponse is required when starting a new poller"); + } + const initialResponse = await getInitialResponse(); + return toOperationResponse(initialResponse, expectedStatuses); + }, + sendPollRequest: async (path: string, pollOptions?: { abortSignal?: AbortSignalLike }) => { + // The poll request will both listen to the user provided abort signal and the poller's own abort signal + function abortListener(): void { + pollAbortController.abort(); + } + const abortSignal = pollAbortController.signal; + if (options?.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (pollOptions?.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (!abortSignal.aborted) { + options?.abortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + pollOptions?.abortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + } + + let response: PathUncheckedResponse; + try { + response = (await client.pathUnchecked(path).get({ abortSignal })) as PathUncheckedResponse; + } finally { + options?.abortSignal?.removeEventListener("abort", abortListener); + pollOptions?.abortSignal?.removeEventListener("abort", abortListener); + } + + return toOperationResponse(response, expectedStatuses); + }, + }; +} + +export function createMemoryStoreUpdateMemoriesPoller( + client: Client, + expectedStatuses: string[], + getInitialResponse?: () => PromiseLike, + options: CreateMemoryStoreUpdateMemoriesPollerOptions = {}, +): MemoryStoreUpdateMemoriesPoller { + if (!getInitialResponse && !options?.restoreFrom) { + throw new Error("getInitialResponse is required when starting a new poller"); + } + let initialResponse: PathUncheckedResponse | undefined; + + const poller = createHttpPoller( + buildRunningOperation( + client, + expectedStatuses, + getInitialResponse + ? async () => { + initialResponse = await getInitialResponse(); + return initialResponse; + } + : undefined, + options, + ), + { + intervalInMs: options?.updateIntervalInMs, + restoreFrom: options?.restoreFrom, + updateState: (state, lastResponse) => { + const flatResponse = lastResponse.flatResponse as PathUncheckedResponse; + applyUpdateState(state, flatResponse); + }, + processResult: async (operationResponse, state) => { + applyUpdateState(state, operationResponse as PathUncheckedResponse); + return state.result as MemoryStoreUpdateResult; + }, + }, + ) as MemoryStoreUpdateMemoriesPoller; + + if (initialResponse && poller.operationState) { + applyUpdateState(poller.operationState, initialResponse); + } + + Object.defineProperties(poller, { + updateId: { + enumerable: true, + get: () => poller.operationState?.updateId, + }, + updateStatus: { + enumerable: true, + get: () => poller.operationState?.updateStatus, + }, + supersededBy: { + enumerable: true, + get: () => poller.operationState?.supersededBy, + }, + }); + + return poller; +} diff --git a/sdk/ai/ai-projects/src/api/memoryStores/operations.ts b/sdk/ai/ai-projects/src/api/memoryStores/operations.ts index 2f7e4351bfb4..2e7262a4dbbd 100644 --- a/sdk/ai/ai-projects/src/api/memoryStores/operations.ts +++ b/sdk/ai/ai-projects/src/api/memoryStores/operations.ts @@ -18,8 +18,6 @@ import { memoryStoreSearchResponseDeserializer, MemoryStoreUpdateResponse, memoryStoreUpdateResponseDeserializer, - MemoryStoreUpdateResult, - MemoryStoreUpdateResultDeserializer, MemoryStoreDeleteScopeResponse, memoryStoreDeleteScopeResponseDeserializer, } from "../../models/models.js"; @@ -27,8 +25,11 @@ import { PagedAsyncIterableIterator, buildPagedAsyncIterator, } from "../../static-helpers/pagingHelpers.js"; -import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; import { expandUrlTemplate } from "../../static-helpers/urlTemplate.js"; +import { + createMemoryStoreUpdateMemoriesPoller, + MemoryStoreUpdateMemoriesPoller, +} from "./memoryStoreUpdateMemoriesPoller.js"; import { MemoryStoresDeleteScopeOptionalParams, MemoryStoresGetUpdateResultOptionalParams, @@ -46,7 +47,6 @@ import { createRestError, operationOptionsToRequestParameters, } from "@azure-rest/core-client"; -import { PollerLike, OperationState } from "@azure/core-lro"; export function _deleteScopeSend( context: Client, @@ -182,38 +182,22 @@ export function _updateMemoriesSend( }); } -export async function _updateMemoriesDeserialize( - result: PathUncheckedResponse, -): Promise { - const expectedStatuses = ["202", "200"]; - if (!expectedStatuses.includes(result.status)) { - const error = createRestError(result); - error.details = apiErrorResponseDeserializer(result.body); - throw error; - } - - if (result?.body?.result === undefined) { - throw createRestError( - `Expected a result in the response at position "result.body.result"`, - result, - ); - } - - return MemoryStoreUpdateResultDeserializer(result.body.result); -} - /** Update memory store with conversation memories. */ export function updateMemories( context: Client, name: string, scope: string, options: MemoryStoresUpdateMemoriesOptionalParams = { requestOptions: {} }, -): PollerLike, MemoryStoreUpdateResult> { - return getLongRunningPoller(context, _updateMemoriesDeserialize, ["202", "200"], { - updateIntervalInMs: options?.updateIntervalInMs, - abortSignal: options?.abortSignal, - getInitialResponse: () => _updateMemoriesSend(context, name, scope, options), - }) as PollerLike, MemoryStoreUpdateResult>; +): MemoryStoreUpdateMemoriesPoller { + return createMemoryStoreUpdateMemoriesPoller( + context, + ["202", "200"], + () => _updateMemoriesSend(context, name, scope, options), + { + updateIntervalInMs: options?.updateIntervalInMs, + abortSignal: options?.abortSignal, + }, + ); } export function _searchMemoriesSend( diff --git a/sdk/ai/ai-projects/src/classic/memoryStores/index.ts b/sdk/ai/ai-projects/src/classic/memoryStores/index.ts index dc64faa23a17..4cb540464477 100644 --- a/sdk/ai/ai-projects/src/classic/memoryStores/index.ts +++ b/sdk/ai/ai-projects/src/classic/memoryStores/index.ts @@ -30,11 +30,10 @@ import { DeleteMemoryStoreResponse, MemoryStoreSearchResponse, MemoryStoreUpdateResponse, - MemoryStoreUpdateResult, MemoryStoreDeleteScopeResponse, } from "../../models/models.js"; +import { MemoryStoreUpdateMemoriesPoller } from "../../api/memoryStores/memoryStoreUpdateMemoriesPoller.js"; import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; /** Interface representing a MemoryStores operations. */ export interface MemoryStoresOperations { @@ -55,7 +54,7 @@ export interface MemoryStoresOperations { name: string, scope: string, options?: MemoryStoresUpdateMemoriesOptionalParams, - ) => PollerLike, MemoryStoreUpdateResult>; + ) => MemoryStoreUpdateMemoriesPoller; /** Search for relevant memories from a memory store based on conversation context. */ searchMemories: ( name: string, diff --git a/sdk/ai/ai-projects/src/index.ts b/sdk/ai/ai-projects/src/index.ts index 8262d65b0a6a..9bed70de0a81 100644 --- a/sdk/ai/ai-projects/src/index.ts +++ b/sdk/ai/ai-projects/src/index.ts @@ -404,6 +404,10 @@ export { MemoryStoresUpdateMemoryStoreOptionalParams, MemoryStoresCreateMemoryStoreOptionalParams, } from "./api/memoryStores/index.js"; +export { + MemoryStoreUpdateMemoriesPoller, + MemoryStoreUpdateOperationState, +} from "./api/memoryStores/memoryStoreUpdateMemoriesPoller.js"; export { RedTeamsCreateOptionalParams, RedTeamsListOptionalParams, diff --git a/sdk/ai/ai-projects/src/restorePollerHelpers.ts b/sdk/ai/ai-projects/src/restorePollerHelpers.ts index 0a961506e05b..db852dd0652c 100644 --- a/sdk/ai/ai-projects/src/restorePollerHelpers.ts +++ b/sdk/ai/ai-projects/src/restorePollerHelpers.ts @@ -2,7 +2,10 @@ // Licensed under the MIT License. import { AIProjectClient } from "./aiProjectClient.js"; -import { _updateMemoriesDeserialize } from "./api/memoryStores/operations.js"; +import { + createMemoryStoreUpdateMemoriesPoller, + MemoryStoreUpdateMemoriesPoller, +} from "./api/memoryStores/memoryStoreUpdateMemoriesPoller.js"; import { getLongRunningPoller } from "./static-helpers/pollingHelpers.js"; import { OperationOptions, PathUncheckedResponse } from "@azure-rest/core-client"; import { AbortSignalLike } from "@azure/abort-controller"; @@ -51,9 +54,21 @@ export function restorePoller( const resourceLocationConfig = metadata?.["resourceLocationConfig"] as | ResourceLocationConfig | undefined; - const { deserializer, expectedStatuses = [] } = - getDeserializationHelper(initialRequestUrl, requestMethod) ?? {}; - const deserializeHelper = options?.processResponseBody ?? deserializer; + const helper = getDeserializationHelper(initialRequestUrl, requestMethod); + if (helper?.kind === "memoryUpdate") { + return createMemoryStoreUpdateMemoriesPoller( + (client as any)["_client"] ?? client, + helper.expectedStatuses ?? ["202", "200"], + undefined, + { + updateIntervalInMs: options?.updateIntervalInMs, + abortSignal: options?.abortSignal, + restoreFrom: serializedState, + }, + ) as MemoryStoreUpdateMemoriesPoller as PollerLike, TResult>; + } + const deserializeHelper = options?.processResponseBody ?? helper?.deserializer; + const expectedStatuses = helper?.expectedStatuses ?? []; if (!deserializeHelper) { throw new Error( `Please ensure the operation is in this client! We can't find its deserializeHelper for ${sourceOperation?.name}.`, @@ -75,13 +90,14 @@ export function restorePoller( interface DeserializationHelper { // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - deserializer: Function; - expectedStatuses: string[]; + deserializer?: Function; + expectedStatuses?: string[]; + kind?: "memoryUpdate"; } const deserializeMap: Record = { "POST /memory_stores/{name}:update_memories": { - deserializer: _updateMemoriesDeserialize, + kind: "memoryUpdate", expectedStatuses: ["202", "200"], }, };