共用方式為


適用於 JavaScript 的 Azure AI 代理用戶端庫 - 版本 1.1.0

使用 AI Agents 用戶端庫可以:

  • 使用 Azure AI 代理程式服務開發代理程式,利用 OpenAI、Microsoft 和其他 LLM 提供者的廣泛模型、工具和功能生態系統。 Azure AI 代理程式服務可讓您為各種產生式 AI 使用案例建置代理程式。
  • 注意: 雖然此包可以獨立使用,但我們建議使用 Azure AI Projects 用戶端庫 來增強體驗。 Projects 庫提供了對高級功能的簡化訪問,例如創建和管理代理、枚舉 AI 模型、使用數據集和管理搜索索引、評估生成式 AI 性能以及啟用 OpenTelemetry 跟蹤。

產品文件 | 樣品 | 包 (npm) | API 參考文件

目錄

入門指南

先決條件

授權

  • 需要 Entra ID 來驗證用戶端。 您的應用程式需要實作 TokenCredential 介面的物件。 這裡的程式代碼範例會使用 DefaultAzureCredential。 若要讓該作業正常運作,您將需要:
    • Contributor 角色。 在 Azure 入口網站中,您可以透過 Azure AI 專案資源的 [存取控制] 索引標籤來完成指派的角色。 在此處了解有關角色分配的更多資訊。
    • 已安裝 Azure CLI
    • 執行 az login,即可登入您的 Azure 帳戶。
    • 請注意,如果您有多個 Azure 訂用帳戶,則包含 Azure AI Project 資源的訂用帳戶必須是您的預設訂用帳戶。 執行 az account list --output table 列出您的所有訂用帳戶,並查看哪一個是預設值。 執行 az account set --subscription "Your Subscription ID or Name" 以變更您的預設訂用帳戶。

安裝套件

npm install @azure/ai-agents @azure/identity

重要概念

建立和驗證用戶端

用於 AgentsClient 構造用戶端。 目前,我們建議您通過 client.agents使用 AgentsClient。

要獲取專案終端節點,您可以參考 文件。 下面我們假設環境變數 PROJECT_ENDPOINT 保存此值。

import { AgentsClient } from "@azure/ai-agents";
import { DefaultAzureCredential } from "@azure/identity";

const projectEndpoint = process.env["PROJECT_ENDPOINT"] || "<project endpoint>";
const modelDeploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || "gpt-4o";
const client = new AgentsClient(projectEndpoint, new DefaultAzureCredential());

範例

代理人

Azure AI 專案用戶端連結庫中的代理程式是設計來協助 AI 專案內的各種互動和作業。 它們可作為管理和執行工作的核心元件,利用不同的工具和資源來達成特定目標。 下列步驟概述與 Agents 互動的典型順序。 有關其他代理範例,請參閱 包示例

建立代理程式

以下是如何建立代理程式的範例:

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

若要允許代理程式存取您的資源或自定義函式,您需要工具。 您可以將工具傳遞至 createAgenttoolstoolResources 自變數。

您可以使用 ToolSet 來執行此動作:

import { ToolSet } from "@azure/ai-agents";

// Upload file for code interpreter tool
const filePath1 = "./data/syntheticCompanyQuarterlyResults.csv";
const fileStream1 = fs.createReadStream(filePath1);
const codeInterpreterFile = await client.files.upload(fileStream1, "assistants", {
  fileName: "myLocalFile",
});
console.log(`Uploaded local file, file ID : ${codeInterpreterFile.id}`);
// Upload file for file search tool
const filePath2 = "./data/sampleFileForUpload.txt";
const fileStream2 = fs.createReadStream(filePath2);
const fileSearchFile = await client.files.upload(fileStream2, "assistants", {
  fileName: "sampleFileForUpload.txt",
});
console.log(`Uploaded file, file ID: ${fileSearchFile.id}`);
// Create vector store for file search tool
const vectorStore = await client.vectorStores
  .createAndPoll({
    fileIds: [fileSearchFile.id],
  })
  .pollUntilDone();
// Create tool set
const toolSet = new ToolSet();
toolSet.addFileSearchTool([vectorStore.id]);
toolSet.addCodeInterpreterTool([codeInterpreterFile.id]);

// Create agent with tool set
const agent = await client.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}`);

多個代理

您可以使用不同的工具建立多個代理程式,然後將它們連接在一起。

import { ToolUtility } from "@azure/ai-agents";

const connectedAgentName = "stock_price_bot";
const modelDeploymentName = process.env["MODEL_DEPLOYMENT_NAME"] || "gpt-4o";
const stockAgent = await client.createAgent(modelDeploymentName, {
  name: "stock-price-agent",
  instructions:
    "Your job is to get the stock price of a company. If you don't know the realtime stock price, return the last known stock price.",
});
// Initialize Connected Agent tool with the agent id, name, and description
const connectedAgentTool = ToolUtility.createConnectedAgentTool(
  stockAgent.id,
  connectedAgentName,
  "Gets the stock price of a company",
);
// Create agent with the Connected Agent tool and process assistant run
const agent = await client.createAgent(modelDeploymentName, {
  name: "my-agent",
  instructions: "You are a helpful assistant, and use the connected agent to get stock prices.",
  tools: [connectedAgentTool.definition],
});
console.log(`Created agent, agent ID: ${agent.id}`);

若要執行 Agent 的檔案搜尋,我們必須先上傳檔案、建立向量存放區,並將檔案與向量存放區產生關聯。 以下是範例:

import { ToolUtility } from "@azure/ai-agents";

const filePath = "./data/sampleFileForUpload.txt";
const localFileStream = fs.createReadStream(filePath);
const file = await client.files.upload(localFileStream, "assistants", {
  fileName: "sampleFileForUpload.txt",
});
console.log(`Uploaded file, file ID: ${file.id}`);

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

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

const agent = await client.createAgent("gpt-4o", {
  name: "File Search Agent",
  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}`);

使用程式代碼解釋器建立代理程式

以下是上傳檔案並將其用於 Agent 程式代碼解釋器的範例:

import { ToolUtility } from "@azure/ai-agents";

const filePath = "./data/syntheticCompanyQuarterlyResults.csv";
const localFileStream = fs.createReadStream(filePath);
const localFile = await client.files.upload(localFileStream, "assistants", {
  fileName: "localFile",
});

console.log(`Uploaded local file, file ID : ${localFile.id}`);

const codeInterpreterTool = ToolUtility.createCodeInterpreterTool([localFile.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.createAgent("gpt-4o", {
  name: "my-agent",
  instructions: "You are a helpful agent",
  tools: [codeInterpreterTool.definition],
  toolResources: codeInterpreterTool.resources,
});
console.log(`Created agent, agent ID: ${agent.id}`);

使用 Bing 地面建立代理程式

若要讓代理程式透過 Bing 搜尋 API 執行搜尋,請使用 ToolUtility.createBingGroundingTool() 以及連線。 請參閱 此處 以了解有關使用 Bing Search 進行 Grounding 的更多資訊。

以下是範例:

import { ToolUtility } from "@azure/ai-agents";

const connectionId = process.env["AZURE_BING_CONNECTION_ID"] || "<connection-name>";

// Initialize agent bing tool with the connection id
const bingTool = ToolUtility.createBingGroundingTool([{ connectionId: connectionId }]);

// Create agent with the bing tool and process assistant run
const agent = await client.createAgent("gpt-4o", {
  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 Machine Learning 整合,提供向量搜尋和全文搜索等進階搜尋技術。 適用於知識庫深入解析、資訊探索和自動化

以下是整合 Azure AI 搜尋的範例:

import { ToolUtility } from "@azure/ai-agents";

const connectionName = process.env["AZURE_AI_SEARCH_CONNECTION_NAME"] || "<connection-name>";

// Initialize Azure AI Search tool
const azureAISearchTool = ToolUtility.createAzureAISearchTool(connectionName, "search-index", {
  queryType: "simple",
  topK: 3,
  filter: "", // Add string here to filter results
  indexConnectionId: connectionName,
  indexName: "search-index",
});

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

使用函數調用建立代理程式

您可以將回呼函式定義為函式工具,以增強代理程式。 您可以透過 createAgenttools的組合,將這些專案提供給 toolResources。 只有函式定義和描述會提供給 createAgent,而不需要實作。 Runevent handler of stream 會根據函式定義引發 requires_action 狀態。 您的程式代碼必須處理此狀態,並呼叫適當的函式。

以下是範例:

import {
  FunctionToolDefinition,
  ToolUtility,
  RequiredToolCall,
  ToolOutput,
} from "@azure/ai-agents";

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: RequiredToolCall & FunctionToolDefinition): ToolOutput | undefined {
    console.log(`Function tool call - ${toolCall.function.name}`);
    const args: any[] = [];
    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;
      }
    }
    // Create a mapping of function names to their implementations
    const functionMap = new Map(
      this.functionTools.map((tool) => [tool.definition.function.name, tool.func]),
    );
    const result = functionMap.get(toolCall.function.name)?.(...args);
    return result
      ? {
          toolCallId: toolCall.id,
          output: JSON.stringify(result),
        }
      : {
          toolCallId: toolCall.id,
          output: JSON.stringify({
            error: `No matching tool found for function: ${toolCall.function.name}`,
          }),
        };
  }

  public getFunctionDefinitions(): FunctionToolDefinition[] {
    return this.functionTools.map((tool) => {
      return tool.definition;
    });
  }
}
const functionToolExecutor = new FunctionToolExecutor();
const functionTools = functionToolExecutor.getFunctionDefinitions();
const agent = await client.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}`);

使用 OpenAPI 建立代理程式

OpenAPI 規格描述特定端點的 REST 作業。 代理程式 SDK 可以讀取 OpenAPI 規格、從中建立函式,以及針對 REST 端點呼叫該函式,而不需要額外的用戶端執行。 以下是建立 OpenAPI 工具的範例(使用匿名驗證):

import { ToolUtility } from "@azure/ai-agents";

// Read in OpenApi spec
const filePath = "./data/weatherOpenApi.json";
const openApiSpec = JSON.parse(fs.readFileSync(filePath, "utf-8"));

// Define OpenApi function
const openApiFunction = {
  name: "getWeather",
  spec: openApiSpec,
  description: "Retrieve weather information for a location",
  auth: {
    type: "anonymous",
  },
  default_params: ["format"], // optional
};

// Create OpenApi tool
const openApiTool = ToolUtility.createOpenApiTool(openApiFunction);

// Create agent with OpenApi tool
const agent = await client.createAgent("gpt-4o", {
  name: "myAgent",
  instructions: "You are a helpful agent",
  tools: [openApiTool.definition],
});
console.log(`Created agent, agent ID: ${agent.id}`);

建立線程

針對每個會話或交談,需要線程。 以下是範例:

const thread = await client.threads.create();
console.log(`Created thread, thread ID: ${thread.id}`);

使用工具資源建立線程

在某些情況下,您可能需要將特定資源指派給個別線程。 若要達成此目的,您會提供 toolResources 自變數給 threads.create。 在下列範例中,您會建立向量存放區並上傳檔案、使用 tools 自變數啟用 Agent 進行檔案搜尋,然後使用 toolResources 自變數將檔案與線程產生關聯。

import { ToolUtility } from "@azure/ai-agents";

const filePath = "./data/syntheticCompanyQuarterlyResults.csv";
const localFileStream = fs.createReadStream(filePath);
const file = await client.files.upload(localFileStream, "assistants", {
  fileName: "sample_file_for_upload.csv",
});
console.log(`Uploaded file, ID: ${file.id}`);

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

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

const agent = await client.agents.createAgent("gpt-4o", {
  name: "myAgent",
  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.threads.create({ toolResources: fileSearchTool.resources });

列出線程

要列出附加到給定代理的所有線程,請使用 threads.list

const threads = client.threads.list();
console.log(`Threads for agent ${agent.id}:`);
for await (const t of threads) {
  console.log(`Thread ID: ${t.id}`);
  console.log(`Created at: ${t.createdAt}`);
  console.log(`Metadata: ${t.metadata}`);
  console.log(`---- `);
}

建立訊息

若要建立要處理的助理訊息,您可以將 user 傳遞為 role,並將問題傳遞為 content

const message = await client.messages.create(thread.id, "user", "hello, world!");
console.log(`Created message, message ID: ${message.id}`);

使用檔案搜尋附件建立訊息

若要將檔案附加至訊息以進行內容搜尋,請使用 ToolUtility.createFileSearchTool()attachments 自變數:

import { ToolUtility } from "@azure/ai-agents";

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

使用程式代碼解釋器附件建立訊息

若要將檔案附加至訊息以進行數據分析,請使用 ToolUtility.createCodeInterpreterTool()attachment 自變數。

以下是範例:

import { ToolUtility } from "@azure/ai-agents";

// 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-4o", {
  name: "my-assistant",
  instructions: "You are helpful assistant",
  tools: [codeInterpreterTool.definition],
});
console.log(`Created agent, agent ID: ${agent.id}`);

const thread = await client.threads.create();
console.log(`Created thread, thread ID: ${thread.id}`);

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

使用影像輸入建立訊息

您可以使用下列方式,將訊息傳送至 Azure 代理程式與影像輸入:

  • 使用儲存為上傳檔案的影像
  • 使用可透過URL存取的公用映像
  • 使用base64編碼的影像字串

以下示例演示了base64方法:

使用base64編碼的影像輸入建立訊息
function imageToBase64DataUrl(imagePath: string, mimeType: string): string {
  try {
    // Read the image file as binary
    const imageBuffer = fs.readFileSync(imagePath);
    // Convert to base64
    const base64Data = imageBuffer.toString("base64");
    // Format as a data URL
    return `data:${mimeType};base64,${base64Data}`;
  } catch (error) {
    console.error(`Error reading image file at ${imagePath}:`, error);
    throw error;
  }
}

// Convert your image file to base64 format
const filePath = "./data/image_file.png";
const imageDataUrl = imageToBase64DataUrl(filePath, "image/png");

// Create a message with both text and image content
console.log("Creating message with image content...");
const inputMessage = "Hello, what is in the image?";
const content = [
  {
    type: "text",
    text: inputMessage,
  },
  {
    type: "image_url",
    imageUrl: {
      url: imageDataUrl,
      detail: "high",
    },
  },
];

const message = await client.messages.create(thread.id, "user", content);
console.log(`Created message, message ID: ${message.id}`);

建立執行、Run_and_Process或 Stream

以下是 runs.create 和輪詢的範例,直到執行完成為止:

// Create and poll a run
console.log("Creating run...");
const run = await client.runs.createAndPoll(thread.id, agent.id, {
  pollingOptions: {
    intervalInMs: 2000,
  },
  onResponse: (response): void => {
    console.log(`Received response with status: ${response.parsedBody.status}`);
  },
});
console.log(`Run finished with status: ${run.status}`);

若要代表您進行 SDK 輪詢,請使用 createThreadAndRun 方法。

以下是範例:

const run = await client.runs.createThreadAndRun(agent.id, {
  thread: {
    messages: [
      {
        role: "user",
        content: "hello, world!",
      },
    ],
  },
});

使用串流處理時,也不需要考慮輪詢。

以下是範例:

const streamEventMessages = await client.runs.create(thread.id, agent.id).stream();

事件處理可以如下完成:

import { RunStreamEvent, MessageStreamEvent, ErrorEvent, DoneEvent } from "@azure/ai-agents";

const streamEventMessages = await client.runs.create(thread.id, agent.id).stream();

for await (const eventMessage of streamEventMessages) {
  switch (eventMessage.event) {
    case RunStreamEvent.ThreadRunCreated:
      console.log(`ThreadRun status: ${eventMessage.data.status}`);
      break;
    case MessageStreamEvent.ThreadMessageDelta:
      {
        const messageDelta = eventMessage.data;
        messageDelta.delta.content.forEach((contentPart) => {
          if (contentPart.type === "text") {
            const textContent = contentPart;
            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 messagesIterator = client.messages.list(thread.id);
const allMessages = [];
for await (const m of messagesIterator) {
  allMessages.push(m);
}
console.log("Messages:", allMessages);

// The messages are following in the reverse order,
// we will iterate them and output only text contents.
const messages = await client.messages.list(thread.id, {
  order: "asc",
});

for await (const dataPoint of messages) {
  const textContent = dataPoint.content.find((item) => item.type === "text");
  if (textContent && "text" in textContent) {
    console.log(`${dataPoint.role}: ${textContent.text.value}`);
  }
}

擷取

無法擷取代理程式上傳的檔案。 如果您的使用案例需要存取代理程式上傳的檔案內容,建議您保留應用程式可存取的其他複本。 不過,代理程式所產生的檔案可由 files.getContent擷取。

以下是從訊息擷取檔案標識碼的範例:

import { isOutputOfType, MessageTextContent, MessageImageFileContent } from "@azure/ai-agents";

const messagesIterator = client.messages.list(thread.id);
const allMessages = [];
for await (const m of messagesIterator) {
  allMessages.push(m);
}
console.log("Messages:", allMessages);

// Get most recent message from the assistant
const assistantMessage = allMessages.find((msg) => msg.role === "assistant");
if (assistantMessage) {
  const textContent = assistantMessage.content.find((content) =>
    isOutputOfType<MessageTextContent>(content, "text"),
  ) as MessageTextContent;
  if (textContent) {
    console.log(`Last message: ${textContent.text.value}`);
  }
}

const imageFile = (allMessages[0].content[0] as MessageImageFileContent).imageFile;
const imageFileName = (await client.agents.files.get(imageFile.fileId)).filename;

const fileContent = await (await client.files.getContent(imageFile.fileId).asNodeStream()).body;
if (fileContent) {
  const chunks: Buffer[] = [];
  for await (const chunk of fileContent) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : 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.vectorStores.delete(vectorStore.id);
console.log(`Deleted vector store, vector store ID: ${vectorStore.id}`);

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

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

故障排除

例外狀況

進行服務呼叫的用戶端方法會針對來自服務的非成功 HTTP 狀態代碼回應,引發 RestError。 例外狀況的 code 會保存 HTTP 回應狀態代碼。 例外狀況的 error.message 包含詳細的訊息,可能有助於診斷問題:

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

try {
  const thread = await client.threads.create();
} catch (e) {
  if (e instanceof RestError) {
    console.log(`Status code: ${e.code}`);
    console.log(e.message);
  } else {
    console.error(e);
  }
}

例如,當您提供錯誤的認證時:

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

報告問題

若要回報客戶端連結庫的問題,或要求其他功能,請在這裡開啟 GitHub 問題

後續步驟

請查看 套件範例 資料夾,其中包含完全可執行的程式代碼。

貢獻

此項目歡迎參與和建議。 大部分的捐款都要求您同意「參與者許可協定」(CLA),宣告您有權,而且實際上確實會授與我們使用您貢獻的許可權。 如需詳細資訊,請瀏覽 https://cla.microsoft.com

當您提交提取要求時,CLA-Bot 會自動判斷您是否需要提供 CLA 並適當裝飾 PR(例如標籤、批註)。 只要遵循 Bot 所提供的指示即可。 您只需要使用我們的 CLA 在所有存放庫上執行此動作一次。

此專案採用了 Microsoft 開放原始碼管理辦法。 如需詳細資訊,請參閱《管理辦法》常見問題或連絡 opencode@microsoft.com,以取得任何其他問題或意見。