Partager via


Bibliothèque de client Azure OpenAI Assistants pour JavaScript - version 1.0.0-beta.5

La bibliothèque cliente Des assistants Azure OpenAI pour JavaScript est une adaptation des API REST d’OpenAI qui fournit une interface idiomatique et une intégration riche avec le reste de l’écosystème du SDK Azure. Il peut se connecter à des ressources Azure OpenAI ou au point de terminaison d’inférence OpenAI non-Azure, ce qui en fait un excellent choix pour le développement non-Azure OpenAI.

Liens clés :

Prise en main

Environnements actuellement pris en charge

Prérequis

Si vous souhaitez utiliser une ressource Azure OpenAI, vous devez disposer d’un abonnement Azure et d’un accès Azure OpenAI. Cela vous permet de créer une ressource Azure OpenAI et d’obtenir à la fois une URL de connexion et des clés API. Pour plus d’informations, consultez Démarrage rapide : Prise en main de la génération de texte à l’aide du service Azure OpenAI.

Si vous souhaitez utiliser la bibliothèque de client JS des Assistants Azure OpenAI pour vous connecter à des applications non-Azure OpenAI, vous aurez besoin d’une clé API à partir d’un compte de développeur à l’adresse https://platform.openai.com/.

Installez le package @azure/openai-assistants

Installez la bibliothèque de client Azure OpenAI Assistants pour JavaScript avec npm:

npm install @azure/openai-assistants

Créez et authentifiez unAssistantsClient

Pour configurer un client à utiliser avec Azure OpenAI, fournissez un URI de point de terminaison valide à une ressource Azure OpenAI, ainsi qu’une clé d’identification, des informations d’identification de jeton ou des informations d’identification d’identité Azure autorisées à utiliser la ressource Azure OpenAI. Pour configurer le client pour qu’il se connecte au service OpenAI, fournissez une clé API à partir du portail des développeurs d’OpenAI.

Utilisation d’une clé API à partir d’Azure

Utilisez le portail Azure pour accéder à votre ressource OpenAI et récupérer une clé API, ou utilisez l’extrait de code Azure CLI ci-dessous :

Note: Parfois, la clé API est appelée « clé d’abonnement » ou « clé API d’abonnement ».

az cognitiveservices account keys list --resource-group <your-resource-group-name> --name <your-resource-name>

Concepts clés

Pour obtenir une vue d’ensemble des concepts et des relations utilisés avec les assistants, consultez la documentation OpenAI sur le fonctionnement des assistants . Cette vue d’ensemble suit de près l’exemple de vue d’ensemble d’OpenAI pour illustrer les principes de base de la création, de l’exécution et de l’utilisation d’assistants et de threads.

Pour commencer, créez un AssistantsClient:

const assistantsClient = new AssistantsClient("<endpoint>", new AzureKeyCredential("<azure_api_key>"));

Avec un client, une assistant peut ensuite être créée. Une assistant est une interface spécialement conçue pour les modèles OpenAI qui peut appeler Des outils tout en autorisant des instructions générales tout au long de la durée de vie de l’assistant.

Code permettant de créer un assistant :

const assistant = await assistantsClient.createAssistant({
  model: "gpt-4-1106-preview",
  name: "JS Math Tutor",
  instructions: "You are a personal math tutor. Write and run code to answer math questions.",
  tools: [{ type: "code_interpreter" }]
});

Une session de conversation entre un Assistant et un utilisateur est appelée thread. Les threads stockent les messages et gèrent automatiquement la troncation pour ajuster le contenu dans le contexte d’un modèle.

Pour créer un thread :

const assistantThread = await assistantsClient.createThread();

Le message représente un message créé par un Assistant ou un utilisateur. Les messages peuvent inclure du texte, des images et d’autres fichiers. Les messages sont stockés sous forme de liste sur le thread. Avec un thread créé, des messages peuvent être créés dessus :

const question = "I need to solve the equation '3x + 11 = 14'. Can you help me?";
const messageResponse = await assistantsClient.createMessage(assistantThread.id, "user", question);

Une exécution représente un appel d’un Assistant sur un thread. L’Assistant utilise sa configuration et les messages du thread pour effectuer des tâches en appelant des modèles et des outils. Dans le cadre d’une exécution, l’Assistant ajoute des messages au thread. Vous pouvez ensuite démarrer une exécution qui évalue le thread par rapport à un assistant :

let runResponse = await assistantsClient.createRun(assistantThread.id, {
   assistantId: assistant.id,
   instructions: "Please address the user as Jane Doe. The user has a premium account." 
});

Une fois l’exécution démarrée, elle doit être interrogée jusqu’à ce qu’elle atteigne un terminal status :

do {
  await new Promise((resolve) => setTimeout(resolve, 800));
  runResponse = await assistantsClient.getRun(assistantThread.id, runResponse.id);
} while (runResponse.status === "queued" || runResponse.status === "in_progress")

En supposant que l’exécution s’est terminée correctement, la liste des messages du thread qui a été exécuté reflète désormais les nouvelles informations ajoutées par le assistant :

const runMessages = await assistantsClient.listMessages(assistantThread.id);
for (const runMessageDatum of runMessages.data) {
  for (const item of runMessageDatum.content) {
    if (item.type === "text") {
      console.log(item.text.value);
    } else if (item.type === "image_file") {
      console.log(item.imageFile.fileId);
    }
  }
}

Exemple de sortie de cette séquence :

2023-11-14 20:21:23 -  assistant: The solution to the equation \(3x + 11 = 14\) is \(x = 1\).
2023-11-14 20:21:18 -       user: I need to solve the equation `3x + 11 = 14`. Can you help me?

Utilisation de fichiers à des fins de récupération

Les fichiers peuvent être chargés, puis référencés par des assistants ou des messages. Tout d’abord, utilisez l’API de chargement généralisée avec un objectif « assistants » pour rendre un ID de fichier disponible :

const filename = "<path_to_text_file>";
await fs.writeFile(filename, "The word 'apple' uses the code 442345, while the word 'banana' uses the code 673457.", "utf8");
const uint8array = await fs.readFile(filename);
const uploadAssistantFile = await assistantsClient.uploadFile(uint8array, "assistants", { filename });

Une fois chargé, l’ID de fichier peut être fourni à un assistant lors de sa création. Notez que les ID de fichier ne seront utilisés que si un outil approprié comme l’interpréteur de code ou la récupération est activé.

const fileAssistant = await assistantsClient.createAssistant({
  model: "gpt-4-1106-preview",
  name: "JS SDK Test Assistant - Retrieval",
  instructions: "You are a helpful assistant that can help fetch data from files you know about.",
  tools: [{ type: "retrieval" }],
  fileIds: [ uploadAssistantFile.id ]
});

Une fois qu’une association d’ID de fichier et un outil pris en charge sont activés, le assistant pourra ensuite consommer les données associées lors de l’exécution de threads.

Utilisation d’outils de fonction et appel de fonction parallèle

Comme décrit dans la documentation d’OpenAI pour assistant outils, les outils qui référencent des fonctionnalités définies par l’appelant en tant que fonctions peuvent être fournis à un assistant pour lui permettre de résoudre et de lever l’ambiguïté dynamiquement pendant une exécution.

Voici un assistant simple qui « sait comment », via les fonctions fournies par l’appelant :

  1. Obtenir la ville préférée de l’utilisateur
  2. Obtenir un surnom pour une ville donnée
  3. Obtenir la météo actuelle, éventuellement avec une unité de température, dans une ville

Pour ce faire, commencez par définir les fonctions à utiliser : les implémentations réelles ne sont ici que des stubs représentatifs.

// Example of a function that defines no parameters
const getFavoriteCity = () => "Atlanta, GA";
const getUserFavoriteCityTool = { 
  type: "function",
  function: {
    name: "getUserFavoriteCity",
    description: "Gets the user's favorite city.",
    parameters: {
      type: "object",
      properties: {}
    }
  }
}; 

// Example of a function with a single required parameter
const getCityNickname = (city) => { 
  switch (city) { 
    case "Atlanta, GA": 
      return "The ATL"; 
    case "Seattle, WA": 
      return "The Emerald City"; 
    case "Los Angeles, CA":
      return "LA"; 
    default: 
      return "Unknown"; 
  }
};

const getCityNicknameTool = { 
  type: "function",
  function: {
    name: "getCityNickname",
    description: "Gets the nickname for a city, e.g. 'LA' for 'Los Angeles, CA'.",
    parameters: { 
      type: "object",
      properties: { 
        city: {
          type: "string",
          description: "The city and state, e.g. San Francisco, CA"
        } 
      }
    }
  }
};

// Example of a function with one required and one optional, enum parameter
const getWeatherAtLocation = (location, temperatureUnit = "f") => {
  switch (location) { 
    case "Atlanta, GA": 
      return temperatureUnit === "f" ? "84f" : "26c"; 
    case "Seattle, WA": 
      return temperatureUnit === "f" ? "70f" : "21c"; 
    case "Los Angeles, CA":
      return temperatureUnit === "f" ? "90f" : "28c"; 
    default: 
      return "Unknown"; 
  }
};

const getWeatherAtLocationTool = { 
  type: "function",
  function: {
    name: "getWeatherAtLocation",
    description: "Gets the current weather at a provided location.",
    parameters: { 
      type: "object",
      properties: { 
        location: {
          type: "string",
          description: "The city and state, e.g. San Francisco, CA"
        },
        temperatureUnit: {
          type: "string",
          enum: ["f", "c"],
        }
      },
      required: ["location"]
    }
  }
};

Avec les fonctions définies dans leurs outils appropriés, vous pouvez maintenant créer un assistant pour lequel ces outils sont activés :

  const weatherAssistant = await assistantsClient.createAssistant({
  // note: parallel function calling is only supported with newer models like gpt-4-1106-preview
  model: "gpt-4-1106-preview",
  name: "JS SDK Test Assistant - Weather",
  instructions: `You are a weather bot. Use the provided functions to help answer questions.
    Customize your responses to the user's preferences as much as possible and use friendly
    nicknames for cities whenever possible.
  `,
  tools: [getUserFavoriteCityTool, getCityNicknameTool, getWeatherAtLocationTool]
});

Si le assistant appelle des outils, le code appelant doit résoudre ToolCall les instances en instances correspondantesToolOutputSubmission. Pour des raisons pratiques, un exemple de base est extrait ici :

const getResolvedToolOutput = (toolCall) => {
  const toolOutput = { toolCallId: toolCall.id };

  if (toolCall["function"]) {
    const functionCall = toolCall["function"];
    const functionName = functionCall.name;
    const functionArgs = JSON.parse(functionCall["arguments"] ?? {});

    switch (functionName) {
      case "getUserFavoriteCity":
        toolOutput.output = getFavoriteCity();
        break;
      case "getCityNickname":
        toolOutput.output = getCityNickname(functionArgs["city"]);
        break;
      case "getWeatherAtLocation":
        toolOutput.output = getWeatherAtLocation(functionArgs.location, functionArgs.temperatureUnit);
        break;
      default:
        toolOutput.output = `Unknown function: ${functionName}`;
        break;
    }
  }
  return toolOutput;
};

Pour gérer les entrées utilisateur telles que « Quelle est la météo en ce moment dans ma ville préférée ? », l’interrogation de la réponse pour l’achèvement doit être complétée par un RunStatus case activée pour RequiresAction ou, dans ce cas, la présence de la RequiredAction propriété lors de l’exécution. Ensuite, la collection de ToolOutputSubmissions doit être envoyée à l’exécution via la SubmitRunToolOutputs méthode afin que l’exécution puisse continuer :

const question = "What's the weather like right now in my favorite city?";
let runResponse = await assistantsClient.createThreadAndRun({ 
  assistantId: weatherAssistant.id, 
  thread: { messages: [{ role: "user", content: question }] },
  tools: [getUserFavoriteCityTool, getCityNicknameTool, getWeatherAtLocationTool]
});

do {
  await new Promise((resolve) => setTimeout(resolve, 500));
  runResponse = await assistantsClient.getRun(runResponse.threadId, runResponse.id);
  
  if (runResponse.status === "requires_action" && runResponse.requiredAction.type === "submit_tool_outputs") {
    const toolOutputs = [];

    for (const toolCall of runResponse.requiredAction.submitToolOutputs.toolCalls) {
      toolOutputs.push(getResolvedToolOutput(toolCall));
    }
    runResponse = await assistantsClient.submitToolOutputsToRun(runResponse.threadId, runResponse.id, toolOutputs);
  }
} while (runResponse.status === "queued" || runResponse.status === "in_progress")

Notez que, lorsque vous utilisez des modèles pris en charge, le assistant peut demander que plusieurs fonctions soient appelées en parallèle. Les modèles plus anciens ne peuvent appeler qu’une seule fonction à la fois.

Une fois que tous les appels de fonction nécessaires ont été résolus, l’exécution se poursuit normalement et les messages terminés sur le thread contiennent une sortie de modèle complétée par les sorties de l’outil de fonction fournis.

Résolution des problèmes

Journalisation

L’activation de la journalisation peut vous aider à mieux comprendre les échecs. Pour avoir un journal des requêtes et réponses HTTP, définissez la variable d’environnement AZURE_LOG_LEVEL sur info. Vous pouvez également activer la journalisation au moment de l’exécution en appelant setLogLevel dans @azure/logger :

const { setLogLevel } = require("@azure/logger");

setLogLevel("info");

Pour obtenir des instructions plus détaillées sur l’activation des journaux, consultez les documents relatifs au package @azure/logger.