Share via


Biblioteca cliente de Azure OpenAI Assistants para JavaScript: versión 1.0.0-beta.5

La biblioteca cliente de Azure OpenAI Assistants para JavaScript es una adaptación de las API REST de OpenAI que proporciona una interfaz idiomática e integración enriquecida con el resto del ecosistema del SDK de Azure. Puede conectarse a recursos de Azure OpenAI o al punto de conexión de inferencia de OpenAI que no sea de Azure, lo que lo convierte en una excelente opción para el desarrollo de OpenAI que no sea de Azure.

Vínculos principales:

Introducción

Entornos admitidos actualmente

Requisitos previos

Si desea usar un recurso de Azure OpenAI, debe tener una suscripción de Azure y acceso a Azure OpenAI. Esto le permitirá crear un recurso de Azure OpenAI y obtener una dirección URL de conexión, así como claves de API. Para más información, consulte Inicio rápido: Introducción a la generación de texto mediante el servicio Azure OpenAI.

Si desea usar la biblioteca cliente de JS de Azure OpenAI Assistants para conectarse a openAI que no sea de Azure, necesitará una clave de API de una cuenta de desarrollador en https://platform.openai.com/.

Instalar el paquete @azure/openai-assistants

Instale la biblioteca cliente de Azure OpenAI Assistants para JavaScript con npm:

npm install @azure/openai-assistants

Crear y autenticar una AssistantsClient

Para configurar un cliente para su uso con Azure OpenAI, proporcione un URI de punto de conexión válido a un recurso de Azure OpenAI junto con una credencial de clave, una credencial de token o una credencial de identidad de Azure autorizada para usar el recurso de Azure OpenAI. Para configurar en su lugar el cliente para conectarse al servicio de OpenAI, proporcione una clave de API desde el portal para desarrolladores de OpenAI.

Uso de una clave de API de Azure

Use Azure Portal para ir al recurso de OpenAI y recuperar una clave de API, o bien use el fragmento de código de la CLI de Azure siguiente:

Nota: A veces, la clave de API se conoce como "clave de suscripción" o "clave de API de suscripción".

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

Conceptos clave

Consulte la documentación de "cómo funcionan los asistentes" de OpenAI para obtener información general sobre los conceptos y las relaciones que se usan con los asistentes. Esta introducción sigue estrechamente el ejemplo de información general de OpenAI para mostrar los conceptos básicos de creación, ejecución y uso de asistentes y subprocesos.

Para empezar, cree un AssistantsClient:

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

Con un cliente, se puede crear un asistente. Un asistente es una interfaz creada específicamente para los modelos openAI que pueden llamar a Herramientas, al tiempo que permite instrucciones de alto nivel durante toda la vigencia del asistente.

Código para crear un asistente:

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 sesión de conversación entre un Asistente y un usuario se denomina Subproceso. Los subprocesos almacenan mensajes y controlan automáticamente el truncamiento para ajustarse al contenido en el contexto de un modelo.

Para crear un subproceso:

const assistantThread = await assistantsClient.createThread();

El mensaje representa un mensaje creado por un Asistente o un usuario. Los mensajes pueden incluir texto, imágenes y otros archivos. Los mensajes se almacenan como una lista en el subproceso. Con un subproceso creado, se pueden crear mensajes en él:

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

Una ejecución representa una invocación de un Asistente en un subproceso. El Asistente usa la configuración y los mensajes del subproceso para realizar tareas mediante una llamada a modelos y herramientas. Como parte de una ejecución, el Asistente anexa mensajes al subproceso. A continuación, se puede iniciar una ejecución que evalúe el subproceso con un asistente:

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

Una vez iniciada la ejecución, se debe sondear hasta que alcance un estado 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")

Suponiendo que la ejecución se completó correctamente, enumerar los mensajes del subproceso que se ejecutó ahora reflejarán la nueva información agregada por el asistente:

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

Salida de ejemplo de esta secuencia:

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?

Trabajar con archivos para la recuperación

Los archivos se pueden cargar y, a continuación, se puede hacer referencia a ellos mediante asistentes o mensajes. En primer lugar, use la API de carga generalizada con un propósito de "asistentes" para que un identificador de archivo esté 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 });

Una vez cargado, el identificador de archivo se puede proporcionar a un asistente tras la creación. Tenga en cuenta que los identificadores de archivo solo se usarán si está habilitada una herramienta adecuada, como el intérprete de código o la recuperación.

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 una asociación de identificador de archivo y una herramienta compatible habilitada, el asistente podrá consumir los datos asociados al ejecutar subprocesos.

Uso de herramientas de función y llamadas a funciones paralelas

Como se describe en la documentación de OpenAI para asistente herramientas, las herramientas que hacen referencia a las funcionalidades definidas por el autor de la llamada como funciones se pueden proporcionar a un asistente para permitir que se resuelva y desambigua dinámicamente durante una ejecución.

Aquí, se describe una asistente sencilla que "sabe cómo", a través de funciones proporcionadas por el autor de la llamada:

  1. Obtener la ciudad favorita del usuario
  2. Obtener un alias para una ciudad determinada
  3. Obtener el tiempo actual, opcionalmente con una unidad de temperatura, en una ciudad

Para ello, empiece por definir las funciones que se van a usar; las implementaciones reales aquí son simplemente códigos auxiliares 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"]
    }
  }
};

Con las funciones definidas en sus herramientas adecuadas, ahora se puede crear un asistente con esas herramientas habilitadas:

  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 el asistente llama a las herramientas, el código de llamada tendrá que resolver ToolCall instancias en instancias coincidentesToolOutputSubmission. Para mayor comodidad, aquí se extrae un ejemplo básico:

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 controlar la entrada del usuario como "¿cuál es el clima en este momento en mi ciudad favorita?", sondear la respuesta para la finalización debe complementarse mediante una RunStatus comprobación de o RequiresAction , en este caso, la presencia de la RequiredAction propiedad en la ejecución. A continuación, la colección de ToolOutputSubmissions debe enviarse a la ejecución mediante el SubmitRunToolOutputs método para que la ejecución pueda 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")

Tenga en cuenta que, al usar modelos compatibles, el asistente puede solicitar que se llame a varias funciones en paralelo. Los modelos más antiguos solo pueden llamar a una función cada vez.

Una vez resueltas todas las llamadas de función necesarias, la ejecución continuará normalmente y los mensajes completados en el subproceso contendrán la salida del modelo complementada por las salidas de la herramienta de función proporcionadas.

Solución de problemas

Registro

La habilitación del registro puede ayudar a descubrir información útil sobre los errores. Para ver un registro de solicitudes y respuestas HTTP, establezca la variable de entorno AZURE_LOG_LEVEL en info. Como alternativa, el registro se puede habilitar en tiempo de ejecución llamando a setLogLevel en @azure/logger:

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

setLogLevel("info");

Para obtener instrucciones más detalladas sobre cómo habilitar los registros, consulte los documentos del paquete @azure/logger.