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

适用于 JavaScript 的 Azure AI Projects 客户端库 - 版本 1.0.0-beta.2

使用 AI Projects 客户端库(预览版)可以:

  • 枚举 Azure AI Foundry 项目中 的连接并获取连接属性。 例如,获取与 Azure OpenAI 连接关联的推理终结点 URL 和凭据。
  • 使用 Azure AI 代理服务开发代理,利用 OpenAI、Microsoft 和其他 LLM 提供商提供的大量模型、工具和功能生态系统。 Azure AI 代理服务支持为各种生成式 AI 用例构建代理。 该包目前以个人预览版提供。
  • 启用 OpenTelemetry 跟踪

产品文档

目录

开始

先决条件

  • LTS 版本的 Node.js
  • Azure 订阅
  • Azure AI Foundry中的 项目。
  • 项目连接字符串。 可以在 Azure AI Foundry 项目概述页的“项目详细信息”下找到它。 下面假设定义了环境变量 AZURE_AI_PROJECTS_CONNECTION_STRING 来保存此值。
  • 需要 Entra ID 才能对客户端进行身份验证。 应用程序需要实现 TokenCredential 接口的对象。 此处的代码示例使用 DefaultAzureCredential。 若要使该工作正常,需要:
    • Contributor 角色。 可以通过 Azure 门户中 Azure AI 项目资源的“访问控制(IAM)”选项卡完成分配的角色。
    • 已安装 Azure CLI
    • 通过运行 az login登录到 Azure 帐户。
    • 请注意,如果有多个 Azure 订阅,则包含 Azure AI 项目资源的订阅必须是默认订阅。 运行 az account list --output table 列出所有订阅,并查看默认订阅。 运行 az account set --subscription "Your Subscription ID or Name" 以更改默认订阅。

安装包

npm install @azure/ai-projects

关键概念

创建并验证客户端

类工厂方法 fromConnectionString 用于构造客户端。 构造客户端:

import { AIProjectsClient } from "@azure/ai-projects";
import { DefaultAzureCredential } from "@azure/identity";

import "dotenv/config";  

const connectionString = process.env["AZURE_AI_PROJECTS_CONNECTION_STRING"] || "<connectionString>";

const client = AIProjectsClient.fromConnectionString(
  connectionString,
  new DefaultAzureCredential(),
);

例子

枚举连接

Azure AI Foundry 项目有一个“管理中心”。 输入时,项目下会显示一个名为“已连接资源”的选项卡。 客户端上的 .connections 操作允许枚举连接并获取连接属性。 连接属性包括资源 URL 和身份验证凭据,等等。

下面是连接操作的代码示例。 可以在 [包示例][samples] [samples] 中的“connections”文件夹下找到完整示例。

获取所有连接的属性

列出 Azure AI Foundry 项目中所有连接的属性:

const connections = await client.connections.listConnections();
for (const connection of connections) {
  console.log(connection);
}

获取特定类型的所有连接的属性

若要列出特定类型的连接属性(此处为 Azure OpenAI):

const connections = await client.connections.listConnections({ category: "AzureOpenAI" });
for (const connection of connections) {
  console.log(connection);
}

按连接名称获取连接的属性

若要获取名为 connectionName的连接的连接属性,

const connection = await client.connections.getConnection("connectionName");
console.log(connection);

若要使用其身份验证凭据获取连接属性,请执行以下操作:

const connection = await client.connections.getConnectionWithSecrets("connectionName");
console.log(connection);

代理(预览版)

Azure AI Projects 客户端库中的代理旨在促进 AI 项目中的各种交互和操作。 它们充当管理和执行任务的核心组件,利用不同的工具和资源来实现特定目标。 以下步骤概述了与代理交互的典型顺序。 有关其他代理示例,请参阅 [包示例][示例] 中的“agents”文件夹。

代理正在积极开发。 即将推出个人预览版注册表单。

创建代理

下面是有关如何创建代理的示例:

const agent = await client.agents.createAgent("gpt-4o", {
  name: "my-agent",
  instructions: "You are a helpful assistant",
});

若要允许代理访问资源或自定义函数,需要工具。 可以将工具传递给 createAgenttoolstoolResources 参数。

可以使用 ToolSet 执行此操作:

const toolSet = new ToolSet();
toolSet.addFileSearchTool([vectorStore.id]);
toolSet.addCodeInterpreterTool([codeInterpreterFile.id]);

// Create agent with tool set
const agent = await client.agents.createAgent("gpt-4o", {
  name: "my-agent",
  instructions: "You are a helpful agent",
  tools: toolSet.toolDefinitions,
  toolResources: toolSet.toolResources,
});
console.log(`Created agent, agent ID: ${agent.id}`);

若要按代理执行文件搜索,首先需要上传文件、创建向量存储并将该文件关联到向量存储。 下面是一个示例:

const localFileStream = fs.createReadStream("sample_file_for_upload.txt");
const file = await client.agents.uploadFile(localFileStream, "assistants", {
  fileName: "sample_file_for_upload.txt",
});
console.log(`Uploaded file, ID: ${file.id}`);

const vectorStore = await client.agents.createVectorStore({
  fileIds: [file.id],
  name: "my_vector_store",
});
console.log(`Created vector store, ID: ${vectorStore.id}`);

const fileSearchTool = ToolUtility.createFileSearchTool([vectorStore.id]);

const agent = await client.agents.createAgent("gpt-4o", {
  name: "SDK Test Agent - Retrieval",
  instructions: "You are helpful agent that can help fetch data from files you know about.",
  tools: [fileSearchTool.definition],
  toolResources: fileSearchTool.resources,
});
console.log(`Created agent, agent ID : ${agent.id}`);

使用代码解释器创建代理

下面是上传文件并将其用于代理的代码解释器的示例:

const fileStream = fs.createReadStream("nifty_500_quarterly_results.csv");
const fFile = await client.agents.uploadFile(fileStream, "assistants", {
  fileName: "nifty_500_quarterly_results.csv",
});
console.log(`Uploaded local file, file ID : ${file.id}`);

const codeInterpreterTool = ToolUtility.createCodeInterpreterTool([file.id]);

// Notice that CodeInterpreter must be enabled in the agent creation, otherwise the agent will not be able to see the file attachment
const agent = await client.agents.createAgent("gpt-4o-mini", {
  name: "my-agent",
  instructions: "You are a helpful agent",
  tools: [codeInterpreterTool.definition],
  toolResources: codeInterpreterTool.resources,
});
console.log(`Created agent, agent ID: ${agent.id}`);

使用必应地面创建代理

若要使代理能够通过必应搜索 API 执行搜索,请使用 ToolUtility.createConnectionTool() 以及连接。

下面是一个示例:

const bingGroundingConnectionId = "<bingGroundingConnectionId>";
const bingTool = ToolUtility.createConnectionTool(connectionToolType.BingGrounding, [
  bingGroundingConnectionId,
]);

const agent = await client.agents.createAgent("gpt-4-0125-preview", {
  name: "my-agent",
  instructions: "You are a helpful agent",
  tools: [bingTool.definition],
});
console.log(`Created agent, agent ID : ${agent.id}`);

Azure AI 搜索是适用于高性能应用程序的企业搜索系统。 它与 Azure OpenAI 服务和 Azure 机器学习集成,提供高级搜索技术,如矢量搜索和全文搜索。 非常适合知识库见解、信息发现和自动化

下面是集成 Azure AI 搜索的示例:

const cognitiveServicesConnectionName = "<cognitiveServicesConnectionName>";
const cognitiveServicesConnection = await client.connections.getConnection(
  cognitiveServicesConnectionName,
);
const azureAISearchTool = ToolUtility.createAzureAISearchTool(
  cognitiveServicesConnection.id,
  cognitiveServicesConnection.name,
);

// Create agent with the Azure AI search tool
const agent = await client.agents.createAgent("gpt-4-0125-preview", {
  name: "my-agent",
  instructions: "You are a helpful agent",
  tools: [azureAISearchTool.definition],
  toolResources: azureAISearchTool.resources,
});
console.log(`Created agent, agent ID : ${agent.id}`);

使用函数调用创建代理

可以通过将回调函数定义为函数工具来增强代理。 可以通过 toolstoolResources的组合提供这些内容来 createAgent。 只有函数定义和说明提供给 createAgent,而不提供实现。 Runevent handler of stream 将根据函数定义引发 requires_action 状态。 代码必须处理此状态并调用相应的函数。

下面是一个示例:

class FunctionToolExecutor {
  private functionTools: { func: Function, definition: FunctionToolDefinition }[];

  constructor() {
    this.functionTools = [{
      func: this.getUserFavoriteCity,
      ...ToolUtility.createFunctionTool({
        name: "getUserFavoriteCity",
        description: "Gets the user's favorite city.",
        parameters: {}
      })
    }, {
      func: this.getCityNickname,
      ...ToolUtility.createFunctionTool({
        name: "getCityNickname",
        description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
        parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g. Seattle, Wa" } } }
      })
    }, {
      func: this.getWeather,
      ...ToolUtility.createFunctionTool({
        name: "getWeather",
        description: "Gets the weather for a location.",
        parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g. Seattle, Wa" }, unit: { type: "string", enum: ['c', 'f'] } } }
      })
    }];
  }

  private getUserFavoriteCity(): {} {
    return { "location": "Seattle, WA" };
  }

  private getCityNickname(location: string): {} {
    return { "nickname": "The Emerald City" };
  }

  private getWeather(location: string, unit: string): {} {
    return { "weather": unit === "f" ? "72f" : "22c" };
  }

  public invokeTool(toolCall: RequiredToolCallOutput & FunctionToolDefinitionOutput): ToolOutput | undefined {
    console.log(`Function tool call - ${toolCall.function.name}`);
    const args = [];
    if (toolCall.function.parameters) {
      try {
        const params = JSON.parse(toolCall.function.parameters);
        for (const key in params) {
          if (Object.prototype.hasOwnProperty.call(params, key)) {
            args.push(params[key]);
          }
        }
      } catch (error) {
        console.error(`Failed to parse parameters: ${toolCall.function.parameters}`, error);
        return undefined;
      }
    }
    const result = this.functionTools.find((tool) => tool.definition.function.name === toolCall.function.name)?.func(...args);
    return result ? {
      toolCallId: toolCall.id,
      output: JSON.stringify(result)
    } : undefined;
  }

  public getFunctionDefinitions(): FunctionToolDefinition[] {
    return this.functionTools.map(tool => {return tool.definition});
  }
}

const functionToolExecutor = new FunctionToolExecutor();
const functionTools = functionToolExecutor.getFunctionDefinitions();
const agent = await client.agents.createAgent("gpt-4o",
  {
    name: "my-agent",
    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: functionTools
  });
console.log(`Created agent, agent ID: ${agent.id}`);

创建线程

对于每个会话或对话,需要一个线程。 下面是一个示例:

const thread = await client.agents.createThread();

使用工具资源创建线程

在某些情况下,可能需要将特定资源分配给单个线程。 为此,请提供 createThreadtoolResources 参数。 在下面的示例中,创建矢量存储并上传文件,使用 tools 参数启用代理进行文件搜索,然后使用 toolResources 参数将该文件与线程相关联。

const localFileStream = fs.createReadStream("sample_file_for_upload.txt");
const file = await client.agents.uploadFile(localFileStream, "assistants", {
  fileName: "sample_file_for_upload.txt",
});
console.log(`Uploaded file, ID: ${file.id}`);

const vectorStore = await client.agents.createVectorStore({
  fileIds: [file.id],
  name: "my_vector_store",
});
console.log(`Created vector store, ID: ${vectorStore.id}`);

const fileSearchTool = ToolUtility.createFileSearchTool([vectorStore.id]);

const agent = await client.agents.createAgent("gpt-4o", {
  name: "SDK Test Agent - Retrieval",
  instructions: "You are helpful agent that can help fetch data from files you know about.",
  tools: [fileSearchTool.definition],
});
console.log(`Created agent, agent ID : ${agent.id}`);

// Create thread with file resources.
// If the agent has multiple threads, only this thread can search this file.
const thread = await client.agents.createThread({ toolResources: fileSearchTool.resources });

创建消息

若要为助手创建要处理的消息,请将 user 作为 role 传递,并将问题作为 content传递:

const message = await client.agents.createMessage(thread.id, {
  role: "user",
  content: "hello, world!",
});

使用文件搜索附件创建邮件

若要将文件附加到消息进行内容搜索,请使用 ToolUtility.createFileSearchTool()attachments 参数:

const fileSearchTool = ToolUtility.createFileSearchTool();
const message = await client.agents.createMessage(thread.id, {
  role: "user",
  content: "What feature does Smart Eyewear offer?",
  attachments: {
    fileId: file.id,
    tools: [fileSearchTool.definition],
  },
});

使用代码解释器附件创建消息

若要将文件附加到消息以供数据分析,请使用 ToolUtility.createCodeInterpreterTool()attachment 参数。

下面是一个示例:

// notice that CodeInterpreter must be enabled in the agent creation,
// otherwise the agent will not be able to see the file attachment for code interpretation
const codeInterpreterTool = ToolUtility.createCodeInterpreterTool();
const agent = await client.agents.createAgent("gpt-4-1106-preview", {
  name: "my-assistant",
  instructions: "You are helpful assistant",
  tools: [codeInterpreterTool.definition],
});
console.log(`Created agent, agent ID: ${agent.id}`);

const thread = client.agents.createThread();
console.log(`Created thread, thread ID: ${thread.id}`);

const message = await client.agents.createMessage(thread.id, {
  role: "user",
  content:
    "Could you please create bar chart in TRANSPORTATION sector for the operating profit from the uploaded csv file and provide file to me?",
  attachments: {
    fileId: file.id,
    tools: [codeInterpreterTool.definition],
  },
});
console.log(`Created message, message ID: ${message.id}`);

创建运行、Run_and_Process或流

下面是在运行完成之前 createRun 和轮询的示例:

let run = await client.agents.createRun(thread.id, agent.id);

// Poll the run as long as run status is queued or in progress
while (
  run.status === "queued" ||
  run.status === "in_progress" ||
  run.status === "requires_action"
) {
  // Wait for a second
  await new Promise((resolve) => setTimeout(resolve, 1000));
  run = await client.agents.getRun(thread.id, run.id);
}

若要代表你轮询 SDK,请使用 createThreadAndRun 方法。

下面是一个示例:

const run = await client.agents.createThreadAndRun(thread.id, agent.id);

使用流式处理时,也无需考虑轮询。

下面是一个示例:

const streamEventMessages = await client.agents.createRun(thread.id, agent.id).stream();

事件处理可以按如下方式完成:

for await (const eventMessage of streamEventMessages) {
switch (eventMessage.event) {
  case RunStreamEvent.ThreadRunCreated:
    console.log(`ThreadRun status: ${(eventMessage.data as ThreadRunOutput).status}`)
    break;
  case MessageStreamEvent.ThreadMessageDelta:
    {
      const messageDelta = eventMessage.data as MessageDeltaChunk;
      messageDelta.delta.content.forEach((contentPart) => {
        if (contentPart.type === "text") {
          const textContent = contentPart as MessageDeltaTextContent
          const textValue = textContent.text?.value || "No text"
          console.log(`Text delta received:: ${textValue}`)
        }
      });
    }
    break;

  case RunStreamEvent.ThreadRunCompleted:
    console.log("Thread Run Completed");
    break;
  case ErrorEvent.Error:
    console.log(`An error occurred. Data ${eventMessage.data}`);
    break;
  case DoneEvent.Done:
    console.log("Stream completed.");
    break;
  }
}

检索消息

若要从代理检索消息,请使用以下示例:

const messages = await client.agents.listMessages(thread.id);

// The messages are following in the reverse order,
// we will iterate them and output only text contents.
for (const dataPoint of messages.data.reverse()) {
    const lastMessageContent: MessageContentOutput = dataPoint.content[dataPoint.content.length - 1];
    console.log( lastMessageContent);
    if (isOutputOfType<MessageTextContentOutput>(lastMessageContent, "text")) {
      console.log(`${dataPoint.role}: ${(lastMessageContent as MessageTextContentOutput).text.value}`);
    }
  }

检索文件

无法检索代理上传的文件。 如果用例需要访问代理上传的文件内容,建议保留应用程序可访问的其他副本。 但是,代理生成的文件可通过 getFileContent检索。

下面是从消息检索文件 ID 的示例:

const messages = await client.agents.listMessages(thread.id);
const imageFile = (messages.data[0].content[0] as MessageImageFileContentOutput).imageFile;
const imageFileName = (await client.agents.getFile(imageFile.fileId)).filename;

const fileContent = await (await client.agents.getFileContent(imageFile.fileId).asNodeStream()).body;
if (fileContent) {
  const chunks: Buffer[] = [];
  for await (const chunk of fileContent) {
    chunks.push(Buffer.from(chunk));
  }
  const buffer = Buffer.concat(chunks);
  fs.writeFileSync(imageFileName, buffer);
} else {
  console.error("Failed to retrieve file content: fileContent is undefined");
}
console.log(`Saved image file to: ${imageFileName}`);

拆卸

若要在完成任务后删除资源,请使用以下函数:

await client.agents.deleteVectorStore(vectorStore.id);
console.log(`Deleted vector store, vector store ID: ${vectorStore.id}`);

await client.agents.deleteFile(file.id);
console.log(`Deleted file, file ID: ${file.id}`);

client.agents.deleteAgent(agent.id);
console.log(`Deleted agent, agent ID: ${agent.id}`);

描图

可以将 Application Insights Azure 资源添加到 Azure AI Foundry 项目。 请参阅工作室中的“跟踪”选项卡。 如果启用了 Application Insights 连接字符串,可以配置代理,并通过 Azure Monitor 观察完整的执行路径。 通常,在创建代理之前,可能需要开始跟踪。

安装

确保通过 安装 OpenTelemetry 和 Azure SDK 跟踪插件

npm install @opentelemetry/api \
  @opentelemetry/instrumentation \
  @opentelemetry/sdk-trace-node \
  @azure/opentelemetry-instrumentation-azure-sdk \
  @azure/monitor-opentelemetry-exporter

还需要导出程序将遥测数据发送到可观测性后端。 可以将跟踪打印到控制台或使用本地查看器,例如 Aspire 仪表板

若要连接到 Aspire 仪表板或其他与 OpenTelemetry 兼容的后端,请安装 OTLP 导出程序:

npm install @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/exporter-metrics-otlp-proto

跟踪示例

下面是 createAgent上面要包含的代码示例:

import { trace } from "@opentelemetry/api";
import { AzureMonitorTraceExporter } from "@azure/monitor-opentelemetry-exporter"
import {
    ConsoleSpanExporter,
    NodeTracerProvider,
    SimpleSpanProcessor,
} from "@opentelemetry/sdk-trace-node";

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register();

const tracer = trace.getTracer("Agents Sample", "1.0.0");

const client = AIProjectsClient.fromConnectionString(
  connectionString || "", new DefaultAzureCredential()
);

if (!appInsightsConnectionString) {
  appInsightsConnectionString = await client.telemetry.getConnectionString();
}

if (appInsightsConnectionString) {
  const exporter = new AzureMonitorTraceExporter({
    connectionString: appInsightsConnectionString
  });
  provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
}

await tracer.startActiveSpan("main", async (span) => {
    client.telemetry.updateSettings({enableContentRecording: true})
// ...

故障 排除

异常

发出服务调用的客户端方法会引发 RestError,以响应来自该服务的非成功 HTTP 状态代码响应。 异常 code 将保留 HTTP 响应状态代码。 异常 error.message 包含可能有助于诊断问题的详细消息:

import { RestError } from "@azure/core-rest-pipeline"

// ...

try {
  const result = await client.connections.listConnections();
} catch (e as RestError) {
  console.log(`Status code: ${e.code}`);
  console.log(e.message);
}

例如,提供错误的凭据时:

Status code: 401 (Unauthorized)
Operation returned an invalid status 'Unauthorized'

报告问题

若要报告客户端库问题或请求其他功能,请在此处 打开 GitHub 问题

贡献

此项目欢迎贡献和建议。 大多数贡献要求你同意参与者许可协议(CLA),声明你有权(实际这样做)授予我们使用你的贡献的权利。 有关详细信息,请访问 https://cla.microsoft.com

提交拉取请求时,CLA 机器人会自动确定是否需要提供 CLA 并适当修饰 PR(例如标签、注释)。 只需按照机器人提供的说明进行操作。 只需使用 CLA 在所有存储库中执行此操作一次。

该项目已采用 Microsoft开源行为准则。 有关详细信息,请参阅《行为准则常见问题解答》或联系 opencode@microsoft.com,了解任何其他问题或意见。