你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

适用于 JavaScript 的 Azure OpenAI 助手客户端库 - 版本 1.0.0-beta.5

适用于 JavaScript 的 Azure OpenAI 助手客户端库是 OpenAI REST API 的改编,它提供一个惯用的接口,并与 Azure SDK 生态系统的其余部分进行丰富的集成。 它可以连接到 Azure OpenAI 资源或非 Azure OpenAI 推理终结点,因此即使是非 Azure OpenAI 开发,也是一个不错的选择。

关键链接:

入门

目前支持的环境

先决条件

若要使用 Azure OpenAI 资源,必须具有 Azure 订阅Azure OpenAI 访问权限。 这将允许你创建 Azure OpenAI 资源,并获取连接 URL 和 API 密钥。 有关详细信息,请参阅 快速入门:开始使用 Azure OpenAI 服务生成文本

如果要使用 Azure OpenAI Assistants JS 客户端库连接到非 Azure OpenAI,则需要来自 开发人员帐户的 API 密钥。https://platform.openai.com/

安装 @azure/openai-assistants

使用 npm安装适用于 JavaScript 的 Azure OpenAI Assistants 客户端库:

npm install @azure/openai-assistants

创建 AssistantsClient 并对其进行身份验证

若要配置客户端以与 Azure OpenAI 配合使用,请向 Azure OpenAI 资源提供有效的终结点 URI,以及有权使用 Azure OpenAI 资源的相应密钥凭据、令牌凭据或 Azure 标识凭据。 若要改为将客户端配置为连接到 OpenAI 的服务,请从 OpenAI 的开发人员门户提供 API 密钥。

使用 Azure 中的 API 密钥

使用 Azure 门户 浏览到 OpenAI 资源并检索 API 密钥,或使用下面的 Azure CLI 代码片段:

注意: 有时,API 密钥称为“订阅密钥”或“订阅 API 密钥”。

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

关键概念

有关与助手使用的概念和关系的概述,请参阅 OpenAI 的“助手工作原理” 文档。 本概述紧跟 OpenAI 的概述示例 ,演示了创建、运行和使用助手和线程的基础知识。

若要开始,请创建 AssistantsClient

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

然后,使用客户端可以创建助手。 助手是 OpenAI 模型的专用接口,可以调用工具,同时允许在助手的整个生命周期内执行高级指令。

用于创建助手的代码:

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

助手和用户之间的对话会话称为线程。 线程存储消息并自动处理截断,使内容适合模型上下文。

创建线程:

const assistantThread = await assistantsClient.createThread();

消息表示由助理或用户创建的消息。 消息可以包括文本、图像和其他文件。 消息作为列表存储在线程上。 创建线程后,可以在该线程上创建消息:

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

运行表示对线程上的助手的调用。 助手使用它的配置和线程的消息通过调用模型和工具来执行任务。 作为“运行”的一部分,助手会将消息追加到线程。 然后,可以启动针对助手评估线程的运行:

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

运行开始后,应轮询该运行,直到达到终端状态:

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

假设运行已成功完成,列出运行线程的消息现在将反映助手添加的新信息:

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

此序列的示例输出:

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?

使用文件进行检索

可以上传文件,然后由助手或邮件引用。 首先,使用具有“助手”用途的通用上传 API 使文件 ID 可用:

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

上传后,文件 ID 可以在创建时提供给助手。 请注意,仅当启用了适当的工具(如代码解释器或检索)时,才会使用文件 ID。

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

启用文件 ID 关联和受支持的工具后,助手在运行线程时能够使用关联的数据。

使用函数工具和并行函数调用

OpenAI 助手 工具文档中所述,可将引用调用方定义功能的工具作为函数提供给助手,以允许它在运行过程中动态解析和消除歧义。

下面概述的是一个简单的助手,它通过调用方提供的函数“知道如何”:

  1. 获取用户最喜欢的城市
  2. 获取给定城市的昵称
  3. 获取城市中的当前天气(可选使用温度单位)

为此,请首先定义要使用的函数 -- 此处的实际实现只是具有代表性的存根。

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

在相应的工具中定义了函数后,现在可以创建启用了这些工具的助手:

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

如果助手调用工具,则调用代码需要将实例解析ToolCall为匹配ToolOutputSubmission的实例。 为方便起见,此处提取了一个基本示例:

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

若要处理用户输入,例如“我最喜欢的城市现在天气如何?”,轮询响应的完成情况应辅以RunStatus检查RequiresAction,在本例中为运行中的属性。RequiredAction 然后,应通过 SubmitRunToolOutputs 方法将 的ToolOutputSubmissions集合提交到运行,以便运行可以继续:

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

请注意,使用支持的模型时,助手可能会请求并行调用多个函数。 旧模型一次只能调用一个函数。

解决所有必需的函数调用后,运行将正常进行,线程上已完成的消息将包含由提供的函数工具输出补充的模型输出。

疑难解答

日志记录

启用日志记录可能有助于发现有关故障的有用信息。 若要查看 HTTP 请求和响应的日志,请将 AZURE_LOG_LEVEL 环境变量设置为 info。 或者,可以在运行时通过调用 @azure/logger 中的 setLogLevel 来启用日志记录:

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

setLogLevel("info");

有关如何启用日志的更详细说明,请查看 @azure/logger 包文档