Udostępnij za pośrednictwem


Biblioteka klienta usługi Azure AI Projects dla języka JavaScript — wersja 1.0.0-beta.2

Użyj biblioteki klienta projektów sztucznej inteligencji (w wersji zapoznawczej), aby:

  • Wyliczanie połączeń w projekcie usługi Azure AI Foundry i uzyskiwanie właściwości połączenia. Na przykład uzyskaj adres URL punktu końcowego wnioskowania i poświadczenia skojarzone z połączeniem usługi Azure OpenAI.
  • tworzenie agentów przy użyciu usługi Azure AI Agent Service, wykorzystując rozległy ekosystem modeli, narzędzi i możliwości platformy OpenAI, firmy Microsoft i innych dostawców LLM. Usługa agenta sztucznej inteligencji platformy Azure umożliwia tworzenie agentów dla szerokiej gamy generowanych przypadków użycia sztucznej inteligencji. Pakiet jest obecnie w prywatnej wersji zapoznawczej.
  • Włącz śledzenie OpenTelemetry.

dokumentacja produktu

Spis treści

Wprowadzenie

Warunek wstępny

  • wersje Node.js LTS
  • Subskrypcja platformy Azure .
  • Projekt w usłudze Azure AI Foundry.
  • Parametry połączenia projektu. Można go znaleźć na stronie przeglądu projektu usługi Azure AI Foundry w obszarze "Szczegóły projektu". Poniżej przyjmiemy założenie, że zmienna środowiskowa AZURE_AI_PROJECTS_CONNECTION_STRING została zdefiniowana do przechowywania tej wartości.
  • Do uwierzytelnienia klienta jest wymagany identyfikator Entra. Aplikacja wymaga obiektu, który implementuje interfejs TokenCredential. Przykłady kodu w tym miejscu używają DefaultAzureCredential. Aby uzyskać to działanie, potrzebne będą następujące elementy:
    • Rola Contributor. Przypisaną rolę można wykonać za pomocą karty "Kontrola dostępu (IAM) zasobu projektu usługi Azure AI w witrynie Azure Portal.
    • zainstalowany interfejsu wiersza polecenia platformy Azure.
    • Zalogowano się do konta platformy Azure, uruchamiając az login.
    • Należy pamiętać, że jeśli masz wiele subskrypcji platformy Azure, subskrypcja zawierająca zasób projektu usługi Azure AI musi być subskrypcją domyślną. Uruchom az account list --output table, aby wyświetlić listę wszystkich subskrypcji i zobaczyć, która z nich jest domyślna. Uruchom az account set --subscription "Your Subscription ID or Name", aby zmienić domyślną subskrypcję.

Instalowanie pakietu

npm install @azure/ai-projects

Kluczowe pojęcia

Tworzenie i uwierzytelnianie klienta

Metoda fabryki klas fromConnectionString służy do konstruowania klienta. Aby utworzyć klienta:

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(),
);

Przykłady

Wyliczanie połączeń

Projekt usługi Azure AI Foundry ma "Centrum zarządzania". Po wprowadzeniu w projekcie zostanie wyświetlona karta o nazwie "Połączone zasoby". Operacje .connections na kliencie umożliwiają wyliczanie połączeń i uzyskiwanie właściwości połączenia. Właściwości połączenia obejmują między innymi adres URL zasobu i poświadczenia uwierzytelniania.

Poniżej przedstawiono przykłady kodu operacji połączenia. Pełne przykłady można znaleźć w folderze "connections" w folderze [package samples][samples].

Pobieranie właściwości wszystkich połączeń

Aby wyświetlić listę właściwości wszystkich połączeń w projekcie azure AI Foundry:

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

Pobieranie właściwości wszystkich połączeń określonego typu

Aby wyświetlić listę właściwości połączeń określonego typu (tutaj Azure OpenAI):

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

Pobieranie właściwości połączenia według jego nazwy połączenia

Aby uzyskać właściwości połączenia połączenia o nazwie connectionName:

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

Aby uzyskać właściwości połączenia przy użyciu poświadczeń uwierzytelniania:

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

Agenci (wersja zapoznawcza)

Agenci w bibliotece klienta usługi Azure AI Projects mają na celu ułatwienie różnych interakcji i operacji w projektach sztucznej inteligencji. Służą one jako podstawowe składniki, które zarządzają zadaniami i wykonują je, wykorzystując różne narzędzia i zasoby w celu osiągnięcia określonych celów. Poniższe kroki przedstawiają typową sekwencję interakcji z agentami. Aby uzyskać dodatkowe przykłady agentów, zobacz folder "agents" w folderze [package samples][samples].

Agenci są aktywnie opracowywani. Wkrótce pojawi się formularz rejestracji w prywatnej wersji zapoznawczej.

Tworzenie agenta

Oto przykład tworzenia agenta:

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

Aby umożliwić agentom dostęp do zasobów lub funkcji niestandardowych, potrzebne są narzędzia. Narzędzia można przekazać do createAgent za pomocą argumentów tools i toolResources.

Możesz użyć ToolSet, aby to zrobić:

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

Aby przeprowadzić wyszukiwanie plików przez agenta, najpierw musimy przekazać plik, utworzyć magazyn wektorów i skojarzyć plik z magazynem wektorów. Oto przykład:

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

Tworzenie agenta za pomocą interpretera kodu

Oto przykład przekazywania pliku i używania go do interpretera kodu przez agenta:

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

Tworzenie agenta za pomocą platformy Bing Grounding

Aby umożliwić agentowi wyszukiwanie za pomocą interfejsu API wyszukiwania Bing, należy użyć ToolUtility.createConnectionTool() wraz z połączeniem.

Oto przykład:

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 Search to system wyszukiwania przedsiębiorstwa dla aplikacji o wysokiej wydajności. Integruje się z usługami Azure OpenAI Service i Azure Machine Learning, oferując zaawansowane technologie wyszukiwania, takie jak wyszukiwanie wektorowe i wyszukiwanie pełnotekstowe. Idealne rozwiązanie do analizy bazy wiedzy, odnajdywania informacji i automatyzacji

Oto przykład integracji usługi Azure AI Search:

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

Tworzenie agenta za pomocą wywołania funkcji

Możesz ulepszyć agentów, definiując funkcje wywołania zwrotnego jako narzędzia funkcji. Można je udostępnić createAgent za pomocą kombinacji tools i toolResources. Tylko definicje i opisy funkcji są udostępniane do createAgent, bez implementacji. Run lub event handler of stream zgłosi stan requires_action na podstawie definicji funkcji. Kod musi obsługiwać ten stan i wywoływać odpowiednie funkcje.

Oto przykład:

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

Tworzenie wątku

Dla każdej sesji lub konwersacji wymagany jest wątek. Oto przykład:

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

Tworzenie wątku za pomocą zasobu narzędzia

W niektórych scenariuszach może być konieczne przypisanie określonych zasobów do poszczególnych wątków. Aby to osiągnąć, należy podać argument toolResources, aby createThread. W poniższym przykładzie utworzysz magazyn wektorów i przekażesz plik, włącz agenta do wyszukiwania plików przy użyciu argumentu tools, a następnie skojarzysz plik z wątkiem przy użyciu argumentu 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 });

Utwórz wiadomość

Aby utworzyć komunikat dla asystenta do przetworzenia, przekażesz user jako role i pytanie content:

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

Tworzenie komunikatu z załącznikiem wyszukiwania plików

Aby dołączyć plik do wiadomości na potrzeby wyszukiwania zawartości, należy użyć ToolUtility.createFileSearchTool() i argumentu 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],
  },
});

Tworzenie wiadomości z załącznikiem interpretera kodu

Aby dołączyć plik do komunikatu na potrzeby analizy danych, należy użyć ToolUtility.createCodeInterpreterTool() i argumentu attachment.

Oto przykład:

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

Tworzenie uruchamiania, Run_and_Process lub strumienia

Oto przykład createRun i sondowania do momentu ukończenia przebiegu:

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

Aby sondować zestaw SDK w Twoim imieniu, użyj metody createThreadAndRun.

Oto przykład:

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

W przypadku przesyłania strumieniowego sondowanie również nie musi być brane pod uwagę.

Oto przykład:

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

Obsługa zdarzeń można wykonać w następujący sposób:

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

Pobieranie wiadomości

Aby pobrać komunikaty z agentów, użyj następującego przykładu:

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

Pobieranie pliku

Nie można odzyskać plików przekazanych przez agentów. Jeśli przypadek użycia musi uzyskać dostęp do zawartości pliku przekazanej przez agentów, zaleca się przechowywanie dodatkowej kopii dostępnej dla aplikacji. Pliki generowane przez agentów można jednak pobierać za pomocą getFileContent.

Oto przykład pobierania identyfikatorów plików z komunikatów:

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

Rozerwanie

Aby usunąć zasoby po wykonaniu zadań, użyj następujących funkcji:

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

Śledzenia

Zasób platformy Azure usługi Application Insights można dodać do projektu usługi Azure AI Foundry. Zobacz kartę Śledzenie w swoim studio. Jeśli została włączona, możesz pobrać parametry połączenia usługi Application Insights, skonfigurować agentów i obserwować pełną ścieżkę wykonywania za pośrednictwem usługi Azure Monitor. Zazwyczaj warto rozpocząć śledzenie przed utworzeniem agenta.

Instalacja

Upewnij się, że zainstalowano wtyczkę OpenTelemetry i wtyczkę śledzenia zestawu SDK platformy Azure za pomocą polecenia

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

Będziesz również potrzebować eksportera do wysyłania danych telemetrycznych do zaplecza możliwości obserwacji. Możesz wydrukować ślady w konsoli programu lub użyć przeglądarki lokalnej, takiej jak Aspire Dashboard.

Aby nawiązać połączenie z pulpitem nawigacyjnym Aspirującego pulpitu nawigacyjnego lub innym zapleczem zgodnym z technologią OpenTelemetry, zainstaluj eksportera OTLP:

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

Przykład śledzenia

Oto przykładowy kod, który ma zostać uwzględniony powyżej 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})
// ...

Rozwiązywanie problemów

Wyjątki

Metody klienta, które tworzą wywołania usługi, zgłaszają resterror odpowiedzi kodu stanu HTTP z usługi. code wyjątku będzie przechowywać kod stanu odpowiedzi HTTP. error.message wyjątku zawiera szczegółowy komunikat, który może być przydatny podczas diagnozowania problemu:

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

Na przykład w przypadku podania nieprawidłowych poświadczeń:

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

Raportowanie problemów

Aby zgłosić problemy z biblioteką klienta lub zażądać dodatkowych funkcji, otwórz problem z usługą GitHub tutaj

Przyczyniając się

Ten projekt z zadowoleniem przyjmuje wkład i sugestie. Większość kontrybucja wymaga zgody na umowę licencyjną współautora (CLA), deklarując, że masz prawo, a w rzeczywistości przyznaj nam prawa do korzystania z twojego wkładu. Aby uzyskać szczegółowe informacje, odwiedź stronę https://cla.microsoft.com.

Po przesłaniu żądania ściągnięcia bot CLA automatycznie określi, czy musisz podać cla i odpowiednio ozdobić żądanie ściągnięcia (np. etykieta, komentarz). Po prostu postępuj zgodnie z instrukcjami dostarczonymi przez bota. Należy to zrobić tylko raz we wszystkich repozytoriach przy użyciu naszego CLA.

Ten projekt przyjął kodeks postępowania firmy Microsoft typu open source. Aby uzyskać więcej informacji, zobacz Często zadawane pytania dotyczące kodeksu postępowania lub skontaktuj się z opencode@microsoft.com z dodatkowymi pytaniami lub komentarzami.