Condividi tramite


Libreria client di Azure OpenAI Assistants per JavaScript - versione 1.0.0-beta.5

La libreria client di Azure OpenAI Assistants per JavaScript è un adattamento delle API REST di OpenAI che fornisce un'interfaccia idiotica e un'integrazione avanzata con il resto dell'ecosistema di Azure SDK. Può connettersi alle risorse OpenAI di Azure o all'endpoint di inferenza OpenAI di Azure, rendendola una scelta ottimale anche per lo sviluppo di Azure OpenAI.

Collegamenti principali:

Introduzione

Ambienti attualmente supportati

Prerequisiti

Se si vuole usare una risorsa OpenAI di Azure, è necessario avere una sottoscrizione di Azure e l'accesso a Azure OpenAI. In questo modo sarà possibile creare una risorsa OpenAI di Azure e ottenere sia un URL di connessione che le chiavi API. Per altre informazioni, vedere Avvio rapido: Introduzione alla generazione di testo usando il servizio OpenAI di Azure.

Se si vuole usare la libreria client JS di Azure OpenAI Assistants per connettersi a non Azure OpenAI, sarà necessaria una chiave API da un account sviluppatore all'indirizzo https://platform.openai.com/.

Installare il pacchetto @azure/openai-assistants

Installare la libreria client di Azure OpenAI Assistants per JavaScript con npm:

npm install @azure/openai-assistants

Creare e autenticare un oggetto AssistantsClient

Per configurare un client da usare con Azure OpenAI, fornire un URI di endpoint valido a una risorsa OpenAI di Azure insieme a una credenziale chiave corrispondente, credenziali del token o credenziali dell'identità di Azure autorizzata a usare la risorsa Azure OpenAI. Per configurare invece il client per connettersi al servizio OpenAI, specificare una chiave API dal portale per sviluppatori di OpenAI.

Uso di una chiave API da Azure

Usare il portale di Azure per passare alla risorsa OpenAI e recuperare una chiave API oppure usare il frammento di interfaccia della riga di comando di Azure seguente:

Nota: A volte la chiave API viene definita "chiave di sottoscrizione" o "chiave API di sottoscrizione".

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

Concetti chiave

Per una panoramica dei concetti e delle relazioni usate con gli assistenti, vedere la documentazione "how assistants work" (Come funzionano gli assistenti) per una panoramica dei concetti e delle relazioni usate con gli assistenti. Questa panoramica segue attentamente l'esempio di panoramica di OpenAI per illustrare le nozioni di base della creazione, dell'esecuzione e dell'uso di assistenti e thread.

Per iniziare, creare un AssistantsClientoggetto :

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

Con un client è possibile creare un assistente. Un assistente è un'interfaccia predefinita per i modelli OpenAI che possono chiamare Strumenti, consentendo istruzioni di alto livello durante tutta la durata del assistente.

Codice per creare un assistente:

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" }]
});

Una sessione di conversazione tra un assistente e un utente viene chiamata thread. I thread archiviano messaggi e gestiscono automaticamente il troncamento per adattare il contenuto al contesto di un modello.

Per creare un thread:

const assistantThread = await assistantsClient.createThread();

Il messaggio rappresenta un messaggio creato da un assistente o da un utente. I messaggi possono includere testo, immagini e altri file. I messaggi vengono archiviati come elenco nel thread. Con un thread creato, i messaggi possono essere creati su di esso:

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

Un oggetto Run rappresenta una chiamata di un assistente in un thread. L'assistente usa la configurazione e i messaggi del thread per eseguire attività chiamando modelli e strumenti. Nell'ambito di un'esecuzione, l'assistente aggiunge messaggi al thread. Un'esecuzione può quindi essere avviata che valuta il thread rispetto a un assistente:

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

Dopo l'avvio dell'esecuzione, deve quindi essere eseguito il polling finché non raggiunge lo stato del terminale:

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

Supponendo che l'esecuzione sia stata completata correttamente, elencare i messaggi dal thread eseguito rifletterà ora nuove informazioni aggiunte dalla assistente:

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);
    }
  }
}

Output di esempio da questa sequenza:

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?

Uso dei file per il recupero

I file possono essere caricati e quindi a cui si fa riferimento da assistenti o messaggi. Usare prima di tutto l'API di caricamento generalizzata con lo scopo di "assistenti" per rendere disponibile un ID file:

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 });

Dopo il caricamento, l'ID file può quindi essere fornito a un assistente al momento della creazione. Si noti che gli ID file verranno usati solo se è abilitato uno strumento appropriato come Interprete codice o Recupero.

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 ]
});

Con un'associazione di ID file e uno strumento supportato abilitato, il assistente sarà quindi in grado di utilizzare i dati associati durante l'esecuzione di thread.

Uso di strumenti per le funzioni e chiamata a funzioni parallele

Come descritto nella documentazione di OpenAI per gli strumenti di assistente, gli strumenti che fanno riferimento alle funzionalità definite dal chiamante come funzioni possono essere fornite a un assistente per consentire la risoluzione dinamica e la disambiguazione durante un'esecuzione.

Ecco una semplice assistente che "sa come", tramite le funzioni fornite dal chiamante:

  1. Ottenere la città preferita dell'utente
  2. Ottenere un soprannome per una determinata città
  3. Ottenere il tempo corrente, facoltativamente con un'unità di temperatura, in una città

A tale scopo, iniziare definendo le funzioni da usare: le implementazioni effettive sono semplicemente stub rappresentativi.

// 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"]
    }
  }
};

Con le funzioni definite negli strumenti appropriati, è ora possibile creare un assistente che dispone di tali strumenti abilitati:

  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]
});

Se l'assistente chiama strumenti, il codice chiamante dovrà risolvere ToolCall le istanze in istanze corrispondentiToolOutputSubmission. Per praticità, un esempio di base viene estratto qui:

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;
};

Per gestire l'input dell'utente come "qual è il meteo come adesso nella mia città preferita?", il polling della risposta per il completamento deve essere completato da un RunStatus controllo RequiresAction o, in questo caso, la presenza della proprietà sull'esecuzione RequiredAction . La raccolta di ToolOutputSubmissions deve quindi essere inviata all'esecuzione tramite il SubmitRunToolOutputs metodo in modo che l'esecuzione possa continuare:

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")

Si noti che, quando si usano modelli supportati, l'assistente può richiedere che diverse funzioni vengano chiamate in parallelo. I modelli meno recenti possono chiamare una sola funzione alla volta.

Una volta risolte tutte le chiamate di funzione necessarie, l'esecuzione procederà normalmente e i messaggi completati nel thread conterrà l'output del modello integrato dagli output degli strumenti di funzione forniti.

Risoluzione dei problemi

Registrazione

L'abilitazione della registrazione consente di individuare informazioni utili sugli errori. Per visualizzare un log di richieste e risposte HTTP, impostare la variabile di ambiente AZURE_LOG_LEVEL su info. In alternativa, la registrazione può essere abilitata in fase di esecuzione chiamando setLogLevel in @azure/logger:

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

setLogLevel("info");

Per istruzioni più dettagliate su come abilitare i log, è possibile esaminare la documentazione del pacchetto @azure/logger.