将 OpenAI、通信和组织数据功能集成到业务线应用中
级别:中等
本教程演示如何将 Azure OpenAI、Azure 通信服务 和 Microsoft Graph/Microsoft Graph 工具包集成到业务线(LOB)应用程序中,以提高用户工作效率、提升用户体验并将 LOB 应用提升到下一级别。 应用程序中的主要功能包括:
- AI:允许用户以自然语言提问并将其答案转换为可用于查询数据库的 SQL,允许用户定义可用于自动生成电子邮件和短信的规则,并了解如何使用自然语言从你自己的自定义数据源检索数据。 Azure OpenAI 用于这些功能。
- 通信:使用Azure 通信服务启用对客户和电子邮件/短信功能的应用内电话呼叫。
- 组织数据:拉取用户可能需要的相关组织数据(文档、聊天、电子邮件、日历事件),以避免上下文切换。 提供对此类组织数据的访问权限可减少用户切换到 Outlook、Teams、OneDrive、其他自定义应用、其手机等的需求,因为它们需要的特定数据和功能直接在应用中提供。 Microsoft Graph 和 Microsoft Graph 工具包用于此功能。
该应用程序是一个简单的客户管理应用,允许用户管理其客户和相关数据。 它由使用 TypeScript 构建的前端组成,它调用后端 API 来检索数据、与 AI 功能交互、发送电子邮件/短信以及拉取组织数据。 下面是在本教程中逐步介绍的应用程序解决方案的概述:
本教程将指导你完成设置所需的 Azure 和 Microsoft 365 资源的过程。 它还将引导你完成用于实现 AI、通信和组织数据功能的代码。 虽然不需要复制和粘贴代码,但某些练习将让你修改代码以尝试不同的方案。
本教程将生成的内容
选择自己的冒险
可以从头到尾完成整个教程,也可以完成感兴趣的特定主题。 本教程分为以下主题:
- 克隆项目练习 (必需的练习)。
- AI 练习:创建 Azure OpenAI 资源 并将其用于将自然语言转换为 SQL、生成电子邮件/短信以及处理自己的数据和文档。
- 通信练习:创建Azure 通信服务资源,并使用它从应用拨打电话并发送电子邮件/短信。
- 组织数据练习: 创建Microsoft Entra ID 应用注册 ,以便Microsoft Graph 和 Microsoft Graph 工具包可用于对组织数据进行身份验证并将组织数据拉取到应用程序中。
先决条件
- 节点 - 节点 20+ 和 npm 10+ 将用于此项目
- git
- Visual Studio Code (尽管建议使用 Visual Studio Code,但可以使用任何编辑器)
- Azure 订阅
- Microsoft 365 开发人员租户
- Docker Desktop 或其他符合 OCI(开放容器计划)的容器运行时,例如 Podman 或 能够运行容器的nerdctl 。
本教程中使用的Microsoft云技术
- Azure 通信服务
- Azure OpenAI 服务
- Microsoft Entra ID
- Microsoft Graph
- Microsoft Graph Toolkit
克隆项目
本教程中使用的代码项目在以下位置 https://github.com/microsoft/MicrosoftCloud提供。 项目的存储库包括运行项目所需的客户端代码和服务器端代码,使你能够探索与人工智能(AI)、通信和组织数据相关的集成功能。 此外,该项目充当资源,指导你将类似的功能合并到自己的应用程序中。
在本练习中,你将:
- 克隆 GitHub 存储库。
- 将 .env 文件添加到项目中并对其进行更新。
在继续之前,请确保已按照本教程的“先决条件”部分中所述安装和配置了所有先决条件。
克隆 GitHub 存储库并创建 .env
文件
运行以下命令,将 Microsoft Cloud GitHub 存储库 克隆到计算机。
git clone https://github.com/microsoft/MicrosoftCloud
在 Visual Studio Code 中打开 MicrosoftCloud/samples/openai-acs-msgraph 文件夹。
注意
尽管我们将在整个本教程中使用 Visual Studio Code,但任何代码编辑器都可用于处理示例项目。
请注意以下文件夹和文件:
- 客户端:客户端应用程序代码。
- 服务器:服务器端 API 代码。
- docker-compose.yml:用于运行本地 PostgreSQL 数据库。
将 项目根目录中的 .env.example 重命名为 .env。
打开 .env 文件,花点时间查看包含的密钥:
ENTRAID_CLIENT_ID= TEAM_ID= CHANNEL_ID= OPENAI_API_KEY= OPENAI_ENDPOINT= OPENAI_MODEL=gpt-4o OPENAI_API_VERSION=2024-05-01-preview POSTGRES_USER= POSTGRES_PASSWORD= ACS_CONNECTION_STRING= ACS_PHONE_NUMBER= ACS_EMAIL_ADDRESS= CUSTOMER_EMAIL_ADDRESS= CUSTOMER_PHONE_NUMBER= API_PORT=3000 AZURE_AI_SEARCH_ENDPOINT= AZURE_AI_SEARCH_KEY= AZURE_AI_SEARCH_INDEX=
更新 .env 中的以下值。 API 服务器将使用这些值连接到本地 PostgreSQL 数据库。
POSTGRES_USER=web POSTGRES_PASSWORD=web-password
准备好项目后,让我们尝试一些应用程序功能并了解如何生成它们。 选择下面的“下一步”按钮以继续使用或跳转到使用目录的特定练习。
AI:创建 Azure OpenAI 资源并部署模型
若要开始在应用程序中使用 Azure OpenAI,需要创建 Azure OpenAI 服务并部署可用于执行任务(例如将自然语言转换为 SQL、生成电子邮件/短信内容等)的模型。
在本练习中,你将:
- 创建 Azure OpenAI 服务资源。
- 部署模型。
- 使用 Azure OpenAI 服务资源中的值更新 .env 文件。
创建 Azure OpenAI 服务资源
在浏览器中访问Azure 门户并登录。
在门户页面顶部的搜索栏中输入 openai,然后从显示的选项中选择 Azure OpenAI。
在工具栏中选择“ 创建 ”。
注意
虽然本教程重点介绍 Azure OpenAI,但如果拥有 OpenAI API 密钥并想要使用它,则可以跳过本部分,直接转到下面的“ 更新项目的 .env 文件 ”部分。 将 OpenAI API 密钥分配到
OPENAI_API_KEY
.env 文件中(可以忽略与 OpenAI 相关的任何其他.env
说明)。Azure OpenAI 模型在特定区域中可用。 请访问 Azure OpenAI 模型可用性文档,了解哪些区域支持本教程中使用的 gpt-4o 模型。
执行以下任务:
- 选择 Azure 订阅。
- 选择要使用的资源组(根据需要创建新资源组)。
- 根据之前查看的文档,选择支持 gpt-4o 模型的区域。
- 输入资源名称。 它必须是唯一值。
- 选择 标准 S0 定价层。
选择“下一步”,直到进入“审阅 + 提交”屏幕。 选择创建。
创建 Azure OpenAI 资源后,导航到它并选择“资源管理”-->“密钥”和“终结点”。
找到 KEY 1 和终结点值。 在下一部分中,你将使用这两个值,以便将它们复制到本地文件。
选择 “资源管理 ”-->“模型部署”。
选择 “管理部署 ”按钮,转到 Azure OpenAI Studio。
选择“部署模型”-->在工具栏中选择“部署基本模型”。
从模型列表中选择 gpt-4o,然后选择“确认”。
注意
Azure OpenAI 支持 多种不同类型的模型。 每个模型都可用于处理不同的方案。
将显示以下对话框。 花点时间检查提供的默认值。
将 每分钟令牌速率限制(千) 值更改为 10 万。 这样,就可以向模型发出更多请求,并在执行以下步骤时避免达到速率限制。
选择“部署”。
部署模型后,选择“ 操场” -->Chat。
部署下拉列表应显示 gpt-4o 模型。
花点时间阅读 提供的系统消息 文本。 这会告知模型如何充当用户与之交互的方式。
在聊天区域中找到文本框,然后输入 “汇总生成 AI 是什么”以及如何使用它。 选择 Enter 以将消息发送到模型并使其生成响应。
尝试其他提示和响应。 例如,输入 “提供有关法国 首都的简短历史记录”,并注意生成的响应。
更新项目 .env
的文件
返回到 Visual Studio Code,在项目的根目录中打开
.env
该文件。从 Azure OpenAI 资源复制 KEY 1 值,并将其
OPENAI_API_KEY
分配给位于 openai-acs-msgraph 文件夹根目录中的 .env 文件中:OPENAI_API_KEY=<KEY_1_VALUE>
复制 *Endpoint 值并将其分配给
OPENAI_ENDPOINT
.env 文件中。 如果存在,请/
从值的末尾删除该字符。OPENAI_ENDPOINT=<ENDPOINT_VALUE>
注意
你将看到 .env 文件中已设置
OPENAI_MODEL
的值。OPENAI_API_VERSION
模型值设置为 gpt-4o ,它与本练习前面创建的模型部署名称匹配。 API 版本设置为 Azure OpenAI 参考文档中定义的受支持值。保存 .env 文件。
启动应用程序服务
是时候启动应用程序服务了,包括数据库、API 服务器和 Web 服务器。
在以下步骤中,将在 Visual Studio Code 中创建三个终端窗口。
右键单击 Visual Studio Code 文件列表中的 .env 文件,然后选择“ 在集成终端中打开”。 在继续操作之前,请确保终端位于项目的根目录中( openai-acs-msgraph )。
从 以下选项中选择 以启动 PostgreSQL 数据库:
如果已安装 并运行 Docker Desktop ,请在终端窗口中运行
docker-compose up
,然后按 Enter。如果 Podman 已安装 并正在运行 podman-compose ,请在终端窗口中运行
podman-compose up
,然后按 Enter。若要使用 Docker Desktop、Podman、nerdctl 或其他已安装的容器运行时直接运行 PostgreSQL 容器,请在终端窗口中运行以下命令:
Mac、Linux 或 适用于 Linux 的 Windows 子系统 (WSL):
[docker | podman | nerdctl] run --name postgresDb -e POSTGRES_USER=web -e POSTGRES_PASSWORD=web-password -e POSTGRES_DB=CustomersDB -v $(pwd)/data:/var/lib/postgresql/data -p 5432:5432 postgres
使用 PowerShell 的 Windows:
[docker | podman] run --name postgresDb -e POSTGRES_USER=web -e POSTGRES_PASSWORD=web-password -e POSTGRES_DB=CustomersDB -v ${PWD}/data:/var/lib/postgresql/data -p 5432:5432 postgres
数据库容器启动后,按 + Visual Studio Code 终端工具栏 中的图标创建第二个终端窗口。
cd
进入服务器/typescript 文件夹,并运行以下命令以安装依赖项并启动 API 服务器。npm install
npm start
+再次在 Visual Studio Code 终端工具栏中按图标以创建第三个终端窗口。
cd
进入客户端文件夹并运行以下命令以安装依赖项并启动 Web 服务器。npm install
npm start
浏览器将启动,你将转到该浏览器 http://localhost:4200。
AI:自然语言到 SQL
在考虑 AI 功能时,引用“只是因为你不能意味着你应该”是一个有用的指南。 例如,Azure OpenAI 的自然语言到 SQL 功能允许用户使用纯英语进行数据库查询,这是一种强大的工具,可提高工作效率。 但是, 强大的 并不总是意味着 适当的 或 安全的。 本练习将演示如何使用此 AI 功能,同时讨论在决定实施此功能之前要记住的重要注意事项。
下面是可用于从数据库检索数据的自然语言查询示例:
Get the the total revenue for all companies in London.
如果出现正确的提示,Azure OpenAI 会将此查询转换为 SQL,该查询可用于从数据库返回结果。 因此,非技术用户(包括业务分析师、营销人员和高管)可以更轻松地从数据库中检索有价值的信息,而无需处理复杂的 SQL 语法或依赖于受约束的数据网格和筛选器。 这种简化的方法可以通过消除用户向技术专家寻求帮助的需要来提高工作效率。
本练习提供了一个起点,可帮助你了解 SQL 的自然语言的工作原理、向你介绍一些重要注意事项、让你思考优缺点,并向你展示入门代码。
通过学习本练习,你将能够:
- 使用 GPT 提示将自然语言转换为 SQL。
- 尝试使用不同的 GPT 提示。
- 使用生成的 SQL 查询之前启动的 PostgreSQL 数据库。
- 从 PostgreSQL 返回查询结果,并在浏览器中显示它们。
首先,试验可用于将自然语言转换为 SQL 的不同 GPT 提示。
使用自然语言到 SQL 功能
在上一练习中,你启动了数据库、API 和应用程序。 还更新了
.env
该文件。 如果未完成这些步骤,请按照练习结束时的说明操作,然后继续操作。返回到浏览器(http://localhost:4200)并找到 datagrid 下方页面的“自定义查询 ”部分。 请注意,已包含示例查询值: 获取所有订单的总收入。按公司分组并包括城市。
选择运行查询按钮。 这会将用户的自然语言查询传递给 Azure OpenAI,它将转换为 SQL。 然后,将使用 SQL 查询查询数据库并返回任何潜在结果。
运行以下 自定义查询:
Get the total revenue for Adventure Works Cycles. Include the contact information as well.
查看在 Visual Studio Code 中运行 API 服务器的终端窗口,并注意到它显示从 Azure OpenAI 返回的 SQL 查询。 服务器端 API 使用 JSON 数据来查询 PostgreSQL 数据库。 查询中包含的任何字符串值将添加为参数值,以防止 SQL 注入攻击:
{ "sql": "SELECT c.company, c.city, c.email, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.company = $1 GROUP BY c.company, c.city, c.email", "paramValues": ["Adventure Works Cycles"] }
返回到浏览器并选择“重置数据”,在 datagrid 中再次查看所有客户。
探索自然语言到 SQL 代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
注意
本练习的目标是展示使用自然语言实现的 SQL 功能,并演示如何开始使用它。 如前所述,在继续执行任何实现之前,请务必讨论此类 AI 是否适合组织。 此外,还必须 规划适当的提示规则和数据库安全措施 ,以防止未经授权的访问和保护敏感数据。
现在,你已经了解了 SQL 功能的自然语言,接下来让我们来看看它是如何实现的。
打开 服务器/apiRoutes.ts 文件并找到
generateSql
路由。 此 API 路由由在浏览器中运行的客户端应用程序调用,用于从自然语言查询生成 SQL。 检索 SQL 查询后,它用于查询数据库并返回结果。router.post('/generateSql', async (req, res) => { const userPrompt = req.body.prompt; if (!userPrompt) { return res.status(400).json({ error: 'Missing parameter "prompt".' }); } try { // Call Azure OpenAI to convert the user prompt into a SQL query const sqlCommandObject = await getSQLFromNLP(userPrompt); let result: any[] = []; // Execute the SQL query if (sqlCommandObject && !sqlCommandObject.error) { result = await queryDb(sqlCommandObject) as any[]; } else { result = [ { query_error : sqlCommandObject.error } ]; } res.json(result); } catch (e) { console.error(e); res.status(500).json({ error: 'Error generating or running SQL query.' }); } });
请注意路由中的
generateSql
以下功能:- 它从中检索用户查询值
req.body.prompt
,并将其分配给名为 的userPrompt
变量。 此值将在 GPT 提示符中使用。 - 它调用函数
getSQLFromNLP()
以将自然语言转换为 SQL。 - 它将生成的 SQL 传递给一个名为
queryDb
执行 SQL 查询的函数,并从数据库返回结果。
- 它从中检索用户查询值
在 编辑器中打开服务器/openAI.ts 文件并找到函数
getSQLFromNLP()
。 此函数由generatesql
路由调用,用于将自然语言转换为 SQL。async function getSQLFromNLP(userPrompt: string): Promise<QueryData> { // Get the high-level database schema summary to be used in the prompt. // The db.schema file could be generated by a background process or the // schema could be dynamically retrieved. const dbSchema = await fs.promises.readFile('db.schema', 'utf8'); const systemPrompt = ` Assistant is a natural language to SQL bot that returns a JSON object with the SQL query and the parameter values in it. The SQL will query a PostgreSQL database. PostgreSQL tables with their columns: ${dbSchema} Rules: - Convert any strings to a PostgreSQL parameterized query value to avoid SQL injection attacks. - Return a JSON object with the following structure: { "sql": "", "paramValues": [] } Examples: User: "Display all company reviews. Group by company." Assistant: { "sql": "SELECT * FROM reviews", "paramValues": [] } User: "Display all reviews for companies located in cities that start with 'L'." Assistant: { "sql": "SELECT r.* FROM reviews r INNER JOIN customers c ON r.customer_id = c.id WHERE c.city LIKE 'L%'", "paramValues": [] } User: "Display revenue for companies located in London. Include the company name and city." Assistant: { "sql": "SELECT c.company, c.city, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.city = $1 GROUP BY c.company, c.city", "paramValues": ["London"] } User: "Get the total revenue for Adventure Works Cycles. Include the contact information as well." Assistant: { "sql": "SELECT c.company, c.city, c.email, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.company = $1 GROUP BY c.company, c.city, c.email", "paramValues": ["Adventure Works Cycles"] } `; let queryData: QueryData = { sql: '', paramValues: [], error: '' }; let results = ''; try { results = await callOpenAI(systemPrompt, userPrompt); if (results) { console.log('results', results); const parsedResults = JSON.parse(results); queryData = { ...queryData, ...parsedResults }; if (isProhibitedQuery(queryData.sql)) { queryData.sql = ''; queryData.error = 'Prohibited query.'; } } } catch (error) { console.log(error); if (isProhibitedQuery(results)) { queryData.sql = ''; queryData.error = 'Prohibited query.'; } else { queryData.error = results; } } return queryData; }
- 参数
userPrompt
将传递到函数中。 该值userPrompt
是用户在浏览器中输入的自然语言查询。 - 定义
systemPrompt
要使用的 AI 助手的类型和应遵循的规则。 这有助于 Azure OpenAI 了解数据库结构、应用的规则以及如何返回生成的 SQL 查询和参数。 - 调用名为
callOpenAI()
的函数,并systemPrompt
userPrompt
向其传递值。 - 检查结果以确保生成的 SQL 查询中不包含禁止的值。 如果找到禁止的值,则 SQL 查询将设置为空字符串。
- 参数
让我们更详细地演练系统提示:
const systemPrompt = ` Assistant is a natural language to SQL bot that returns a JSON object with the SQL query and the parameter values in it. The SQL will query a PostgreSQL database. PostgreSQL tables with their columns: ${dbSchema} Rules: - Convert any strings to a PostgreSQL parameterized query value to avoid SQL injection attacks. - Return a JSON object with the following structure: { "sql": "", "paramValues": [] } Examples: User: "Display all company reviews. Group by company." Assistant: { "sql": "SELECT * FROM reviews", "paramValues": [] } User: "Display all reviews for companies located in cities that start with 'L'." Assistant: { "sql": "SELECT r.* FROM reviews r INNER JOIN customers c ON r.customer_id = c.id WHERE c.city LIKE 'L%'", "paramValues": [] } User: "Display revenue for companies located in London. Include the company name and city." Assistant: { "sql": "SELECT c.company, c.city, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.city = $1 GROUP BY c.company, c.city", "paramValues": ["London"] } User: "Get the total revenue for Adventure Works Cycles. Include the contact information as well." Assistant: { "sql": "SELECT c.company, c.city, c.email, SUM(o.total) AS revenue FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE c.company = $1 GROUP BY c.company, c.city, c.email", "paramValues": ["Adventure Works Cycles"] } `;
定义了要使用的 AI 助手的类型。 在本例中,为 SQL 机器人提供“自然语言”。
定义了数据库中的表名和列。 提示中包含的高级架构可以在服务器/db.schema 文件中找到,如下所示。
- customers (id, company, city, email) - orders (id, customer_id, date, total) - order_items (id, order_id, product_id, quantity, price) - reviews (id, customer_id, review, date, comment)
提示
可以考虑创建仅包含数据用户的只读视图,以便使用自然语言查询 SQL。
定义规则以将任何字符串值转换为参数化查询值以避免 SQL 注入攻击。
定义规则以始终返回包含 SQL 查询的 JSON 对象及其参数值。
提供了示例用户提示和预期的 SQL 查询和参数值。 这称为 “很少的”学习。 虽然 LLM 是针对大量数据进行训练的,但它们只能适应几个示例的新任务。 另一种方法是“零射”学习,其中未提供任何示例,并且模型应生成正确的 SQL 查询和参数值。
该
getSQLFromNLP()
函数将系统和用户提示发送到也位于服务器/openAI.ts文件中的函数callOpenAI()
。 该callOpenAI()
函数通过检查环境变量来确定是否应调用 Azure OpenAI 服务或 OpenAI 服务。 如果环境变量中提供了密钥、终结点和模型,则会调用 Azure OpenAI,否则将调用 OpenAI。function callOpenAI(systemPrompt: string, userPrompt: string, temperature = 0, useBYOD = false) { const isAzureOpenAI = OPENAI_API_KEY && OPENAI_ENDPOINT && OPENAI_MODEL; if (isAzureOpenAI) { if (useBYOD) { return getAzureOpenAIBYODCompletion(systemPrompt, userPrompt, temperature); } return getAzureOpenAICompletion(systemPrompt, userPrompt, temperature); } return getOpenAICompletion(systemPrompt, userPrompt, temperature); }
注意
尽管我们将重点介绍整个本教程中的 Azure OpenAI,但如果仅提供
OPENAI_API_KEY
.env 文件中的值,应用程序将改用 OpenAI。 如果选择使用 OpenAI 而不是 Azure OpenAI,在某些情况下可能会看到不同的结果。找到函数
getAzureOpenAICompletion()
。async function getAzureOpenAICompletion(systemPrompt: string, userPrompt: string, temperature: number): Promise<string> { const completion = await createAzureOpenAICompletion(systemPrompt, userPrompt, temperature); let content = completion.choices[0]?.message?.content?.trim() ?? ''; console.log('Azure OpenAI Output: \n', content); if (content && content.includes('{') && content.includes('}')) { content = extractJson(content); } return content; }
此函数执行以下任务:
参数:
systemPrompt
,userPrompt
是temperature
主要参数。systemPrompt
:通知 Azure OpenAI 模型其角色和要遵循的规则。userPrompt
:包含用户提供的信息,例如用于生成输出的自然语言输入或规则。temperature
:指示模型的响应的创造力级别。 较高的值会导致更具创意的输出。
完成生成:
- 函数使用
systemPrompt
userPrompta0> 调用 createAzureOpenAICompletion()
并temperature
生成完成。 - 它会从完成中的第一个选项中提取内容,并剪裁任何额外的空格。
- 如果内容包含类似于 JSON 的结构(由存在
{
并且}
指示),则会提取 JSON 内容。
- 函数使用
日志记录和返回值:
- 该函数将 Azure OpenAI 输出记录到控制台。
- 它将处理的内容作为字符串返回。
找到函数
createAzureOpenAICompletion()
。async function createAzureOpenAICompletion(systemPrompt: string, userPrompt: string, temperature: number, dataSources?: any[]): Promise<any> { const baseEnvVars = ['OPENAI_API_KEY', 'OPENAI_ENDPOINT', 'OPENAI_MODEL']; const byodEnvVars = ['AZURE_AI_SEARCH_ENDPOINT', 'AZURE_AI_SEARCH_KEY', 'AZURE_AI_SEARCH_INDEX']; const requiredEnvVars = dataSources ? [...baseEnvVars, ...byodEnvVars] : baseEnvVars; checkRequiredEnvVars(requiredEnvVars); const config = { apiKey: OPENAI_API_KEY, endpoint: OPENAI_ENDPOINT, apiVersion: OPENAI_API_VERSION, deployment: OPENAI_MODEL }; const aoai = new AzureOpenAI(config); const completion = await aoai.chat.completions.create({ model: OPENAI_MODEL, // gpt-4o, gpt-3.5-turbo, etc. Pulled from .env file max_tokens: 1024, temperature, response_format: { type: "json_object", }, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt } ], // @ts-expect-error data_sources is a custom property used with the "Azure Add Your Data" feature data_sources: dataSources }); return completion; } function checkRequiredEnvVars(requiredEnvVars: string[]) { for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { throw new Error(`Missing ${envVar} in environment variables.`); } } }
此函数执行以下任务:
参数:
systemPrompt
,userPrompt
是temperature
前面讨论的主要参数。- 可选
dataSources
参数支持“Azure 自带数据”功能,本教程稍后将介绍此功能。
环境变量检查:
- 该函数验证基本环境变量的存在,如果缺少任何环境变量,则会引发错误。
配置对象:
config
使用文件中OPENAI_API_KEY
的值.env
(、、OPENAI_ENDPOINT
、OPENAI_API_VERSION
OPENAI_MODEL
)创建对象。 这些值用于构造用于调用 Azure OpenAI 的 URL。
AzureOpenAI 实例:
AzureOpenAI
的实例是使用config
对象创建的。 符号AzureOpenAI
是包的openai
一部分,应在文件顶部导入该包。
生成完成:
- 该
chat.completions.create()
函数使用以下属性调用:model
:指定文件中定义的.env
GPT 模型(例如 gpt-4o、gpt-3.5-turbo)。max_tokens
:定义完成的最大令牌数。temperature
:设置采样温度。 较高的值(例如 0.9)会产生更多的创造性响应,而较低的值(例如,0)会产生更具确定性的答案。response_format
:定义响应格式。 在这里,它设置为返回 JSON 对象。 有关 JSON 模式的更多详细信息,请参阅 Azure OpenAI 参考文档。messages
:包含用于生成聊天完成的消息。 此示例包含两条消息:一个来自系统(定义行为和规则),一个来自用户(包含提示文本)。
- 该
返回值:
- 该函数返回 Azure OpenAI 生成的完成对象。
注释掉函数中的
getSQLFromNLP()
以下行:// if (isProhibitedQuery(queryData.sql)) { // queryData.sql = ''; // }
保存 openAI.ts。 API 服务器将自动重新生成 TypeScript 代码并重启服务器。
返回到浏览器,在自定义查询输入中输入从数据库选择所有表名。 选择“运行查询”。 是否显示表名?
返回到
getSQLFromNLP()
服务器/openAI.ts中的函数,并将以下规则添加到Rules:
系统提示符部分,然后保存该文件。- Do not allow the SELECT query to return table names, function names, or procedure names.
返回到浏览器并执行以下任务:
- 在自定义查询输入中输入从数据库选择所有表名。 选择“运行查询”。 是否显示表名?
- 在自定义查询输入中输入“选择数据库中的所有函数名称”,然后再次选择“运行查询”。 是否显示函数名称?
问:模型是否始终遵循在提示中定义的规则?
答:不! 请务必注意,OpenAI 模型有时可能会返回可能与你定义的规则不匹配的意外结果。 在代码中规划这一点非常重要。
返回到 服务器/openAI.ts 并找到函数
isProhibitedQuery()
。 这是可在 Azure OpenAI 返回结果后运行后处理代码的示例。 请注意,如果在生成的 SQL 查询中返回禁止的关键字,它将属性sql
设置为空字符串。 这可确保如果从 Azure OpenAI 返回意外结果,则不会对数据库运行 SQL 查询。function isProhibitedQuery(query: string): boolean { if (!query) return false; const prohibitedKeywords = [ 'insert', 'update', 'delete', 'drop', 'truncate', 'alter', 'create', 'replace', 'information_schema', 'pg_catalog', 'pg_tables', 'pg_proc', 'pg_namespace', 'pg_class', 'table_schema', 'table_name', 'column_name', 'column_default', 'is_nullable', 'data_type', 'udt_name', 'character_maximum_length', 'numeric_precision', 'numeric_scale', 'datetime_precision', 'interval_type', 'collation_name', 'grant', 'revoke', 'rollback', 'commit', 'savepoint', 'vacuum', 'analyze' ]; const queryLower = query.toLowerCase(); return prohibitedKeywords.some(keyword => queryLower.includes(keyword)); }
注意
请务必注意,这只是演示代码。 如果选择将自然语言转换为 SQL,可能需要其他禁止的关键字来涵盖特定的用例。 这是一项功能,必须谨慎地规划和使用,以确保仅返回有效的 SQL 查询并针对数据库运行。 除了禁止的关键字,还需要考虑安全性。
返回到 服务器/openAI.ts 并取消注释函数
getSQLFromNLP()
中的以下代码。 保存文件。if (isProhibitedQuery(queryData.sql)) { queryData.sql = ''; }
从
systemPrompt
中删除以下规则并保存文件。- Do not allow the SELECT query to return table names, function names, or procedure names.
返回到浏览器,再次将数据库中的所有表名称输入到自定义查询输入中,然后选择“运行查询”按钮。
是否显示任何表结果? 即使没有规则,
isProhibitedQuery()
后期处理代码也禁止对数据库运行该类型的查询。如前所述,将自然语言集成到业务线应用程序中的 SQL 对用户非常有用,但它确实附带了自己的一组注意事项。
优点:
用户友好性:此功能可让用户更方便地进行数据库交互,而无需具备技术专业知识,从而减少对 SQL 知识的需求,并可能加快操作速度。
提高工作效率:业务分析师、营销人员、管理人员和其他非技术用户无需依赖技术专家即可从数据库中检索有价值的信息,从而提高效率。
广泛的应用程序:通过使用高级语言模型,应用程序可以设计为迎合广泛的用户和用例。
注意事项:
安全:最大的担忧之一是安全。 如果用户可以使用自然语言与数据库交互,则需要采取可靠的安全措施来防止未经授权的访问或恶意查询。 可以考虑实现只读模式,以防止用户修改数据。
数据隐私:某些数据可能很敏感,不应易于访问,因此你需要确保适当的保护措施和用户权限到位。
准确性:虽然自然语言处理已显著改善,但它并不完美。 误解用户查询可能会导致结果不准确或意外行为。 你需要计划如何处理意外的结果。
效率:不能保证从自然语言查询返回的 SQL 将高效。 在某些情况下,如果处理后规则检测到 SQL 查询问题,可能需要对 Azure OpenAI 的其他调用。
培训和用户适应:需要训练用户才能正确制定查询。 虽然它比学习 SQL 更容易,但仍可能涉及学习曲线。
在继续下一练习之前要考虑的几个最后要点:
- 请记住,“只是因为你不能意味着你应该”在这里适用。 在将自然语言集成到应用程序中之前,请谨慎地进行规划。 了解潜在风险并规划风险非常重要。
- 在使用这种类型的技术之前,请与团队、数据库管理员、安全团队、利益干系人以及任何其他相关方讨论潜在方案,以确保它适合你的组织。 请务必讨论 SQL 的自然语言是否满足安全、隐私以及组织可能满足的任何其他要求。
- 安全性应该是一个主要问题,并内置于规划、开发和部署过程中。
- 虽然 SQL 的自然语言可能非常强大,但仔细规划必须进入它,以确保提示具有所需的规则,并包含后期处理功能。 计划额外的时间来实施和测试这种类型的功能,并考虑返回意外结果的方案。
- 使用 Azure OpenAI,客户可在运行与 OpenAI 相同的模型时获得 Microsoft Azure 的安全功能。 Azure OpenAI 提供专用网络、区域可用性和负责任 AI 内容筛选功能。 详细了解 Azure OpenAI 服务的数据、隐私和安全性。
现在,你已了解如何使用 Azure OpenAI 将自然语言转换为 SQL,并了解了实现此类功能的优缺点。 在下一练习中,你将了解如何使用 Azure OpenAI 生成电子邮件和短信。
AI:生成完成
除了 SQL 功能的自然语言以外,还可以使用 Azure OpenAI 服务生成电子邮件和短信,以提高用户工作效率并简化通信工作流。 通过使用 Azure OpenAI 的语言生成功能,用户可以定义特定的规则,例如“订单延迟 5 天”,系统将根据这些规则自动生成上下文适当的电子邮件和短信。
此功能为用户提供了一个精心制作的邮件模板,在发送之前,用户可以轻松自定义此功能。 结果是撰写消息所需的时间和工作量显著减少,使用户能够专注于其他重要任务。 此外,Azure OpenAI 的语言生成技术可以集成到自动化工作流中,使系统能够自主生成和发送消息,以响应预定义的触发器。 这种自动化级别不仅加速了通信过程,而且可确保跨各种方案实现一致的准确消息传送。
通过学习本练习,你将能够:
- 尝试使用不同的提示。
- 使用提示生成电子邮件和短信的完成。
- 浏览启用 AI 完成的代码。
- 了解提示工程的重要性,并在提示中包含规则。
让我们开始使用可用于生成电子邮件和短信的不同规则。
使用 AI 完成功能
在上一练习中,你启动了数据库、API 和应用程序。 还更新了
.env
该文件。 如果未完成这些步骤,请按照练习结束时的说明操作,然后继续操作。返回到浏览器(http://localhost:4200)并选择 Datagrid 中任意行上的“联系客户 ”,然后 是“电子邮件/短信客户 ” 转到“消息生成器 ”屏幕。
这使用 Azure OpenAI 将定义的消息规则转换为电子邮件/短信。 执行以下任务:
输入“订单”等 规则在输入中延迟 5 天 ,然后选择“ 生成电子邮件/短信 ”按钮。
你将看到为电子邮件生成的主题和正文,以及为短信生成的短消息。
注意
由于尚未启用Azure 通信服务,因此无法发送电子邮件或短信。
关闭浏览器中的电子邮件/短信对话框窗口。 现在,你已看到此功能在操作中,让我们来看看它是如何实现的。
探索 AI 完成代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
打开 服务器/apiRoutes.ts 文件并找到
completeEmailSmsMessages
路由。 选择“生成电子邮件/短信”按钮时,应用的前端部分将调用此 API。 它会从正文中检索用户提示、公司和联系人姓名值,并将其completeEmailSMSMessages()
传递到服务器/openAI.ts文件中的函数。 然后,结果将返回到客户端。router.post('/completeEmailSmsMessages', async (req, res) => { const { prompt, company, contactName } = req.body; if (!prompt || !company || !contactName) { return res.status(400).json({ status: false, error: 'The prompt, company, and contactName parameters must be provided.' }); } let result; try { // Call OpenAI to get the email and SMS message completions result = await completeEmailSMSMessages(prompt, company, contactName); } catch (e: unknown) { console.error('Error parsing JSON:', e); } res.json(result); });
打开 服务器/openAI.ts 文件并找到函数
completeEmailSMSMessages()
。async function completeEmailSMSMessages(prompt: string, company: string, contactName: string) { console.log('Inputs:', prompt, company, contactName); const systemPrompt = ` Assistant is a bot designed to help users create email and SMS messages from data and return a JSON object with the email and SMS message information in it. Rules: - Generate a subject line for the email message. - Use the User Rules to generate the messages. - All messages should have a friendly tone and never use inappropriate language. - SMS messages should be in plain text format and NO MORE than 160 characters. - Start the message with "Hi <Contact Name>,\n\n". Contact Name can be found in the user prompt. - Add carriage returns to the email message to make it easier to read. - End with a signature line that says "Sincerely,\nCustomer Service". - Return a valid JSON object with the emailSubject, emailBody, and SMS message values in it: { "emailSubject": "", "emailBody": "", "sms": "" } - The sms property value should be in plain text format and NO MORE than 160 characters. `; const userPrompt = ` User Rules: ${prompt} Contact Name: ${contactName} `; let content: EmailSmsResponse = { status: true, email: '', sms: '', error: '' }; let results = ''; try { results = await callOpenAI(systemPrompt, userPrompt, 0.5); if (results) { const parsedResults = JSON.parse(results); content = { ...content, ...parsedResults, status: true }; } } catch (e) { console.log(e); content.status = false; content.error = results; } return content; }
此函数具有以下功能:
systemPrompt
用于定义能够生成电子邮件和短信的 AI 助手。 还包括systemPrompt
:- 助理遵循的规则来控制消息的语气、开始和结束格式、短信的最大长度等。
- 有关响应中应包含的数据的信息 - 在本例中为 JSON 对象。
userPrompt
用于定义最终用户希望在生成电子邮件和短信时包含的规则和联系人名称。 之前 输入的订单延迟 5 天 规则包含在内userPrompt
。- 该函数调用
callOpenAI()
前面浏览的函数以生成电子邮件和短信完成。
返回到浏览器,刷新页面,然后在电子邮件/短信客户之后的任何行上选择“联系客户”,以再次转到“消息生成器”屏幕。
在消息生成器输入中输入以下规则:
- 订单提前。
- 告诉客户永远不要再向我们订购,我们不希望他们的业务。
选择“ 生成电子邮件/短信 ”并记下该邮件。 系统
All messages should have a friendly tone and never use inappropriate language.
提示中的规则将覆盖用户提示中的负规则。返回到编辑器中的服务器/openAI.ts* 并从函数中的
completeEmailSMSMessages()
提示中删除All messages should have a friendly tone and never use inappropriate language.
规则。 保存文件。返回到浏览器中的电子邮件/短信生成器,并再次运行相同的规则:
- 订单提前。
- 告诉客户永远不要再向我们订购,我们不希望他们的业务。
选择“ 生成电子邮件/短信 ”,并注意到返回的消息。
在这些方案中会发生什么情况? 使用 Azure OpenAI 时, 可以应用内容筛选 以确保始终使用适当的语言。 如果使用 OpenAI,则系统提示中定义的规则用于确保返回的消息合适。
注意
这说明了使用正确的信息和规则设计提示的重要性,以确保返回正确的结果。 在提示工程文档简介中阅读有关此过程的详细信息。
撤消对
systemPrompt
文件中completeEmailSMSMessages()
所做的更改,保存文件,然后重新运行该文件,但仅使用Order is ahead of schedule.
规则(不包括负规则)。 这一次,应会看到电子邮件和短信按预期返回。在继续下一练习之前要考虑的几个最后要点:
- 请务必让循环中的人员查看生成的消息。 在此示例中,Azure OpenAI 完成返回建议的电子邮件和短信,但用户可以在发送之前覆盖这些消息。 如果计划自动执行电子邮件,请执行某种类型的人工审阅过程,以确保已批准的邮件被发送非常重要。 将 AI 视为警戒,而不是 Autopilot。
- 完成仅与添加到提示中的规则一样好。 花时间测试提示和返回的完成。 请考虑使用 提示流 创建一个全面的解决方案,以简化原型制作、试验、迭代和部署 AI 应用程序。 邀请其他项目利益干系人查看完成情况。
- 可能需要包括处理后代码,以确保正确处理意外结果。
- 使用系统提示定义 AI 助手应遵循的规则和信息。 使用用户提示定义最终用户希望包括在完成中的规则和信息。
AI:数据上的 Azure OpenAI
Azure OpenAI 自然语言处理(NLP)和完成功能集成为提高用户工作效率提供了重大潜力。 通过利用适当的提示和规则,AI 助手可以有效地生成各种形式的通信,例如电子邮件、短信等。 此功能可提高用户效率和简化工作流。
虽然此功能本身非常强大,但在某些情况下,用户可能需要根据公司的自定义数据生成完成。 例如,你可能有一系列产品手册,在用户协助客户解决安装问题时可能很难导航。 或者,你可以维护与医疗保健权益相关的一组全面的常见问题解答(常见问题解答),这可能证明用户难以阅读并获取他们需要的答案。 在这些情况下,Azure OpenAI 服务使你能够利用自己的数据生成完成情况,确保对用户问题做出更定制和上下文准确的响应。
下面是 Azure OpenAI 文档中“自带数据”功能的快速概述。
注意
基于自有数据的 Azure OpenAI 的主要特点之一是它能够以增强模型输出的方式检索和利用数据。 基于自有数据的 Azure OpenAI 与 Azure AI 搜索相配合,可以根据用户输入和提供的对话历史记录确定从指定数据源检索哪些数据。 然后,此数据经过增强,作为提示重新提交到 OpenAI 模型,并将检索到的信息追加到原始提示中。 尽管检索到的数据将追加到提示中,但生成的输入仍由模型处理,就像处理任何其他提示一样。 检索数据并将提示提交到模型后,模型将使用此信息提供补全。
通过学习本练习,你将能够:
- 使用 Azure AI Studio 创建自定义数据源。
- 使用 Azure AI Studio 部署嵌入模型。
- 上传自定义文档。
- 在聊天操场中启动聊天会话,以试验基于自己的数据生成完成。
- 浏览使用 Azure AI 搜索和 Azure OpenAI 生成基于你自己的数据完成的代码。
让我们首先在 Azure AI Studio 中部署嵌入模型和添加自定义数据源。
将自定义数据源添加到 Azure AI Studio
导航到 Azure OpenAI Studio 并使用有权访问 Azure OpenAI 资源的凭据登录。
从导航菜单中选择“部署”。
选择“ 部署模型 ”-->工具栏中的“部署基本模型 ”。
从模型列表中选择文本嵌入-ada-002 模型,然后选择“确认”。
选择以下选项:
- 部署名称: text-embedding-ada-002
- 模型版本: 默认
- 部署类型: 标准
- 将 每分钟令牌速率限制 (千) 值设置为 120K
- 内容筛选器: DefaultV2
- 启用动态引号: 已启用
选择“部署”按钮。
创建模型后, 从导航菜单中选择“开始 ”转到欢迎屏幕。
在 欢迎屏幕上找到“自带数据 ”磁贴,然后选择“ 立即试用”。
选择“ 添加数据 ”,然后选择 “添加数据源”。
在 “选择数据源 ”下拉列表中,选择“ 上传文件”。
在 “选择 Azure Blob 存储资源 ”下拉列表下,选择“ 创建新的 Azure Blob 存储资源”。
在 “订阅”下拉列表中选择 Azure 订阅 。
在 “选择 Azure Blob 存储资源 ”下拉列表下,选择“ 创建新的 Azure Blob 存储资源”。
这会将你带到Azure 门户,你可以在其中执行以下任务:
- 输入存储帐户的唯一名称,例如 byodstorage[姓氏]。。
- 选择靠近你的位置的区域。
- 选择“审阅”,然后选择“创建”。
创建 Blob 存储资源后,返回到 Azure AI Studio 对话框,然后从 “选择 Azure Blob 存储资源”下拉列表中选择新创建的 Blob 存储资源 。 如果未列出它,请选择下拉列表旁边的刷新图标。
需要启用跨域资源共享(CORS),以便访问存储帐户。 在 Azure AI Studio 对话框中选择 “打开 CORS ”。
在 “选择 Azure AI 搜索资源 ”下拉列表下,选择“ 创建新的 Azure AI 搜索资源”。
这会将你带回Azure 门户,你可以在其中执行以下任务:
- 输入 AI 搜索资源的唯一名称,例如 byodsearch-[姓氏]。。
- 选择靠近你的位置的区域。
- 在“定价层”部分中,选择“更改定价层”,然后选择“基本”,然后选择“选择”。 不支持免费层,因此在本教程结束时,你将清理 AI 搜索资源。
- 选择“审阅”,然后选择“创建”。
创建 AI 搜索资源后,转到“资源 概述 ”页,并将 URL 值复制到本地文件。
在 导航菜单中选择“设置 ”-->“键 ”。
在“API 访问控制”页上,选择“两者”以允许使用托管标识或密钥访问服务。 出现提示时选择“是”。
注意
尽管我们将在本练习中使用 API 密钥,因为添加角色分配最多可能需要 10 分钟,但可以稍作额外努力,使系统分配的托管标识能够更安全地访问服务。
在 左侧导航菜单中选择“密钥 ”,并将 “主管理密钥 ”值复制到本地文件。 稍后在练习中需要 URL 和键值。
在 导航菜单中选择“设置 ”-->语义排名器 ,并确保 已选择“免费 ”。
注意
若要检查语义排名器在特定区域中是否可用, 请检查 Azure 网站上的“产品可用 区域”页,以查看区域是否已列出。
返回到“Azure AI Studio 添加数据 ”对话框,然后从 “选择 Azure AI 搜索资源”下拉列表中选择新创建的搜索资源 。 如果未列出它,请选择下拉列表旁边的刷新图标。
输入输入索引名称值 byod-search-index 的值。
选中“ 将此搜索资源 添加矢量搜索”复选框。
在 “选择嵌入模型 ”下拉列表中,选择 前面创建的 text-embedding-ada-002 模型。
在 “上传文件 ”对话框中,选择“ 浏览文件”。
导航到项目的客户文档文件夹(位于项目的根目录),然后选择以下文件:
- 时钟 A102 安装Instructions.docx
- 公司FAQs.docx
注意
此功能目前支持以下文件格式进行本地索引创建:.txt、.md、.html、.pdf、.docx和.pptx。
选择“上传文件”。 这些文件将上传到 前面创建的 Blob 存储资源的 fileupload-byod-search-index 容器中。
选择“下一步”转到“数据管理”对话框。
在 “搜索类型 ”下拉列表中,选择“ 混合 + 语义”。
注意
此选项支持关键字和矢量搜索。 返回结果后,使用深度学习模型将辅助排名过程应用于结果集,从而提高用户的搜索相关性。 若要了解有关语义搜索的详细信息,请查看 Azure AI 搜索文档中的 语义搜索 。
确保 “选择大小 ”值设置为 1024。
选择下一步。
对于 Azure 资源身份验证类型,请选择 API 密钥。 详细了解如何在 Azure AI 搜索身份验证文档中选择正确的身份验证类型。
选择下一步。
查看详细信息,然后选择“ 保存并关闭”。
上传自定义数据后,数据将编制索引并可用于 聊天操场。 这个过程可能需要几分钟。 完成后,请继续下一部分。
在聊天操场中使用自定义数据源
在 Azure OpenAI Studio 中找到页面的聊天会话 部分,并输入以下 用户查询:
What safety rules are required to install a clock?
提交用户查询后,应会看到如下所示的结果:
展开聊天响应中的 1 个引用部分,并注意到列出了时钟 A102 安装Instructions.docx文件,你可以将其选中以查看文档。
输入以下 用户消息:
What should I do to mount the clock on the wall?
应会看到如下所示的结果:
现在,让我们试验公司常见问题解答文档。 在“用户查询”字段中输入以下文本:
What is the company's policy on vacation time?
应会看到该请求未找到任何信息。
在“用户查询”字段中输入以下文本:
How should I handle refund requests?
应会看到如下所示的结果:
展开聊天响应中的 1 个引用部分,并注意到公司FAQs.docx文件已列出,你可以将其选中以查看文档。
选择聊天操场工具栏中的“查看代码”。
请注意,可以在不同语言之间切换,查看终结点并访问终结点的密钥。 关闭“ 示例代码 ”对话框窗口。
打开聊天消息上方的 “显示原始 JSON ”切换开关。 请注意,聊天会话以类似于以下内容的消息开头:
{ "role": "system", "content": "You are an AI assistant that helps people find information." }
创建自定义数据源并在聊天操场中对其进行试验后,让我们看看如何在项目的应用程序中使用它。
在应用程序中使用“自带数据”功能
返回到 VS Code 中的项目并打开 .env 文件。 使用 AI Services 终结点、密钥和索引名称更新以下值。 在本练习中,你已将终结点和密钥复制到本地文件。
AZURE_AI_SEARCH_ENDPOINT=<AI_SERVICES_ENDPOINT_VALUE> AZURE_AI_SEARCH_KEY=<AI_SERVICES_KEY_VALUE> AZURE_AI_SEARCH_INDEX=byod-search-index
在上一练习中,你启动了数据库、API 和应用程序。 还更新了
.env
该文件。 如果未完成这些步骤,请按照前面的练习结束时的说明继续操作。应用程序加载到浏览器中后,选择 应用程序右上角的“聊天帮助 ”图标。
聊天对话框中应显示以下文本:
How should I handle a company refund request?
选择“获取帮助”按钮。 应会看到之前 在 Azure OpenAI Studio 中上传的公司FAQs.docx 文档返回的结果。 如果要阅读文档,可以在项目根目录的“客户文档”文件夹中找到该 文档 。
将文本更改为以下内容,然后选择“ 获取帮助 ”按钮:
What safety rules are required to install a clock?
应会看到之前 在 Azure OpenAI Studio 中上传的时钟 A102 安装Instructions.docx 文档返回的结果。 此文档也位于 项目根目录的客户文档 文件夹中。
浏览代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
返回到 Visual Studio Code 中的项目源代码。
打开 服务器/apiRoutes.ts 文件并找到
completeBYOD
路由。 在“聊天帮助”对话框中选择“获取帮助”按钮时,将调用此 API。 它会从请求正文中检索用户提示,并将其completeBYOD()
传递给服务器/openAI.ts文件中的函数。 然后,结果将返回到客户端。router.post('/completeBYOD', async (req, res) => { const { prompt } = req.body; if (!prompt) { return res.status(400).json({ status: false, error: 'The prompt parameter must be provided.' }); } let result; try { // Call OpenAI to get custom "bring your own data" completion result = await completeBYOD(prompt); } catch (e: unknown) { console.error('Error parsing JSON:', e); } res.json(result); });
打开 服务器/openAI.ts 文件并找到函数
completeBYOD()
。async function completeBYOD(userPrompt: string): Promise<string> { const systemPrompt = 'You are an AI assistant that helps people find information in documents.'; return await callOpenAI(systemPrompt, userPrompt, 0, true); }
此函数具有以下功能:
- 该
userPrompt
参数包含用户键入到聊天帮助对话框中的信息。 - 该
systemPrompt
变量定义用于帮助人们查找信息的 AI 助手。 callOpenAI()
用于调用 Azure OpenAI API 并返回结果。 它传递systemPrompt
和userPrompt
值以及以下参数:temperature
- 要包括在响应中的创造力量。 在这种情况下,用户需要一致的(较少创意)答案,因此该值设置为 0。useBYOD
- 一个布尔值,该值指示是否将 AI 搜索与 Azure OpenAI 一起使用。 在本例中,它设置为true
使用 AI 搜索功能。
- 该
该
callOpenAI()
函数接受用于useBYOD
确定要调用的 OpenAI 函数的参数。 在这种情况下,它将设置为useBYOD
true
调用函数getAzureOpenAIBYODCompletion()
。function callOpenAI(systemPrompt: string, userPrompt: string, temperature = 0, useBYOD = false) { const isAzureOpenAI = OPENAI_API_KEY && OPENAI_ENDPOINT && OPENAI_MODEL; if (isAzureOpenAI) { if (useBYOD) { return getAzureOpenAIBYODCompletion(systemPrompt, userPrompt, temperature); } return getAzureOpenAICompletion(systemPrompt, userPrompt, temperature); } return getOpenAICompletion(systemPrompt, userPrompt, temperature); }
在服务器/openAI.ts中找到函数
getAzureOpenAIBYODCompletion()
。 它与前面检查的getAzureOpenAICompletion()
函数非常相似,但显示为单独的函数,以突出显示 Azure OpenAI 在 Azure OpenAI 中提供的“Azure OpenAI 数据”方案特有的一些主要差异。async function getAzureOpenAIBYODCompletion(systemPrompt: string, userPrompt: string, temperature: number): Promise<string> { const dataSources = [ { type: 'azure_search', parameters: { authentication: { type: 'api_key', key: AZURE_AI_SEARCH_KEY }, endpoint: AZURE_AI_SEARCH_ENDPOINT, index_name: AZURE_AI_SEARCH_INDEX } } ]; const completion = await createAzureOpenAICompletion(systemPrompt, userPrompt, temperature, dataSources) as AzureOpenAIYourDataResponse; console.log('Azure OpenAI Add Your Own Data Output: \n', completion.choices[0]?.message); for (let citation of completion.choices[0]?.message?.context?.citations ?? []) { console.log('Citation Path:', citation.filepath); } return completion.choices[0]?.message?.content?.trim() ?? ''; }
请注意函数中的
getAzureOpenAIBYODCompletion()
以下功能:- 将创建一个
dataSources
属性,其中包含 AI 搜索资源的key
,endpoint
以及index_name
在本练习前面添加到.env
文件的值 - 该
createAzureOpenAICompletion()
函数使用systemPrompt
userPrompt temperature
> 和dataSources
值调用。 此函数用于调用 Azure OpenAI API 并返回结果。 - 返回响应后,文档引文将记录到控制台。 然后,完成消息内容将返回到调用方。
- 将创建一个
在继续下一练习之前要考虑的几个最后要点:
- 示例应用程序在 Azure AI 搜索中使用单个索引。 可以将多个索引和数据源与 Azure OpenAI 配合使用。
dataSources
函数中的getAzureOpenAIBYODCompletion()
属性可以更新为根据需要包含多个数据源。 - 必须使用这种类型的方案仔细评估安全性。 用户不应能够提出问题并从他们无法访问的文档获取结果。
- 示例应用程序在 Azure AI 搜索中使用单个索引。 可以将多个索引和数据源与 Azure OpenAI 配合使用。
现在,你已了解 Azure OpenAI、提示、完成以及如何使用自己的数据,接下来让我们转到下一个练习,了解如何使用通信功能来增强应用程序。 若要了解有关 Azure OpenAI 的详细信息,请查看 Azure OpenAI 服务 培训内容入门。 有关将自己的数据与 Azure OpenAI 配合使用的其他信息,请参阅 Azure OpenAI 的数据 文档。
通信:创建Azure 通信服务资源
有效的通信对于成功的自定义业务应用程序至关重要。 通过使用 Azure 通信服务 (ACS),可以向应用程序添加电话呼叫、实时聊天、音频/视频呼叫和电子邮件和短信等功能。 之前,你了解了 Azure OpenAI 如何为电子邮件和短信生成完成。 现在,你将了解如何发送消息。 ACS 和 OpenAI 一起可以通过简化通信、改进交互并提高业务工作效率来增强应用程序。
通过学习本练习,你将能够:
- 创建Azure 通信服务 (ACS) 资源。
- 添加带呼叫和短信功能的免费电话号码。
- 连接电子邮件域。
- 使用 ACS 资源中的值更新项目的 .env 文件。
创建Azure 通信服务资源
访问浏览器中的Azure 门户,并登录(如果尚未登录)。
在页面顶部的搜索栏中键入通信服务,然后从出现的选项中选择通信服务。
在工具栏中选择“ 创建 ”。
执行以下任务:
- 选择 Azure 订阅。
- 选择要使用的资源组(如果不存在资源组)。
- 输入 ACS 资源名称。 它必须是唯一值。
- 选择数据位置。
选择“ 查看 + 创建 ”,然后选择 “创建”。
已成功创建新的Azure 通信服务资源! 接下来,你将启用电话呼叫和短信功能。 你还将将电子邮件域连接到资源。
启用电话呼叫和短信功能
添加电话号码并确保电话号码已启用呼叫功能。 你将使用此电话号码从应用呼叫电话。
从 “资源”菜单中选择“电话”和“短信 ”-“>电话号码 ”。
在 工具栏中选择“+ 获取 ”(或选择“ 获取数字 ”按钮),然后输入以下信息:
- 国家或地区:
United States
- 数字类型:
Toll-free
注意
Azure 订阅需要信用卡才能创建免费号码。 如果没有文件卡,可以随意跳过添加电话号码并跳转到连接电子邮件域的练习的下一部分。 你仍然可以使用该应用,但无法拨打电话号码。
- 号码:为列出的某个电话号码选择 “添加到购物车 ”。
- 国家或地区:
选择“下一步”,查看电话号码详细信息,然后选择“立即购买”。
注意
美国和加拿大现在强制对免费号码进行短信验证。 若要启用短信,必须在购买电话号码后提交验证。 虽然本教程不会完成此过程,但可以从 资源菜单中选择电话和短信 --法规>文档 ,并添加所需的验证文档。
创建电话号码后,将其选中以进入 “功能 ”面板。 确保设置了以下值(应默认设置这些值):
- 在 “呼叫 ”部分中,选择
Make calls
。 - 在 “短信 ”部分中,选择
Send and receive SMS
。 - 选择“保存”。
- 在 “呼叫 ”部分中,选择
将电话号码值复制到文件中供以后使用。 电话号码应遵循以下常规模式:
+12345678900
连接电子邮件域
执行以下任务,为 ACS 资源创建连接的电子邮件域,以便发送电子邮件。 这将用于从应用发送电子邮件。
- 从“资源”菜单中选择“电子邮件”-->“域”。
- 从工具栏中选择 “连接”域 。
- 选择订阅和资源组。
- 在 “电子邮件服务 ”下拉列表下,选择
Add an email service
。 - 为电子邮件服务提供一个名称,例如
acs-demo-email-service
。 - 选择“ 查看 + 创建 ” ,然后选择“创建”。
- 部署完成后,选择
Go to resource
并选择1-click add
添加免费的 Azure 子域。 - 添加子域后(部署子域需要一些时间),将其选中。
- 进入 AzureManagedDomain 屏幕后,从“资源”菜单中选择“电子邮件服务-->MailFrom 地址”。
- 将 MailFrom 值复制到文件。 稍后将在更新 .env 文件时使用它。
- 返回到Azure 通信服务资源,然后从资源菜单中选择“电子邮件-域>”。
- 选择
Add domain
并输入MailFrom
上一步中的值(确保选择正确的订阅、资源组和电子邮件服务)。 选择Connect
按钮。
.env
更新文件
现在 ACS 电话号码(已启用呼叫和短信)和电子邮件域已准备就绪,请在项目中的 .env 文件中更新以下键/值:
ACS_CONNECTION_STRING=<ACS_CONNECTION_STRING> ACS_PHONE_NUMBER=<ACS_PHONE_NUMBER> ACS_EMAIL_ADDRESS=<ACS_EMAIL_ADDRESS> CUSTOMER_EMAIL_ADDRESS=<EMAIL_ADDRESS_TO_SEND_EMAIL_TO> CUSTOMER_PHONE_NUMBER=<UNITED_STATES_BASED_NUMBER_TO_SEND_SMS_TO>
ACS_CONNECTION_STRING
connection string
:ACS 资源的“密钥”部分的值。ACS_PHONE_NUMBER
:将免费号码分配给ACS_PHONE_NUMBER
该值。ACS_EMAIL_ADDRESS
:分配电子邮件“MailTo”地址值。CUSTOMER_EMAIL_ADDRESS
:分配要从应用发送到的电子邮件地址(因为应用数据库中的客户数据只是示例数据)。 可以使用个人电子邮件地址。CUSTOMER_PHONE_NUMBER
:由于在其他国家/地区发送短信所需的其他验证,你需要提供基于美国的电话号码(截至今天)。 如果没有基于美国的数字,则可以将其留空。
启动/重启应用程序和 API 服务器
根据你完成的练习执行 以下步骤之一 ,至此时间点:
如果在前面的练习中启动了数据库、API 服务器和 Web 服务器,则需要停止 API 服务器和 Web 服务器并重新启动它们以选取 .env 文件更改。 可以让数据库保持运行状态。
找到运行 API 服务器和 Web 服务器的终端窗口,然后按 Ctrl + C 停止它们。 通过在每个终端窗口中键入
npm start
并按 Enter 再次启动它们。 继续下一练习。如果尚未启动数据库、API 服务器和 Web 服务器,请完成以下步骤:
在以下步骤中,将在 Visual Studio Code 中创建三个终端窗口。
右键单击 Visual Studio Code 文件列表中的 .env 文件,然后选择“ 在集成终端中打开”。 在继续操作之前,请确保终端位于项目的根目录中( openai-acs-msgraph )。
从 以下选项中选择 以启动 PostgreSQL 数据库:
如果已安装 并运行 Docker Desktop ,请在终端窗口中运行
docker-compose up
,然后按 Enter。如果 Podman 已安装 并正在运行 podman-compose ,请在终端窗口中运行
podman-compose up
,然后按 Enter。若要使用 Docker Desktop、Podman、nerdctl 或其他已安装的容器运行时直接运行 PostgreSQL 容器,请在终端窗口中运行以下命令:
Mac、Linux 或 适用于 Linux 的 Windows 子系统 (WSL):
[docker | podman | nerdctl] run --name postgresDb -e POSTGRES_USER=web -e POSTGRES_PASSWORD=web-password -e POSTGRES_DB=CustomersDB -v $(pwd)/data:/var/lib/postgresql/data -p 5432:5432 postgres
使用 PowerShell 的 Windows:
[docker | podman] run --name postgresDb -e POSTGRES_USER=web -e POSTGRES_PASSWORD=web-password -e POSTGRES_DB=CustomersDB -v ${PWD}/data:/var/lib/postgresql/data -p 5432:5432 postgres
数据库容器启动后,按 + Visual Studio Code 终端工具栏 中的图标创建第二个终端窗口。
cd
进入服务器/typescript 文件夹,并运行以下命令以安装依赖项并启动 API 服务器。npm install
npm start
+再次在 Visual Studio Code 终端工具栏中按图标以创建第三个终端窗口。
cd
进入客户端文件夹并运行以下命令以安装依赖项并启动 Web 服务器。npm install
npm start
浏览器将启动,你将转到该浏览器 http://localhost:4200。
通信:拨打电话
将Azure 通信服务的电话呼叫功能集成到自定义业务线(LOB)应用程序中,为企业及其用户提供了几个关键优势:
- 直接从 LOB 应用程序中实现员工、客户和合作伙伴之间的无缝实时通信,无需在多个平台或设备之间切换。
- 增强用户体验并提高整体运营效率。
- 帮助快速解决问题,因为用户可以快速轻松地与相关的支持团队或主题专家联系。
通过学习本练习,你将能够:
- 浏览应用程序中的电话呼叫功能。
- 演练代码,了解如何生成电话呼叫功能。
使用电话呼叫功能
在上一练习中,你创建了一个Azure 通信服务(ACS)资源,并启动了数据库、Web 服务器和 API 服务器。 还更新了 .env 文件中的以下值。
ACS_CONNECTION_STRING=<ACS_CONNECTION_STRING> ACS_PHONE_NUMBER=<ACS_PHONE_NUMBER> ACS_EMAIL_ADDRESS=<ACS_EMAIL_ADDRESS> CUSTOMER_EMAIL_ADDRESS=<EMAIL_ADDRESS_TO_SEND_EMAIL_TO> CUSTOMER_PHONE_NUMBER=<UNITED_STATES_BASED_NUMBER_TO_SEND_SMS_TO>
在继续之前,请确保已完成上一 练习 。
返回到浏览器(http://localhost:4200),找到 datagrid,然后选择“联系客户”,然后在第一行中选择“呼叫客户”。
电话呼叫组件将添加到标头中。 输入要呼叫的电话号码(确保其以 + 开头并包括国家/地区代码),然后选择“ 呼叫”。 系统将提示你允许访问麦克风。
选择 “挂起” 以结束呼叫。 选择“ 关闭 ”以关闭电话呼叫组件。
浏览电话呼叫代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
打开 customers-list.component.ts 文件。 文件的完整路径为 client/src/app/customers-list/customers-list.component.ts。
请注意,
openCallDialog()
使用事件总线发送消息CustomerCall
和客户电话号码。openCallDialog(data: Phone) { this.eventBus.emit({ name: Events.CustomerCall, value: data }); }
注意
如果有兴趣了解更多内容,可以在eventbus.service.ts文件中找到事件总线代码。 文件的完整路径为 client/src/app/core/eventbus.service.ts。
标头组件的
ngOnInit()
函数订阅CustomerCall
事件总线发送的事件,并显示电话呼叫组件。 可以在header.component.ts中找到此代码。ngOnInit() { this.subscription.add( this.eventBus.on(Events.CustomerCall, (data: Phone) => { this.callVisible = true; // Show phone call component this.callData = data; // Set phone number to call }) ); }
打开 phone-call.component.ts。 花点时间来阐述代码。 文件的完整路径是 client/src/app/phone-call/phone-call.component.ts。 请注意以下主要功能:
- 通过调用
acsService.getAcsToken()
函数来ngOnInit()
检索Azure 通信服务访问令牌; - 将“电话拨号程序”添加到页面。 可以通过单击标头中的电话号码输入来查看拨号程序。
- 分别使用
startCall()
和函数启动和endCall()
结束调用。
- 通过调用
在查看进行电话呼叫的代码之前,让我们看看如何检索 ACS 访问令牌以及如何创建电话呼叫对象。 在 phone-call.component.ts 中找到函数
ngOnInit()
。async ngOnInit() { if (ACS_CONNECTION_STRING) { this.subscription.add( this.acsService.getAcsToken().subscribe(async (user: AcsUser) => { const callClient = new CallClient(); const tokenCredential = new AzureCommunicationTokenCredential(user.token); this.callAgent = await callClient.createCallAgent(tokenCredential); }) ); } }
此函数执行以下操作:
- 通过调用
acsService.getAcsToken()
函数检索 ACS userId 和访问令牌。 - 检索访问令牌后,代码将执行以下操作:
- 创建和使用
AzureCommunicationTokenCredential
访问令牌的新实例CallClient
。 - 创建使用
CallClient
和AzureCommunicationTokenCredential
对象的新实例CallAgent
。 稍后你将看到用于CallAgent
启动和结束调用。
- 创建和使用
- 通过调用
打开 acs.services.ts 并找到函数
getAcsToken()
。 文件的完整路径为 client/src/app/core/acs.service.ts。 该函数向/acstoken
API 服务器公开的路由发出 HTTP GET 请求。getAcsToken(): Observable<AcsUser> { return this.http.get<AcsUser>(this.apiUrl + 'acstoken') .pipe( catchError(this.handleError) ); }
名为
createACSToken()
的 API 服务器函数检索 userId 和访问令牌,并将其返回到客户端。 可以在服务器/typescript/acs.ts文件中找到它。import { CommunicationIdentityClient } from '@azure/communication-identity'; const connectionString = process.env.ACS_CONNECTION_STRING as string; async function createACSToken() { if (!connectionString) return { userId: '', token: '' }; const tokenClient = new CommunicationIdentityClient(connectionString); const { user, token } = await tokenClient.createUserAndToken(["voip"]); return { userId: user.communicationUserId, token }; }
此函数执行以下操作:
- 检查 ACS
connectionString
值是否可用。 否则,返回一个包含空userId
和token
. 的对象。 - 创建一个新实例
CommunicationIdentityClient
,并将该值传递给connectionString
它。 - 使用
tokenClient.createUserAndToken()
“voip”范围创建新的用户和令牌。 - 返回一个包含
userId
和token
值的对象。
- 检查 ACS
现在你已了解了如何检索 userId 和令牌,请返回
phone-call.component.ts
并找到函数startCall()
。在电话呼叫组件中选择呼叫时调用此函数。 它使用
CallAgent
前面提到的对象来启动调用。 该callAgent.startCall()
函数接受一个对象,该对象表示要呼叫的号码和用于进行呼叫的 ACS 电话号码。startCall() { this.call = this.callAgent?.startCall( [{ phoneNumber: this.customerPhoneNumber }], { alternateCallerId: { phoneNumber: this.fromNumber } }); console.log('Calling: ', this.customerPhoneNumber); console.log('Call id: ', this.call?.id); this.inCall = true; // Adding event handlers to monitor call state this.call?.on('stateChanged', () => { console.log('Call state changed: ', this.call?.state); if (this.call?.state === 'Disconnected') { console.log('Call ended. Reason: ', this.call.callEndReason); this.inCall = false; } }); }
endCall()
在电话呼叫组件中选择“挂起”时,将调用该函数。endCall() { if (this.call) { this.call.hangUp({ forEveryone: true }); this.call = undefined; this.inCall = false; } else { this.hangup.emit(); } }
如果调用正在进行,则
call.hangUp()
调用函数以结束调用。 如果未进行呼叫,则会hangup
向标头父组件发出事件以隐藏电话呼叫组件。在继续下一个练习之前,让我们回顾本练习中介绍的关键概念:
- ACS userId 和访问令牌使用
acsService.createUserAndToken()
函数从 API 服务器检索。 - 令牌用于创建
CallClient
和CallAgent
对象。 - 对象
CallAgent
用于通过分别调用callAgent.startCall()
和函数来启动和callAgent.hangUp()
结束调用。
- ACS userId 和访问令牌使用
了解如何将电话呼叫集成到应用程序中后,让我们将焦点切换到使用Azure 通信服务发送电子邮件和短信。
通信:发送电子邮件和短信
除了电话呼叫,Azure 通信服务还可以发送电子邮件和短信。 如果要直接从应用程序向客户或其他用户发送消息,这非常有用。
通过学习本练习,你将能够:
- 了解如何从应用程序发送电子邮件和短信。
- 演练代码,了解如何实现电子邮件和短信功能。
使用电子邮件和短信功能
在上一练习中,你创建了一个Azure 通信服务(ACS)资源,并启动了数据库、Web 服务器和 API 服务器。 还更新了 .env 文件中的以下值。
ACS_CONNECTION_STRING=<ACS_CONNECTION_STRING> ACS_PHONE_NUMBER=<ACS_PHONE_NUMBER> ACS_EMAIL_ADDRESS=<ACS_EMAIL_ADDRESS> CUSTOMER_EMAIL_ADDRESS=<EMAIL_ADDRESS_TO_SEND_EMAIL_TO> CUSTOMER_PHONE_NUMBER=<UNITED_STATES_BASED_NUMBER_TO_SEND_SMS_TO>
在继续之前,请确保已完成 练习 。
返回到浏览器(http://localhost:4200)并选择“联系客户”,然后在第一行中选择“电子邮件/短信客户”。
选择“电子邮件/短信”选项卡并执行以下任务:
- 输入电子邮件 主题 和 正文 ,然后选择“ 发送电子邮件 ”按钮。
- 输入短信并选择“ 发送短信 ”按钮。
注意
美国和加拿大现在强制对免费号码进行短信验证。 若要启用短信,必须在购买电话号码后提交验证。 虽然本教程不会完成此过程,但可以从Azure 门户中的Azure 通信服务资源中选择电话和短信-->法规文档并添加所需的验证文档。
检查你是否收到了电子邮件和短信。 仅当你提交了上一条说明中提到的法规文档时,短信功能才起作用。 提醒一下,电子邮件将发送到定义的
CUSTOMER_EMAIL_ADDRESS
值,并将短信发送到 .env 文件中定义的CUSTOMER_PHONE_NUMBER
值。 如果无法提供基于美国的电话号码用于短信,则可以跳过此步骤。注意
如果在收件箱中看不到在 .env 文件中定义的
CUSTOMER_EMAIL_ADDRESS
地址的电子邮件,请检查垃圾邮件文件夹。
浏览电子邮件代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
打开 customers-list.component.ts 文件。 文件的完整路径为 client/src/app/customers-list/customers-list.component.ts。
在 datagrid 中选择了 “联系人客户 ”后跟 “电子邮件/SMS 客户 ”时,组件
customer-list
会显示一个对话框。 这由openEmailSmsDialog()
customer-list.component.ts文件中的函数处理。openEmailSmsDialog(data: any) { if (data.phone && data.email) { // Create the data for the dialog let dialogData: EmailSmsDialogData = { prompt: '', title: `Contact ${data.company}`, company: data.company, customerName: data.first_name + ' ' + data.last_name, customerEmailAddress: data.email, customerPhoneNumber: data.phone } // Open the dialog const dialogRef = this.dialog.open(EmailSmsDialogComponent, { data: dialogData }); // Subscribe to the dialog afterClosed observable to get the dialog result this.subscription.add( dialogRef.afterClosed().subscribe((response: EmailSmsDialogData) => { console.log('SMS dialog result:', response); if (response) { dialogData = response; } }) ); } else { alert('No phone number available.'); } }
该
openEmailSmsDialog()
函数执行以下任务:- 检查对象(表示 datagrid 中的行)是否
data
包含 aphone
和email
属性。 如果这样做,它将创建一个dialogData
对象,其中包含要传递给对话框的信息。 EmailSmsDialogComponent
打开对话框,并将dialogData
对象传递给该对话框。afterClosed()
订阅对话框的事件。 关闭对话框时会触发此事件。 该response
对象包含输入到对话框中的信息。
- 检查对象(表示 datagrid 中的行)是否
打开 email-sms-dialog.component.ts 文件。 文件的完整路径是 client/src/app/email-sms-dialog/email-sms-dialog.component.ts。
找到函数
sendEmail()
:sendEmail() { if (this.featureFlags.acsEmailEnabled) { // Using CUSTOMER_EMAIL_ADDRESS instead of this.data.email for testing purposes this.subscription.add( this.acsService.sendEmail(this.emailSubject, this.emailBody, this.getFirstName(this.data.customerName), CUSTOMER_EMAIL_ADDRESS /* this.data.email */) .subscribe(res => { console.log('Email sent:', res); if (res.status) { this.emailSent = true; } }) ); } else { this.emailSent = true; // Used when ACS email isn't enabled } }
该
sendEmail()
函数执行以下任务:- 检查功能标志是否
acsEmailEnabled
设置为true
。 此标志检查环境变量是否ACS_EMAIL_ADDRESS
具有分配的值。 - 如果
acsEmailEnabled
为 true,则调用函数acsService.sendEmail()
并传递电子邮件主题、正文、客户名称和客户电子邮件地址。 由于数据库包含示例数据,CUSTOMER_EMAIL_ADDRESS
因此使用环境变量而不是this.data.email
。 在实际应用程序中,this.data.email
将使用该值。 sendEmail()
订阅服务中的acsService
函数。 此函数返回一个 RxJS 可观测值,其中包含来自客户端服务的响应。- 如果电子邮件已成功发送,则属性
emailSent
设置为true
。
- 检查功能标志是否
为了提供更好的代码封装和重用,客户端服务(如 acs.service.ts )在整个应用程序中使用。 这允许将所有 ACS 功能合并到一个位置。
打开 acs.service.ts 并找到函数
sendEmail()
。 文件的完整路径为 client/src/app/core/acs.service.ts。sendEmail(subject: string, message: string, customerName: string, customerEmailAddress: string) : Observable<EmailSmsResponse> { return this.http.post<EmailSmsResponse>(this.apiUrl + 'sendEmail', { subject, message, customerName, customerEmailAddress }) .pipe( catchError(this.handleError) ); }
该
sendEmail()
函数AcsService
执行以下任务:- 调用函数
http.post()
并将电子邮件主题、消息、客户名称和客户电子邮件地址传递给该函数。 该http.post()
函数返回一个 RxJS 可观察值,其中包含来自 API 调用的响应。 - 使用 RxJS
catchError
运算符处理函数返回http.post()
的任何错误。
- 调用函数
现在,让我们来看看应用程序如何与 ACS 电子邮件功能交互。 打开 acs.ts 文件并找到函数
sendEmail()
。 文件的完整路径为 服务器/typescript/acs.ts。该
sendEmail()
函数执行以下任务:创建一个新
EmailClient
对象,并将 ACS 连接字符串传递给它(此值是从环境变量中检索的ACS_CONNECTION_STRING
)。const emailClient = new EmailClient(connectionString);
创建一个新
EmailMessage
对象并传递发件人、主题、邮件和收件人信息。const msgObject: EmailMessage = { senderAddress: process.env.ACS_EMAIL_ADDRESS as string, content: { subject: subject, plainText: message, }, recipients: { to: [ { address: customerEmailAddress, displayName: customerName, }, ], }, };
使用
emailClient.beginSend()
函数发送电子邮件并返回响应。 尽管函数仅发送到此示例中的一个收件人,但beginSend()
该函数也可用于发送到多个收件人。const poller = await emailClient.beginSend(msgObject);
poller
等待对象发出信号,并将响应发送到调用方。
浏览短信代码
返回到 之前打开的 email-sms-dialog.component.ts文件。 文件的完整路径是 client/src/app/email-sms-dialog/email-sms-dialog.component.ts。
找到函数
sendSms()
:sendSms() { if (this.featureFlags.acsPhoneEnabled) { // Using CUSTOMER_PHONE_NUMBER instead of this.data.customerPhoneNumber for testing purposes this.subscription.add( this.acsService.sendSms(this.smsMessage, CUSTOMER_PHONE_NUMBER /* this.data.customerPhoneNumber */) .subscribe(res => { if (res.status) { this.smsSent = true; } }) ); } else { this.smsSent = true; } }
该
sendSMS()
函数执行以下任务:- 检查功能标志是否
acsPhoneEnabled
设置为true
。 此标志检查环境变量是否ACS_PHONE_NUMBER
具有分配的值。 - 如果
acsPhoneEnabled
为 true,则acsService.SendSms()
调用函数并传递短信和客户电话号码。 由于数据库包含示例数据,CUSTOMER_PHONE_NUMBER
因此使用环境变量而不是this.data.customerPhoneNumber
。 在实际应用程序中,this.data.customerPhoneNumber
将使用该值。 sendSms()
订阅服务中的acsService
函数。 此函数返回一个 RxJS 可观测值,其中包含来自客户端服务的响应。- 如果短信已成功发送,则会将
smsSent
属性设置为true
。
- 检查功能标志是否
打开 acs.service.ts 并找到函数
sendSms()
。 文件的完整路径为 client/src/app/core/acs.service.ts。sendSms(message: string, customerPhoneNumber: string) : Observable<EmailSmsResponse> { return this.http.post<EmailSmsResponse>(this.apiUrl + 'sendSms', { message, customerPhoneNumber }) .pipe( catchError(this.handleError) ); }
该
sendSms()
函数执行以下任务:- 调用函数
http.post()
并将消息和客户电话号码传递给该函数。 该http.post()
函数返回一个 RxJS 可观察值,其中包含来自 API 调用的响应。 - 使用 RxJS
catchError
运算符处理函数返回http.post()
的任何错误。
- 调用函数
最后,让我们来看看应用程序如何与 ACS 短信功能交互。 打开 acs.ts 文件。 文件的完整路径是 服务器/typescript/acs.ts 并找到函数
sendSms()
。该
sendSms()
函数执行以下任务:创建一个新
SmsClient
对象,并将 ACS 连接字符串传递给它(此值是从环境变量中检索的ACS_CONNECTION_STRING
)。const smsClient = new SmsClient(connectionString);
smsClient.send()
呼叫函数并传递 ACS 电话号码(from
)、客户电话号码(to
)和短信:const sendResults = await smsClient.send({ from: process.env.ACS_PHONE_NUMBER as string, to: [customerPhoneNumber], message: message }); return sendResults;
返回调用方响应。
可以在以下文章中了解有关 ACS 电子邮件和短信功能的详细信息:
在继续下一个练习之前,让我们回顾本练习中介绍的关键概念:
- acs.service.ts文件封装客户端应用程序使用的 ACS 电子邮件和短信功能。 它处理对服务器的 API 调用,并向调用方返回响应。
- 服务器端 API 使用 ACS
EmailClient
和SmsClient
对象发送电子邮件和短信。
了解电子邮件和短信的发送方式后,让我们将重点切换为将组织数据集成到应用程序中。
组织数据:创建Microsoft Entra ID 应用注册
通过将组织数据(电子邮件、文件、聊天和日历事件)直接集成到自定义应用程序中来提高用户工作效率。 通过使用 Microsoft Graph API 和 Microsoft Entra ID,可以在应用中无缝检索和显示相关数据,从而减少用户切换上下文的需求。 无论是引用发送给客户的电子邮件、查看 Teams 消息还是访问文件,用户都可以快速找到所需的信息,而无需离开你的应用,简化他们的决策过程。
通过学习本练习,你将能够:
- 创建Microsoft Entra ID 应用注册,以便 Microsoft Graph 可以访问组织数据并将其引入应用。
- 从Microsoft Teams 中查找
team
和channel
ID,这些团队需要将聊天消息发送到特定频道。 - 使用Microsoft Entra ID 应用注册中的值更新项目的 .env 文件。
创建Microsoft Entra ID 应用注册
选择“管理”-->应用注册后跟“+ 新建注册”。
填写新的应用注册表单详细信息,如下所示,然后选择“ 注册” :
- 名称: microsoft-graph-app
- 支持的帐户类型: 任何组织目录中的帐户(任何Microsoft Entra ID 租户 - 多租户)
- 重定向 URI:
- 选择单页应用程序(SPA),然后在“重定向 URI”字段中输入
http://localhost:4200
。
- 选择单页应用程序(SPA),然后在“重定向 URI”字段中输入
- 选择“注册”以创建应用注册。
在资源菜单中选择“ 概述 ”,并将
Application (client) ID
该值复制到剪贴板。
更新项目的 .env 文件
在 编辑器中打开 .env 文件,并将
Application (client) ID
值赋给ENTRAID_CLIENT_ID
。ENTRAID_CLIENT_ID=<APPLICATION_CLIENT_ID_VALUE>
如果想要启用将消息从应用发送到 Teams 频道的功能,请使用Microsoft 365 开发租户帐户登录到 Microsoft Teams (本教程的预请求中提到了这一点)。
登录后,展开团队,找到想要从应用向其发送消息的频道。 例如,可以选择 公司 团队和 常规 频道(或任何要使用的团队/频道)。
在团队标题中,单击三个点(...),然后选择“ 获取团队链接”。
在弹出窗口中显示的链接中,团队 ID 是后面的
team/
字母和数字字符串。 例如,在链接“https://teams.microsoft.com/l/team/19%3ae9b9.../"中,团队 ID 为 19%3ae9b9... 最多为以下/
字符。复制团队 ID 并将其分配给
TEAM_ID
.env 文件中。在通道标头中,单击三个点(...),然后选择“ 获取通道链接”。
在弹出窗口中显示的链接中,通道 ID 是后面的
channel/
字母和数字字符串。 例如,在链接“https://teams.microsoft.com/l/channel/19%3aQK02.../"中,通道 ID 为 19%3aQK02... 最多为以下/
字符。复制通道 ID 并将其分配给
CHANNEL_ID
.env 文件中。保存 env 文件,然后再继续。
启动/重启应用程序和 API 服务器
根据你完成的练习执行 以下步骤之一 ,至此时间点:
如果在前面的练习中启动了数据库、API 服务器和 Web 服务器,则需要停止 API 服务器和 Web 服务器并重新启动它们以选取 .env 文件更改。 可以让数据库保持运行状态。
找到运行 API 服务器和 Web 服务器的终端窗口,然后按 Ctrl + C 停止它们。 通过在每个终端窗口中键入
npm start
并按 Enter 再次启动它们。 继续下一练习。如果尚未启动数据库、API 服务器和 Web 服务器,请完成以下步骤:
在以下步骤中,将在 Visual Studio Code 中创建三个终端窗口。
右键单击 Visual Studio Code 文件列表中的 .env 文件,然后选择“ 在集成终端中打开”。 在继续操作之前,请确保终端位于项目的根目录中( openai-acs-msgraph )。
从 以下选项中选择 以启动 PostgreSQL 数据库:
如果已安装 并运行 Docker Desktop ,请在终端窗口中运行
docker-compose up
,然后按 Enter。如果 Podman 已安装 并正在运行 podman-compose ,请在终端窗口中运行
podman-compose up
,然后按 Enter。若要使用 Docker Desktop、Podman、nerdctl 或其他已安装的容器运行时直接运行 PostgreSQL 容器,请在终端窗口中运行以下命令:
Mac、Linux 或 适用于 Linux 的 Windows 子系统 (WSL):
[docker | podman | nerdctl] run --name postgresDb -e POSTGRES_USER=web -e POSTGRES_PASSWORD=web-password -e POSTGRES_DB=CustomersDB -v $(pwd)/data:/var/lib/postgresql/data -p 5432:5432 postgres
使用 PowerShell 的 Windows:
[docker | podman] run --name postgresDb -e POSTGRES_USER=web -e POSTGRES_PASSWORD=web-password -e POSTGRES_DB=CustomersDB -v ${PWD}/data:/var/lib/postgresql/data -p 5432:5432 postgres
数据库容器启动后,按 + Visual Studio Code 终端工具栏 中的图标创建第二个终端窗口。
cd
进入服务器/typescript 文件夹,并运行以下命令以安装依赖项并启动 API 服务器。npm install
npm start
+再次在 Visual Studio Code 终端工具栏中按图标以创建第三个终端窗口。
cd
进入客户端文件夹并运行以下命令以安装依赖项并启动 Web 服务器。npm install
npm start
浏览器将启动,你将转到该浏览器 http://localhost:4200。
组织数据:登录用户并获取访问令牌
用户需要使用 Microsoft Entra ID 进行身份验证,以便Microsoft Graph 访问组织数据。 在本练习中,你将了解如何使用 Microsoft Graph 工具包的 mgt-login
组件对用户进行身份验证和检索访问令牌。 然后,可以使用访问令牌调用 Microsoft Graph。
注意
如果你不熟悉 Microsoft Graph,可以在 Microsoft Graph 基础知识学习路径中了解有关它的详细信息。
通过学习本练习,你将能够:
- 了解如何将 Microsoft Entra ID 应用与 Microsoft Graph 工具包相关联,以对用户进行身份验证和检索组织数据。
- 了解范围的重要性。
- 了解 Microsoft Graph 工具包的 mgt 登录 组件如何用于对用户进行身份验证和检索访问令牌。
使用登录功能
在上一 练习中,你在 Microsoft Entra ID 中创建应用注册,并启动应用程序服务器和 API 服务器。 此外,还更新了
.env
文件中的以下值(TEAM_ID
并且CHANNEL_ID
是可选的):ENTRAID_CLIENT_ID=<APPLICATION_CLIENT_ID_VALUE> TEAM_ID=<TEAMS_TEAM_ID> CHANNEL_ID=<TEAMS_CHANNEL_ID>
在继续之前,请确保已完成上一 练习 。
返回到浏览器(http://localhost:4200),选择 “登录 ”标头,然后使用 Microsoft 365 开发人员租户中的管理员用户帐户登录。
提示
使用 Microsoft 365 开发人员租户管理员帐户登录。 可以通过转到Microsoft 365 管理中心来查看开发人员租户中的其他用户。
如果首次登录到应用程序,系统会提示你同意应用程序请求的权限。 在浏览代码时,你将在下一部分中了解有关这些权限(也称为“范围”)的详细信息。 选择“接受”以继续操作。
登录后,应会看到标头中显示的用户名称。
浏览登录代码
登录后,让我们看看用于登录用户并检索访问令牌和用户配置文件的代码。 你将了解 属于 Microsoft Graph 工具包的一部分的 mgt-login Web 组件。
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
打开 客户端/package.json ,并注意到
@microsoft/mgt
依赖项中包含和@microsoft/mgt-components
包。 该@microsoft/mgt
包包含 MSAL(Microsoft身份验证库)提供程序功能和 Web 组件,例如 mgt-login 和其他组件,可用于登录用户并检索和显示组织数据。打开 客户端/src/main.ts ,注意以下从
@microsoft/mgt-components
包导入。 导入的符号用于注册应用程序中使用的 Microsoft Graph 工具包组件。import { registerMgtLoginComponent, registerMgtSearchResultsComponent, registerMgtPersonComponent, } from '@microsoft/mgt-components';
滚动到文件底部并记下以下代码:
registerMgtLoginComponent(); registerMgtSearchResultsComponent(); registerMgtPersonComponent();
此代码注册
mgt-login
和mgt-search-results
Web 组件,mgt-person
并启用它们以便在应用程序中使用。若要使用 mgt 登录组件登录用户,需要引用和使用Microsoft Entra ID 应用的客户端 ID(如 .env 文件中存储)。
ENTRAID_CLIENT_ID
打开 graph.service.ts 并找到函数
init()
。 文件的完整路径为 client/src/app/core/graph.service.ts。 你将看到以下导入和代码:import { Msal2Provider, Providers, ProviderState } from '@microsoft/mgt'; init() { if (!this.featureFlags.microsoft365Enabled) return; if (!Providers.globalProvider) { console.log('Initializing Microsoft Graph global provider...'); Providers.globalProvider = new Msal2Provider({ clientId: ENTRAID_CLIENT_ID, scopes: ['User.Read', 'Presence.Read', 'Chat.ReadWrite', 'Calendars.Read', 'ChannelMessage.Read.All', 'ChannelMessage.Send', 'Files.Read.All', 'Mail.Read'] }); } else { console.log('Global provider already initialized'); } }
此代码创建一个新
Msal2Provider
对象,从应用注册中传递 Microsoft Entra ID 客户端 ID,以及scopes
应用将请求访问的对象。 用于scopes
请求访问应用将访问的 Microsoft Graph 资源。Msal2Provider
创建对象后,会将其Providers.globalProvider
分配给对象,该对象由 Microsoft Graph 工具包组件用来从 Microsoft Graph 检索数据。在编辑器中打开 header.component.html ,找到 mgt-login 组件。 文件的完整路径 client/src/app/header/header.component.html。
@if (this.featureFlags.microsoft365Enabled) { <mgt-login class="mgt-dark" (loginCompleted)="loginCompleted()"></mgt-login> }
mgt-login 组件允许用户登录,并提供对与 Microsoft Graph 一起使用的令牌的访问权限。 成功登录后,
loginCompleted
将触发该事件,该事件将调用该loginCompleted()
函数。 尽管 mgt-login 在本示例中的 Angular 组件中使用,但它与任何 Web 应用程序兼容。mgt-login 组件的显示取决于
featureFlags.microsoft365Enabled
要设置为true
的值。 此自定义标志检查ENTRAID_CLIENT_ID
是否存在环境变量,以确认应用程序是否已正确配置,并且能够针对 Microsoft Entra ID 进行身份验证。 添加标志以适应用户选择仅完成教程中的 AI 或通信练习的情况,而不是遵循整个练习序列。打开 header.component.ts 并找到函数
loginCompleted
。 发出事件并处理使用Providers.globalProvider
检索已登录用户配置文件时loginCompleted
调用此函数。async loginCompleted() { const me = await Providers.globalProvider.graph.client.api('me').get(); this.userLoggedIn.emit(me); }
在此示例中,正在调用 Microsoft 图形
me
API 以检索用户的配置文件(me
表示当前登录的用户)。 该this.userLoggedIn.emit(me)
代码语句从组件发出事件,以将配置文件数据传递给父组件。 在这种情况下,父组件 app.component.ts 文件,这是应用程序的根组件。若要详细了解 mgt-login 组件,请访问 Microsoft Graph 工具包 文档。
登录到应用程序后,让我们看看如何检索组织数据。
组织数据:检索文件、聊天和向 Teams 发送邮件
在当今的数字环境中,用户使用各种组织数据,包括电子邮件、聊天、文件、日历事件等。 这可能会导致频繁的上下文转变(在任务或应用程序之间切换),这可能会中断焦点并减少工作效率。 例如,处理项目的用户可能需要从当前应用程序切换到 Outlook,以在电子邮件中查找关键详细信息,或切换到 OneDrive for Business 以查找相关文件。 这种来回操作会干扰焦点和浪费时间,这些时间可能更好地花在手头的任务上。
为了提高效率,可以将组织数据直接集成到用户日常使用的应用程序。 通过将组织数据引入应用程序,用户可以更无缝地访问和管理信息,最大程度地减少上下文转移并提高工作效率。 此外,此集成还提供有价值的见解和上下文,使用户能够做出明智的决策并更有效地工作。
通过学习本练习,你将能够:
- 了解 Microsoft Graph 工具包中的 mgt-search-results Web 组件如何用于搜索文件。
- 了解如何直接调用 Microsoft Graph,从 OneDrive for Business 检索文件,以及如何从 Microsoft Teams 聊天消息中检索文件。
- 了解如何使用 Microsoft Graph 将聊天消息发送到 Microsoft Teams 频道。
使用组织数据功能
在上一练习中,你在 Microsoft Entra ID 中创建应用注册,并启动应用程序服务器和 API 服务器。 还更新了
.env
文件中的以下值。ENTRAID_CLIENT_ID=<APPLICATION_CLIENT_ID_VALUE> TEAM_ID=<TEAMS_TEAM_ID> CHANNEL_ID=<TEAMS_CHANNEL_ID>
在继续之前,请确保已完成上一 练习 。
返回到浏览器(http://localhost:4200)。 如果尚未登录,请选择 标头中的“登录 ”,然后使用Microsoft 365 开发人员租户中的用户登录。
注意
除了对用户进行身份验证外, mgt-login Web 组件还检索访问令牌,Microsoft Graph 可用于访问文件、聊天、电子邮件、日历事件和其他组织数据。 访问令牌包含范围(权限),例如
Chat.ReadWrite
,Files.Read.All
以及之前看到的其他人:Providers.globalProvider = new Msal2Provider({ clientId: ENTRAID_CLIENT_ID, // retrieved from .env file scopes: ['User.Read', 'Presence.Read', 'Chat.ReadWrite', 'Calendars.Read', 'ChannelMessage.Read.All', 'ChannelMessage.Send', 'Files.Read.All', 'Mail.Read'] });
在 datagrid 中选择 Adatum Corporation 行的“查看相关内容”。 这将导致使用 Microsoft Graph 检索组织数据,例如文件、聊天、电子邮件和日历事件。 加载数据后,数据将显示在选项卡式接口的 datagrid 下方。 请务必注意,此时可能不会看到任何数据,因为尚未为 Microsoft 365 开发人员租户中的用户添加任何文件、聊天、电子邮件或日历事件。 让我们在下一步中解决此问题。
Microsoft 365 租户在此阶段可能没有任何相关的 Adatum Corporation 组织数据。 若要添加一些示例数据,请至少执行以下操作之一:
使用 Microsoft 365 开发人员租户凭据访问 https://onedrive.com 和登录来添加文件。
- 在 左侧导航中选择“我的文件 ”。
- 从菜单中选择“ + 添加新 ”,然后选择 “文件夹上传 ”。
- 从克隆的项目中选择 openai-acs-msgraph/customer documents 文件夹。
通过使用 Microsoft 365 开发人员租户凭据访问 https://teams.microsoft.com 和登录来添加聊天消息和日历事件。
在 左侧导航中选择 Teams 。
选择团队和频道。
选择“ 开始发布”。
为 主题输入 Adatum Corporation 的新订单以及要添加到邮件正文的任何其他文本。 选择“发布”按钮。
可以随意添加其他聊天消息,这些消息提及应用程序中使用的其他公司,例如 Adventure Works Cycles、 Contoso Pharmaceuticals 和 Tailwind Traders。
在左侧导航中选择 “日历 ”。
选择“ 新建会议”。
输入标题和正文的“与 Adatum Corporation 会面”,了解项目日程。
选择“保存”。
使用 Microsoft 365 开发人员租户凭据访问 https://outlook.com 和登录来添加电子邮件。
选择“ 新建邮件”。
在“ To” 字段中输入个人电子邮件地址。
为 主题输入为 Adatum Corporation 下达的新订单,并输入想要正文的任何内容。
选择Send。
返回到浏览器中的应用程序并刷新页面。 再次为 Adatum Corporation 行选择“查看相关内容”。 现在应会看到选项卡中显示的数据,具体取决于在上一步中执行的任务。
让我们浏览一下在应用程序中启用组织数据功能的代码。 为了检索数据,应用程序的客户端部分使用前面查看的 mgt 登录组件检索的访问令牌来调用 Microsoft Graph API。
浏览文件搜索代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
首先,让我们看看如何从 OneDrive for Business 检索文件数据。 打开 files.component.html 并花点时间浏览代码。 文件的完整路径 client/src/app/files/files.component.html。
找到 mgt-search-results 组件并记下以下属性:
<mgt-search-results class="search-results" entity-types="driveItem" [queryString]="searchText" (dataChange)="dataChange($any($event))" />
mgt-search-results 组件是 Microsoft Graph 工具包的一部分,顾名思义,它用于显示来自 Microsoft Graph 的搜索结果。 该组件在本示例中使用以下功能:
该
class
特性用于指定search-results
应将 CSS 类应用于组件。该
entity-types
属性用于指定要搜索的数据类型。 在这种情况下,该值driveItem
用于搜索 OneDrive for Business 中的文件。该
queryString
属性用于指定搜索词。 在这种情况下,当用户为 datagrid 中的行选择视图相关内容时,该值将searchText
绑定到传递给文件组件的属性。 周围的queryString
方括号指示属性绑定到searchText
值。当搜索结果发生更改时,将
dataChange
触发该事件。 在这种情况下,在文件组件中调用名为dataChange()
客户函数,并将事件数据传递给该函数。 周围的dataChange
括号指示事件绑定到dataChange()
函数。由于未提供自定义模板,因此内置
mgt-search-results
的默认模板用于显示搜索结果。
使用 mgt-search-results 等组件的替代方法是直接使用代码调用 Microsoft Graph API。 若要查看工作原理,请打开 graph.service.ts 文件并找到函数
searchFiles()
。 文件的完整路径为 client/src/app/core/graph.service.ts。你会注意到
query
参数传递给函数。 这是在用户为 datagrid 中的行选择 “查看相关内容 ”时传递的搜索词。 如果未传递搜索词,则返回空数组。async searchFiles(query: string) { const files: DriveItem[] = []; if (!query) return files; ... }
然后创建一个筛选器,用于定义要执行的搜索类型。 在本例中,代码正在搜索 OneDrive for Business 中的文件,因此
driveItem
就像之前在组件中看到的那样mgt-search-results
使用。 这与传入driveItem
entity-types
之前看到的 mgt-search-results 组件相同。 然后,将query
参数添加到queryString
筛选器中,ContentType:Document
const filter = { "requests": [ { "entityTypes": [ "driveItem" ], "query": { "queryString": `${query} AND ContentType:Document` } } ] };
然后使用函数
Providers.globalProvider.graph.client.api()
调用/search/query
Microsoft图形 API。 该filter
对象将post()
传递给将数据发送到 API 的函数。const searchResults = await Providers.globalProvider.graph.client.api('/search/query').post(filter);
然后循环访问搜索结果以查找
hits
。 每个hit
文档都包含有关找到的文档的信息。 名为resource
的属性包含文档元数据,并将其添加到files
数组中。if (searchResults.value.length !== 0) { for (const hitContainer of searchResults.value[0].hitsContainers) { if (hitContainer.hits) { for (const hit of hitContainer.hits) { files.push(hit.resource); } } } }
然后,数组
files
将返回到调用方。return files;
查看此代码,可以看到之前浏览的 mgt-search-results Web 组件为你做了大量工作,并显著减少你编写的代码量! 但是,在某些情况下,你可能希望直接调用 Microsoft Graph,以更好地控制发送到 API 的数据或结果的处理方式。
打开 files.component.ts 文件并找到函数
search()
。 文件的完整路径为 client/src/app/files/files.component.ts。尽管由于 正在使用 mgt-search-results 组件而注释掉此函数的正文,但当用户为 datagrid 中的某行选择 “查看相关内容 ”时,该函数可用于调用 Microsoft Graph。 函数
search()
在graph.service.ts中调用searchFiles()
,并将参数传递给query
它(此示例中的公司名称)。 然后,搜索结果将data
分配给组件的属性。override async search(query: string) { this.data = await this.graphService.searchFiles(query); }
然后
data
,文件组件可以使用该属性来显示搜索结果。 可以使用自定义 HTML 绑定或使用名为mgt-file-list
的另一个 Microsoft Graph 工具包控件来处理此问题。 下面是在用户与文件交互时将itemClick
属性绑定到data
其中一个组件files
属性并处理事件的示例。<mgt-file-list (itemClick)="itemClick($any($event))" [files]="data"></mgt-file-list>
无论是选择使用 前面显示的 mgt-search-results 组件还是编写自定义代码来调用 Microsoft Graph 都将取决于你的特定方案。 在此示例中, mgt-search-results 组件用于简化代码并减少必须执行的操作量。
浏览 Teams 聊天消息搜索代码
返回到 graph.service.ts 并找到函数
searchChatMessages()
。 你将看到它类似于之前查看的searchFiles()
函数。- 它将筛选数据以Microsoft Graph 的
/search/query
API,并将结果转换为包含相关信息teamId
channelId
的对象数组,并且messageId
与搜索词匹配。 - 若要检索 Teams 频道消息,对 API 和
teamId
channelId
API 进行/teams/${chat.teamId}/channels/${chat.channelId}/messages/${chat.messageId}
第二次调用,并messageId
传递。 这会返回完整消息详细信息。 - 将执行其他筛选任务,并将生成的消息从
searchChatMessages()
调用方返回。
- 它将筛选数据以Microsoft Graph 的
打开chats.component.ts文件并找到函数
search()
。 文件的完整路径是 client/src/app/chats/chats.component.ts。 函数search()
在graph.service.ts中调用searchChatMessages()
,并将参数传递给query
它。override async search(query: string) { this.data = await this.graphService.searchChatMessages(query); }
搜索结果将
data
分配给组件的属性和数据绑定,用于循环访问结果数组并呈现数据。 此示例使用 Angular 材料卡片 组件来显示搜索结果。@if (this.data.length) { <div> @for (chatMessage of this.data;track chatMessage.id) { <mat-card> <mat-card-header> <mat-card-title [innerHTML]="chatMessage.summary"></mat-card-title> <!-- <mat-card-subtitle [innerHTML]="chatMessage.body"></mat-card-subtitle> --> </mat-card-header> <mat-card-actions> <a mat-stroked-button color="basic" [href]="chatMessage.webUrl" target="_blank">View Message</a> </mat-card-actions> </mat-card> } </div> }
向 Microsoft Teams 频道发送消息
除了搜索 Microsoft Teams 聊天消息外,应用程序还允许用户将消息发送到 Microsoft Teams 频道。 这可以通过调用
/teams/${teamId}/channels/${channelId}/messages
Microsoft Graph 的终结点来完成。在以下代码中,你将看到已创建包含
teamId
和channelId
值的 URL。 环境变量值用于此示例中的团队 ID 和通道 ID,但可以使用 Microsoft Graph 动态检索这些值。 常body
量包含要发送的消息。 然后发出 POST 请求,结果将返回到调用方。async sendTeamsChat(message: string): Promise<TeamsDialogData> { if (!message) new Error('No message to send.'); if (!TEAM_ID || !CHANNEL_ID) new Error('Team ID or Channel ID not set in environment variables. Please set TEAM_ID and CHANNEL_ID in the .env file.'); const url = `https://graph.microsoft.com/v1.0/teams/${TEAM_ID}/channels/${CHANNEL_ID}/messages`; const body = { "body": { "contentType": "html", "content": message } }; const response = await Providers.globalProvider.graph.client.api(url).post(body); return { id: response.id, teamId: response.channelIdentity.teamId, channelId: response.channelIdentity.channelId, message: response.body.content, webUrl: response.webUrl, title: 'Send Teams Chat' }; }
利用 Microsoft Graph 中的此类功能,使用户能够直接从他们正在使用的应用程序与 Microsoft Teams 进行交互,从而增强用户产品性能。
组织数据:检索电子邮件和日历事件
在上一个练习中,你学习了如何使用 Microsoft Graph 和来自 Microsoft Graph 工具包的 mgt-search-results 组件从 OneDrive for Business 检索文件以及来自 Microsoft Teams 的聊天。 你还了解了如何将消息发送到 Microsoft Teams 频道。 在本练习中,你将了解如何从 Microsoft Graph 检索电子邮件和日历事件并将其集成到应用程序中。
通过学习本练习,你将能够:
- 了解 Microsoft Graph 工具包中的 mgt-search-results Web 组件如何用于搜索电子邮件和日历事件。
- 了解如何自定义 mgt-search-results 组件以自定义方式呈现搜索结果。
- 了解如何直接调用 Microsoft Graph 以检索电子邮件和日历事件。
浏览电子邮件搜索代码
提示
如果使用的是 Visual Studio Code,可以通过选择以下方法直接打开文件:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然后键入要打开的文件的名称。
在上一练习中,你在 Microsoft Entra ID 中创建应用注册,并启动应用程序服务器和 API 服务器。 还更新了
.env
文件中的以下值。ENTRAID_CLIENT_ID=<APPLICATION_CLIENT_ID_VALUE> TEAM_ID=<TEAMS_TEAM_ID> CHANNEL_ID=<TEAMS_CHANNEL_ID>
在继续之前,请确保已完成上一 练习 。
打开 emails.component.html ,花点时间浏览代码。 文件的完整路径 client/src/app/emails/emails.component.html。
找到 mgt-search-results 组件:
<mgt-search-results class="search-results" entity-types="message" [queryString]="searchText" (dataChange)="dataChange($any($event))"> <template data-type="result-message"></template> </mgt-search-results>
此 mgt-search-results 组件示例的配置方式与之前查看的方式相同。 唯一的区别是,属性
entity-types
设置为message
用于搜索电子邮件和提供空模板的属性。- 该
class
特性用于指定search-results
应将 CSS 类应用于组件。 - 该
entity-types
属性用于指定要搜索的数据类型。 在本例中,该值为message
。 - 该
queryString
属性用于指定搜索词。 - 当搜索结果发生更改时,将
dataChange
触发该事件。 将调用电子邮件组件函数dataChange()
、将结果传递给它,并在组件中更新名为data
的属性。 - 为组件定义空模板。 这种类型的模板通常用于定义搜索结果的呈现方式。 但是,在此方案中,我们告知组件不要呈现任何消息数据。 相反,我们将使用标准数据绑定自行呈现数据(在本例中使用 Angular,但你可以使用所需的任何库/框架)。
- 该
在emails.component.html中查看 mgt-search-results 组件下方,查找用于呈现电子邮件的数据绑定。 此示例循环访问
data
属性并写出电子邮件主题、正文预览和查看完整电子邮件的链接。@if (this.data.length) { <div> @for (email of this.data;track $index) { <mat-card> <mat-card-header> <mat-card-title>{{email.resource.subject}}</mat-card-title> <mat-card-subtitle [innerHTML]="email.resource.bodyPreview"></mat-card-subtitle> </mat-card-header> <mat-card-actions> <a mat-stroked-button color="basic" [href]="email.resource.webLink" target="_blank">View Email Message</a> </mat-card-actions> </mat-card> } </div> }
除了使用 mgt-search-search-results 组件来检索邮件之外,Microsoft Graph 还提供多个可用于搜索电子邮件的 API。
/search/query
你之前看到的 API 当然可以使用,但更直接的选择是messages
API。若要查看如何调用此 API,请返回到 graph.service.ts 并找到函数
searchEmailMessages()
。 它创建一个 URL,该 URL 可用于调用messages
Microsoft Graph 的终结点,并将值$search
分配给query
参数。 然后,该代码发出 GET 请求,并将结果返回到调用方。 运算符$search
会自动搜索query
主题、正文和发送方字段中的值。async searchEmailMessages(query:string) { if (!query) return []; // The $search operator will search the subject, body, and sender fields automatically const url = `https://graph.microsoft.com/v1.0/me/messages?$search="${query}"&$select=subject,bodyPreview,from,toRecipients,receivedDateTime,webLink`; const response = await Providers.globalProvider.graph.client.api(url).get(); return response.value; }
位于emails.component.ts的电子邮件组件调用
searchEmailMessages()
并在 UI 中显示结果。override async search(query: string) { this.data = await this.graphService.searchEmailMessages(query); }
浏览日历事件搜索代码
还可以使用 mgt-search-results 组件搜索日历事件。 它可以为你处理呈现结果,但你也可以定义你自己的模板,稍后在本练习中将看到该模板。
打开 calendar-events.component.html 并花点时间浏览代码。 文件的完整路径 client/src/app/calendar-events/calendar-events.component.html。 你将看到它与之前查看的文件和电子邮件组件非常相似。
<mgt-search-results class="search-results" entity-types="event" [queryString]="searchText" (dataChange)="dataChange($any($event))"> <template data-type="result-event"></template> </mgt-search-results>
此 mgt-search-results 组件示例的配置方式与之前查看的方式相同。 唯一的区别是,
entity-types
属性设置为event
用于搜索日历事件和提供空模板的属性。- 该
class
特性用于指定search-results
应将 CSS 类应用于组件。 - 该
entity-types
属性用于指定要搜索的数据类型。 在本例中,该值为event
。 - 该
queryString
属性用于指定搜索词。 - 当搜索结果发生更改时,将
dataChange
触发该事件。 调用日历 事件 组件的dataChange()
函数、将结果传递给它,并在组件中更新名为data
的属性。 - 为组件定义空模板。 在此方案中,我们将告知组件不要呈现任何数据。 相反,我们将使用标准数据绑定自行呈现数据。
- 该
紧邻calendar-events.component.html组件下方
mgt-search-results
,你将找到用于呈现日历事件的数据绑定。 此示例循环访问data
属性并写出事件的开始日期、时间和主题。 组件中包含的自定义函数,例如dayFromDateTime()
,调用timeRangeFromEvent()
这些函数来正确设置数据的格式。 HTML 绑定还包括一个链接,用于在 Outlook 中查看日历事件,以及指定事件的位置。@if (this.data.length) { <div> @for (event of this.data;track $index) { <div class="root"> <div class="time-container"> <div class="date">{{ dayFromDateTime(event.resource.start.dateTime)}}</div> <div class="time">{{ timeRangeFromEvent(event.resource) }}</div> </div> <div class="separator"> <div class="vertical-line top"></div> <div class="circle"> @if (!this.event.resource.bodyPreview?.includes('Join Microsoft Teams Meeting')) { <div class="inner-circle"></div> } </div> <div class="vertical-line bottom"></div> </div> <div class="details"> <div class="subject">{{ event.resource.subject }}</div> @if (this.event.resource.location?.displayName) { <div class="location"> at <a href="https://bing.com/maps/default.aspx?where1={{event.resource.location.displayName}}" target="_blank" rel="noopener"><b>{{ event.resource.location.displayName }}</b></a> </div> } @if (this.event.resource.attendees?.length) { <div class="attendees"> @for (attendee of this.event.resource.attendees;track attendee.emailAddress.name) { <span class="attendee"> <mgt-person person-query="{{attendee.emailAddress.name}}"></mgt-person> </span> } </div> } @if (this.event.resource.bodyPreview?.includes('Join Microsoft Teams Meeting')) { <div class="online-meeting"> <img class="online-meeting-icon" src="https://img.icons8.com/color/48/000000/microsoft-teams.png" title="Online Meeting" /> <a class="online-meeting-link" href="{{ event.resource.onlineMeetingUrl }}"> Join Teams Meeting </a> </div> } </div> </div> } </div> }
除了使用
search/query
API 搜索日历事件之外,Microsoft Graph 还提供events
可用于搜索日历事件的 API。 在 graph.service.ts 中找到函数searchCalendarEvents()
。该
searchCalendarEvents()
函数创建开始和结束日期/时间值,用于定义要搜索的时间段。 然后,它会创建一个 URL,该 URL 可用于调用events
Microsoft Graph 的终结点,并包括query
参数和开始和结束日期/时间变量。 然后发出 GET 请求,结果将返回到调用方。async searchCalendarEvents(query:string) { if (!query) return []; const startDateTime = new Date(); const endDateTime = new Date(startDateTime.getTime() + (7 * 24 * 60 * 60 * 1000)); const url = `/me/events?startdatetime=${startDateTime.toISOString()}&enddatetime=${endDateTime.toISOString()}&$filter=contains(subject,'${query}')&orderby=start/dateTime`; const response = await Providers.globalProvider.graph.client.api(url).get(); return response.value; }
下面是创建的 URL 的细分:
- URL
/me/events
部分用于指定应检索已登录用户的事件。 - 参数
startdatetime
enddatetime
用于定义要搜索的时间段。 在这种情况下,搜索将返回在接下来的 7 天内开始的事件。 - 查询
$filter
参数用于按query
值(在本例中从 datagrid 中选择的公司名称)筛选结果。 该contains()
函数用于查找query
日历事件属性中的subject
值。 - 查询
$orderby
参数用于按属性对结果进行start/dateTime
排序。
- URL
url
创建后,将使用值url
向Microsoft图形 API 发出 GET 请求,并将结果返回到调用方。与前面的组件一样, 日历事件 组件(calendar-events.component.ts 文件)调用
search()
并显示结果。override async search(query: string) { this.data = await this.graphService.searchCalendarEvents(query); }
注意
还可以从自定义 API 或服务器端应用程序进行Microsoft Graph 调用。 查看以下教程,查看从 Azure 函数调用 Microsoft 图形 API 的示例。
现在,你已看到使用 Microsoft Graph 检索文件、聊天、电子邮件和日历事件的示例。 相同的概念也适用于其他Microsoft图形 API。 例如,可以使用 Microsoft Graph 用户 API 搜索组织中的用户。 还可以使用Microsoft图形 组 API 搜索组织中的组。 可以在文档中查看Microsoft图形 API 的完整列表。
祝贺你!
你已完成本教程
祝贺你! 本教程介绍了如何:
- Azure OpenAI 可用于提高用户工作效率。
- Azure 通信服务可用于集成通信功能。
- Microsoft图形 API 和组件可用于检索和显示组织数据。
通过使用这些技术,你可以创建有效的解决方案,通过最大程度地减少上下文转变并提供必要的决策信息来提高用户工作效率。
清理 Azure 资源
清理 Azure 资源以避免对帐户产生更多费用。 转到Azure 门户并删除以下资源:
- Azure AI 搜索资源
- Azure 存储资源
- Azure OpenAI 资源(确保先删除模型,然后再删除 Azure OpenAI 资源)
- Azure 通信服务资源
后续步骤
文档
- Azure OpenAI 文档
- 基于自有数据的 Azure OpenAI
- Azure 通信服务文档
- Microsoft Graph 文档
- Microsoft Graph 工具包文档
- Microsoft Teams 开发人员文档
培训内容
你有关于此部分的问题? 如果有,请向我们提供反馈,以便我们对此部分作出改进。