Partilhar via


Biblioteca de cliente dos Assistentes OpenAI do Azure para JavaScript – versão 1.0.0-beta.5

A biblioteca de cliente dos Assistentes OpenAI do Azure para JavaScript é uma adaptação das APIs REST da OpenAI que fornece uma interface idiomática e uma integração avançada com o resto do ecossistema do SDK do Azure. Pode ligar-se aos recursos do Azure OpenAI ou ao ponto final de inferência não Azure OpenAI, tornando-o uma ótima opção para o desenvolvimento não Azure OpenAI.

Ligações principais:

Introdução

Ambientes atualmente suportados

Pré-requisitos

Se quiser utilizar um recurso do Azure OpenAI, tem de ter uma subscrição do Azure e acesso ao Azure OpenAI. Isto irá permitir-lhe criar um recurso do Azure OpenAI e obter um URL de ligação, bem como chaves de API. Para obter mais informações, veja Início Rápido: Começar a gerar texto com o Serviço OpenAI do Azure.

Se quiser utilizar a biblioteca de cliente JS dos Assistentes OpenAI do Azure para ligar ao OpenAI não Azure, precisará de uma chave de API de uma conta de programador em https://platform.openai.com/.

Instalar o pacote @azure/openai-assistants

Instale a biblioteca de cliente dos Assistentes OpenAI do Azure para JavaScript com npm:

npm install @azure/openai-assistants

Criar e autenticar um AssistantsClient

Para configurar um cliente para utilização com o Azure OpenAI, forneça um URI de ponto final válido para um recurso do Azure OpenAI juntamente com uma credencial de chave, credencial de token ou credencial de identidade do Azure que esteja autorizada a utilizar o recurso do Azure OpenAI. Em vez disso, para configurar o cliente para ligar ao serviço da OpenAI, forneça uma chave de API a partir do portal de programador da OpenAI.

Utilizar uma Chave de API do Azure

Utilize o Portal do Azure para navegar para o recurso OpenAI e obter uma chave de API ou utilize o fragmento da CLI do Azure abaixo:

Nota: Por vezes, a chave de API é referida como uma "chave de subscrição" ou "chave de API de subscrição".

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

Conceitos-chave

Veja a documentação "como funcionam os assistentes" da OpenAI para obter uma descrição geral dos conceitos e relações utilizados com os assistentes. Esta descrição geral segue de perto o exemplo de descrição geral da OpenAI para demonstrar as noções básicas de criação, execução e utilização de assistentes e threads.

Para começar, crie um AssistantsClient:

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

Com um cliente, pode criar um assistente. Um assistente é uma interface criada de propósito para modelos OpenAI que pode chamar Ferramentas ao mesmo tempo que permite instruções de alto nível ao longo da duração do assistente.

O código para criar uma 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" }]
});

Uma sessão de conversação entre um Assistente e um utilizador chama-se Thread. Os threads armazenam Mensagens e processam automaticamente a truncagem para ajustar o conteúdo ao contexto de um modelo.

Para criar um thread:

const assistantThread = await assistantsClient.createThread();

A mensagem representa uma mensagem criada por um Assistente ou um utilizador. As mensagens podem incluir texto, imagens e outros ficheiros. As mensagens são armazenadas como uma lista no Thread. Com um thread criado, as mensagens podem ser criadas no mesmo:

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

Uma Execução representa uma invocação de um Assistente num Thread. O Assistente utiliza a configuração e as Mensagens do Thread para realizar tarefas ao chamar modelos e ferramentas. Como parte de uma Execução, o Assistente acrescenta Mensagens ao Tópico. Em seguida, pode ser iniciada uma execução que avalia o thread relativamente a um 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." 
});

Assim que a execução for iniciada, deverá ser consultada até atingir o estado do terminal:

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

Partindo do princípio de que a execução foi concluída com êxito, a listagem de mensagens do thread que foi executado irá agora refletir novas informações adicionadas pelo 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);
    }
  }
}

Saída de exemplo desta sequência:

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?

Trabalhar com ficheiros para obtenção

Os ficheiros podem ser carregados e, em seguida, referenciados por assistentes ou mensagens. Primeiro, utilize a API de carregamento generalizada com um objetivo de "assistentes" para disponibilizar um ID de ficheiro:

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

Depois de carregado, o ID do ficheiro pode ser fornecido a uma assistente após a criação. Tenha em atenção que os IDs de ficheiro só serão utilizados se uma ferramenta adequada, como o Interpretador de Código ou a Obtenção, estiver ativada.

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

Com uma associação de ID de ficheiro e uma ferramenta suportada ativada, o assistente poderá consumir os dados associados ao executar threads.

Utilizar ferramentas de função e chamadas de funções paralelas

Conforme descrito na documentação da OpenAI para ferramentas de assistente, as ferramentas que referenciam as capacidades definidas pelo autor da chamada como funções podem ser fornecidas a um assistente para permitir que resolva e desambiguar dinamicamente durante uma execução.

Aqui, delineado é um assistente simples que "sabe como fazê-lo", através de funções fornecidas pelo autor da chamada:

  1. Obter a cidade favorita do utilizador
  2. Obter uma alcunha para uma determinada cidade
  3. Obter o tempo atual, opcionalmente com uma unidade de temperatura, numa cidade

Para tal, comece por definir as funções a utilizar. As implementações reais aqui são apenas stubs representativos.

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

Com as funções definidas nas ferramentas adequadas, pode agora ser criada uma assistente que tenha essas ferramentas ativadas:

  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 o assistente chamar ferramentas, o código de chamada terá de resolver ToolCall instâncias em instâncias correspondentesToolOutputSubmission. Para sua comodidade, é extraído um exemplo básico aqui:

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

Para lidar com entradas de utilizadores como "como a meteorologia neste momento na minha cidade favorita?", consultar a resposta para conclusão deve ser complementada por uma RunStatus verificação RequiresAction ou, neste caso, pela presença da RequiredAction propriedade em execução. Em seguida, a coleção de ToolOutputSubmissions deve ser submetida para a execução através do SubmitRunToolOutputs método para que a execução possa continuar:

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

Tenha em atenção que, ao utilizar modelos suportados, o assistente pode pedir que várias funções sejam chamadas em paralelo. Os modelos mais antigos só podem chamar uma função de cada vez.

Assim que todas as chamadas de função necessárias tiverem sido resolvidas, a execução prosseguirá normalmente e as mensagens concluídas no thread conterão a saída do modelo complementada pelas saídas da ferramenta de função fornecida.

Resolução de problemas

Registo

Ativar o registo pode ajudar a descobrir informações úteis sobre falhas. Para ver um registo de pedidos e respostas HTTP, defina a variável de AZURE_LOG_LEVEL ambiente como info. Em alternativa, o registo pode ser ativado no runtime ao chamar setLogLevel no @azure/logger:

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

setLogLevel("info");

Para obter instruções mais detalhadas sobre como ativar os registos, pode ver os documentos do pacote @azure/logger.