將 OpenAI、通訊和組織資料功能整合到企業營運應用程式
層級:中繼
本教學課程示範如何將 Azure OpenAI、Azure 通訊服務 和 Microsoft Graph/Microsoft Graph 工具組整合到企業營運應用程式中,以提高用戶生產力、提升用戶體驗,並將 LOB 應用程式提升到下一個層級。 應用程式中的主要功能包括:
- AI:讓使用者以自然語言詢問問題,並將其答案轉換成 SQL,以用來查詢資料庫、允許使用者定義可用來自動產生電子郵件和簡訊的規則,以及瞭解如何使用自然語言從您自己的自定義數據源擷取數據。 Azure OpenAI 會用於這些功能。
- 通訊:使用 Azure 通訊服務 啟用客戶和電子郵件/簡訊功能的應用程式內電話通話。
- 組織數據:提取使用者可能需要的相關組織數據(檔、聊天、電子郵件、行事曆事件),以避免內容切換。 提供這種類型的組織數據的存取,可減少使用者切換至 Outlook、Teams、OneDrive、其他自定義應用程式、手機等的需求,因為應用程式會直接提供所需的特定數據和功能。 Microsoft Graph 和 Microsoft Graph 工具組會用於這項功能。
應用程式是簡單的客戶管理應用程式,可讓使用者管理其客戶和相關數據。 它是由使用 TypeScript 建置的前端所組成,其會呼叫後端 API 來擷取數據、與 AI 功能互動、傳送電子郵件/SMS 訊息,以及提取組織數據。 以下是您將在本教學課程中逐步解說的應用程式解決方案概觀:
本教學課程將逐步引導您完成設定必要 Azure 和Microsoft 365 資源的程式。 它也會逐步引導您完成用來實作 AI、通訊和組織數據功能的程式碼。 雖然您不需要複製並貼上程序代碼,但有些練習會要求您修改程序代碼,以嘗試不同的案例。
您將在本教學課程中建置的內容
選擇您自己的冒險
您可以從頭到尾完成整個教學課程,或完成感興趣的特定主題。 本教學課程分成下列主題:
- 複製項目練習 (必要練習)。
- AI 練習:建立 Azure OpenAI 資源 ,並用它來將自然語言轉換成 SQL、產生電子郵件/SMS 訊息,以及使用您自己的數據和檔。
- 通訊練習:建立 Azure 通訊服務 資源,並使用它從應用程式撥打電話,並傳送電子郵件/簡訊。
- 組織數據練習: 建立Microsoft Entra ID 應用程式註冊 ,讓 Microsoft Graph 和 Microsoft Graph 工具組可用來驗證組織數據,並將組織數據提取到應用程式中。
必要條件
- 節點 - 節點 20+ 和 npm 10+ 將用於此專案
- git
- Visual Studio Code (雖然建議使用 Visual Studio Code ,但可以使用任何編輯器)
- Azure 訂用帳戶
- Microsoft 365 開發人員租使用者
- Docker Desktop 或其他符合 OCI (Open Container Initiative) 規範的容器運行時間,例如 Podman,或 能夠執行容器的 nerdctl 。
Microsoft本教學課程中使用的雲端技術
- Azure 通訊服務
- Azure OpenAI 服務
- Microsoft Entra ID
- Microsoft Graph
- Microsoft Graph 工具組
複製專案
本教學課程中使用的程式碼項目位於 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,但任何程式代碼編輯器都可以用來處理範例專案。
請注意下列資料夾與檔案:
- client:用戶端應用程式程序代碼。
- 伺服器:伺服器端 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、產生電子郵件/SMS 訊息內容等等。
在此練習中,您將會:
- 建立 Azure OpenAI 服務資源。
- 部署模型。
- 使用 Azure OpenAI 服務資源中的值來更新 .env 檔案。
建立 Azure OpenAI 服務資源
請瀏覽瀏覽器中的 Azure 入口網站 並登入。
在入口網站頁面頂端的搜尋列中輸入 openai,然後從出現的選項中選取 [Azure OpenAI]。
選取 工具列中的 [建立 ]。
注意
雖然本教學課程著重於 Azure OpenAI,但如果您有 OpenAI API 金鑰並想要使用它,您可以略過本節,並直接前往 下方的更新專案的 .env 檔案 一節。 在 .env 檔案中將 OpenAI API 金鑰指派給
OPENAI_API_KEY
(您可以忽略與 OpenAI 相關的任何其他.env
指示)。Azure OpenAI 模型可在特定區域中使用。 請流覽 Azure OpenAI 模型可用性檔,以瞭解哪些區域支援本教學課程中使用的 gpt-4o 模型。
執行下列工作:
- 選取 Azure 訂閱。
- 選取要使用的資源群組(視需要建立新的資源群組)。
- 根據您稍早查看的文件,選取支援 gpt-4o 模型的區域。
- 輸入資源名稱。 它必須是唯一值。
- 選取標準 S0 定價層。
選取 [下一步 ],直到您進入 [ 檢閱 + 提交 ] 畫面為止。 選取 建立。
建立 Azure OpenAI 資源之後,流覽至該資源並選取 [資源管理] -->[金鑰] 和 [端點]。
找出 KEY 1 和端點值。 您將在下一節中使用這兩個值,以便將它們複製到本機檔案。
選取 [資源管理 ] --[>模型部署]。
選取 [ 管理部署] 按鈕以移至 Azure OpenAI Studio。
選取 工具列中的 [部署模型 ] -->[部署基底模型 ]。
從模型清單中選取 gpt-4o ,然後選取 [ 確認]。
注意
Azure OpenAI 支援 數種不同類型的模型。 每個模型都可以用來處理不同的案例。
下列對話框隨即顯示。 花點時間檢查所提供的預設值。
將 每分鐘令牌速率限制 (thousands) 值變更為 100K。 這可讓您對模型提出更多要求,並避免在執行下列步驟時達到速率限制。
選取部署。
部署模型之後,請選取 [遊樂場 ] -->Chat。
[ 部署] 下拉式清單應該會顯示 gpt-4o 模型。
請花點時間閱讀 所提供的系統訊息 文字。 這會告訴模型如何在使用者與其互動時運作。
在聊天區域中找出文本框,然後輸入 摘要什麼是 Generative 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>
注意
您會看到 和
OPENAI_API_VERSION
的值OPENAI_MODEL
已在 .env 檔案中設定。 模型值會設定為 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-compose 並執行 Podman,請在終端機視窗中執行
podman-compose up
,然後按 Enter。若要使用 Docker Desktop、Podman、nerdctl 或您已安裝的另一個容器運行時間直接執行 PostgreSQL 容器,請在終端機視窗中執行下列命令:
Mac、Linux 或 Windows 子系統 Linux 版 (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
進入 客戶端 資料夾,然後執行下列命令來安裝相依性並啟動網頁伺服器。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 傳回查詢結果,並在瀏覽器中顯示它們。
讓我們從實驗不同的 GPT 提示開始,這些提示可用來將自然語言轉換成 SQL。
使用自然語言到 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 Bot」。
資料庫中的數據表名稱和數據行已定義。 提示中包含的高階架構可以在 server/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 插入式攻擊。
規則的定義是一律傳回 JSON 物件,其中包含 SQL 查詢和其中的參數值。
提供範例使用者提示和預期的 SQL 查詢和參數值。 這稱為 「很少拍攝」的學習。 雖然 LLM 是針對大量數據進行定型,但只能使用一些範例來適應新工作。 替代方法是「零射」學習,其中未提供任何範例,而且模型預期會產生正確的 SQL 查詢和參數值。
函
getSQLFromNLP()
式會將系統和使用者提示傳送至名為callOpenAI()
的函式,該函式也位於伺服器/openAI.ts檔案中。 函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
、userPrompt
和temperature
呼叫createAzureOpenAICompletion()
,以產生完成。 - 它會從完成的第一個選項擷取內容,並修剪任何額外的空格符。
- 如果內容包含類似 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
是使用來自.env
檔案的值來建立的 (OPENAI_API_KEY
、OPENAI_ENDPOINT
OPENAI_API_VERSION
OPENAI_MODEL
。 這些值可用來建構 URL 來呼叫 Azure OpenAI。
- 物件
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()
server/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,客戶可享有 Microsoft Azure 的安全性功能,同時執行與 OpenAI 相同的模型。 Azure OpenAI 提供私人網路、區域可用性,以及負責任 AI 內容篩選。 深入瞭解 Azure OpenAI 服務的數據、隱私權和安全性。
您現在已瞭解如何使用 Azure OpenAI 將自然語言轉換成 SQL,並了解實作這種類型的功能優缺點。 在下一個練習中,您將瞭解如何使用 Azure OpenAI 產生電子郵件和簡訊。
AI:產生完成
除了 SQL 功能的自然語言之外,您也可以使用 Azure OpenAI 服務來產生電子郵件和簡訊,以提高用戶生產力並簡化通訊工作流程。 藉由利用 Azure OpenAI 的語言產生功能,使用者可以定義特定規則,例如「訂單延遲 5 天」,系統會根據這些規則自動產生內容適當的電子郵件和簡訊。
這項功能可做為使用者的跳躍起點,為使用者提供精心製作的訊息範本,讓他們在傳送之前可以輕鬆地自定義。 結果是撰寫訊息所需的時間和精力大幅減少,讓用戶能夠專注於其他重要工作。 此外,Azure OpenAI 的語言產生技術可以整合到自動化工作流程中,讓系統能夠自主產生和傳送訊息,以響應預先定義的觸發程式。 這種自動化層級不僅可加速通訊程式,還能確保各種案例的一致且精確的傳訊。
在本練習中,您將會:
- 試驗不同的提示。
- 使用提示來產生電子郵件和SMS訊息的完成。
- 探索可啟用 AI 完成的程式代碼。
- 瞭解提示工程的重要性,並在提示中包含規則。
讓我們開始試驗可用來產生電子郵件和簡訊的不同規則。
使用 AI 完成功能
在上一個 練習 中,您已啟動資料庫、API 和應用程式。 您也會更新檔案
.env
。 如果您未完成這些步驟,請遵循練習結尾的指示,再繼續進行。返回瀏覽器 (http://localhost:4200) 並選取 datagrid 中任何數據列中的 [連絡客戶],然後選取 [電子郵件/SMS 客戶] 以移至 [訊息產生器] 畫面。
這會使用 Azure OpenAI 將您定義的訊息規則轉換成電子郵件/SMS 訊息。 執行下列工作:
輸入 [訂單] 之類的 規則會在輸入中延遲 5 天 ,然後選取 [產生電子郵件/簡訊] 按鈕。
您會看到針對電子郵件產生的主旨和本文,以及針對SMS產生的簡短訊息。
注意
因為 Azure 通訊服務 尚未啟用,因此您將無法傳送電子郵件或簡訊。
關閉瀏覽器中的電子郵件/SMS 對話框視窗。 現在您已瞭解此功能運作情形,讓我們來檢查其實作方式。
探索 AI 完成程式碼
提示
如果您使用 Visual Studio Code,您可以選取下列專案來直接開啟檔案:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然後輸入您要開啟的檔案名。
開啟伺服器/apiRoutes.ts檔案,並找出
completeEmailSmsMessages
路由。 選取 [產生電子郵件/SMS 訊息] 按鈕時,應用程式前端部分會呼叫此 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
也包括:- 助理要遵循的規則,以控制訊息的語氣、開始和結束格式、SMS 訊息的最大長度等等。
- 應該包含在回應中的數據相關信息 - 在此案例中為 JSON 物件。
userPrompt
用來定義使用者想要在產生電子郵件和簡訊時包含的規則和聯繫人名稱。 您 稍早輸入的訂單延遲 5 天 規則會包含在 中userPrompt
。- 函式會呼叫
callOpenAI()
您稍早探索的函式,以產生電子郵件和簡訊完成。
返回瀏覽器,重新整理頁面,然後選取任何數據列上的 [連絡客戶],然後選取 [電子郵件/SMS 客戶] 以再次進入 [訊息產生器] 畫面。
在訊息產生器輸入中輸入下列規則:
- 訂單早於排程。
- 告知客戶再也不要再訂購,我們不希望他們的業務。
選取 [ 產生電子郵件/簡訊] ,並記下訊息。 系統
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.
規則。 儲存檔案。傳回瀏覽器中的電子郵件/SMS 訊息產生器,然後再次執行相同的規則:
- 訂單早於排程。
- 告知客戶再也不要再訂購,我們不希望他們的業務。
選取 [ 產生電子郵件/SMS 訊息 ],並注意傳回的訊息。
這些案例中會發生什麼事? 使用 Azure OpenAI 時, 可以套用內容篩選 ,以確保一律使用適當的語言。 如果您使用 OpenAI,系統會使用系統提示字元中定義的規則,以確保傳回的訊息是適當的。
注意
這說明使用正確的資訊和規則來設計提示的重要性,以確保傳回適當的結果。 如需此程式 的詳細資訊,請參閱提示工程 文件的簡介。
復原您在 中
completeEmailSMSMessages()
所做的systemPrompt
變更、儲存盤案,然後再次重新執行,但只使用Order is ahead of schedule.
規則(不包含負規則)。 這次您應該會看到如預期傳回的電子郵件和簡訊。在繼續進行下一個練習之前,需要考慮幾個最後幾點:
- 請務必讓迴圈中的人員檢閱產生的訊息。 在此範例中,Azure OpenAI 完成會傳回建議的電子郵件和簡訊,但用戶可以在傳送之前覆寫這些訊息。 如果您打算將電子郵件自動化,請執行某種類型的人工檢閱程式,以確保已核准的郵件已送出很重要。 將 AI 視為警戒,而不是 Autopilot。
- 完成只會和您在提示中新增的規則一樣好。 花點時間測試提示和傳回的完成。 請考慮使用 提示流程 來建立完整的解決方案,以簡化原型設計、實驗、反覆運算和部署 AI 應用程式。 邀請其他項目項目關係人檢閱完成專案。
- 您可能需要包含後置處理程序代碼,以確保正確處理非預期的結果。
- 使用系統提示來定義 AI 助理應遵循的規則和資訊。 使用使用者提示來定義使用者想要包含在完成中的規則和資訊。
AI:Azure OpenAI on your data
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 資源的認證登入。
從導覽功能表中選取 [部署 ]。
選取工具列中的 [選取部署模型] -->[部署基底模型]。
從模型清單中選取 text-embedding-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 和索引鍵值。
選取 導覽功能表中的 [設定 ] -->Semantic ranker , 並確定已選取 [免費 ]。
注意
若要檢查特定區域中是否有語意排名器, 請檢查 Azure 網站上的 [依區域 提供的產品] 頁面,以查看您的區域是否已列出。
返回 [Azure AI Studio 新增數據 ] 對話框,然後從 [選取 Azure AI 搜尋資源] 下拉式清單中選取新建立的 搜尋資源 。 如果您沒有看到它列示,請選取下拉式清單旁的重新整理圖示。
針對 Enter 索引名稱值,輸入 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 服務端點、金鑰和索引名稱更新下列值。 您稍早在本練習中將端點和金鑰複製到本機檔案。
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
- 布爾值,指出是否要搭配 Azure OpenAI 使用 AI 搜尋。 在此情況下,它會設定為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); }
getAzureOpenAIBYODCompletion()
找出伺服器/openAI.ts中的函式。 這與您稍早檢查的函式相當類似getAzureOpenAICompletion()
,但會顯示為個別函式,以醒目提示 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
屬性,其中包含本練習稍早新增至.env
檔案的key
AI 搜尋資源的、endpoint
和index_name
值 - 使用、
userPrompt
、temperature
和dataSources
值呼叫systemPrompt
函createAzureOpenAICompletion()
式。 此函式可用來呼叫 Azure OpenAI API 並傳回結果。 - 傳回回應之後,會將檔引文記錄到主控台。 然後,完成訊息內容會傳回給呼叫端。
- 建立
在繼續進行下一個練習之前,需要考慮幾個最後幾點:
- 範例應用程式會在 Azure AI 搜尋中使用單一索引。 您可以搭配 Azure OpenAI 使用多個索引和數據源。
dataSources
函式中的getAzureOpenAIBYODCompletion()
屬性可以更新為視需要包含多個數據源。 - 您必須謹慎評估此類型的案例的安全性。 使用者應該無法詢問問題,並從無法存取的檔取得結果。
- 範例應用程式會在 Azure AI 搜尋中使用單一索引。 您可以搭配 Azure OpenAI 使用多個索引和數據源。
既然您已瞭解 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 訂用帳戶上需要信用卡才能建立免付費電話號碼。 如果您沒有檔案上的卡片,請隨意略過新增電話號碼,並跳到聯機電子郵件網域的練習下一節。 您仍然可以使用應用程式,但無法撥打電話號碼。
- 號碼:針對列出的其中一個電話號碼,選取 [新增至購物車 ]。
- 國家或地區:
選取 [ 下一步],檢閱電話號碼詳細數據,然後選取 [ 立即購買]。
注意
美國 和加拿大現在強制對免付費號碼進行簡訊驗證。 若要啟用 SMS 訊息,您必須在電話號碼購買之後提交驗證。 雖然本教學課程不會進行該程式,但您可以從 [資源] 功能選取 [電話語音和簡訊 ] -->[法規檔 ],然後新增必要的驗證檔。
建立電話號碼之後,請選取它以移至 [功能 ] 面板。 確定已設定下列值(預設應設定):
- 在 [ 通話] 區段中,選取
Make calls
。 - 在 SMS 區段中,選取
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 伺服器和網頁伺服器,您必須停止 API 伺服器和 Web 伺服器,然後重新啟動它們以挑選 .env 檔案變更。 您可以讓資料庫保持執行狀態。
找出執行 API 伺服器和網頁伺服器的終端機視窗,然後按 CTRL + C 以停止它們。 在每一個終端機視窗中輸入
npm start
,然後按 Enter 鍵,再次啟動它們。 繼續進行下一個練習。如果您尚未啟動資料庫、API 伺服器和網頁伺服器,請完成下列步驟:
在下列步驟中,您將在 Visual Studio Code 中建立三個終端機視窗。
以滑鼠右鍵按兩下 Visual Studio Code 檔案清單中的 .env 檔案,然後選取 [在整合式終端機中開啟]。 在繼續之前,請確定您的終端機位於專案的根目錄 - openai-acs-msgraph 。
從 下列其中一個選項 中選擇,以啟動 PostgreSQL 資料庫:
如果您已安裝 並執行 Docker Desktop ,請在終端機視窗中執行
docker-compose up
,然後按 Enter。如果您已安裝 Podman-compose 並執行 Podman,請在終端機視窗中執行
podman-compose up
,然後按 Enter。若要使用 Docker Desktop、Podman、nerdctl 或您已安裝的另一個容器運行時間直接執行 PostgreSQL 容器,請在終端機視窗中執行下列命令:
Mac、Linux 或 Windows 子系統 Linux 版 (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
進入 客戶端 資料夾,然後執行下列命令來安裝相依性並啟動網頁伺服器。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。 請注意下列主要功能:
- 在 中
ngOnInit()
呼叫acsService.getAcsToken()
函式,以擷取 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 和存取令牌。 - 擷取存取令牌之後,程式代碼會執行下列動作:
- 建立的新實例
CallClient
,並使用AzureCommunicationTokenCredential
存取令牌。 - 使用
CallClient
和AzureCommunicationTokenCredential
物件建立 的新實例CallAgent
。 稍後您會看到CallAgent
用來啟動和結束通話。
- 建立的新實例
- 藉由呼叫
開啟 acs.services.ts 並找出 函
getAcsToken()
式。 檔案的完整路徑是 client/src/app/core/acs.service.ts。 函式會對 API 伺服器公開的路由提出 HTTP GET 要求/acstoken
。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) 並選取 [連絡客戶],然後在第一個數據列中選取 [電子郵件/SMS 客戶]。
選取 [ 電子郵件/SMS] 索引標籤,然後執行下列工作:
- 輸入電子郵件 主旨 和 本文 ,然後選取 [ 傳送電子郵件] 按鈕。
- 輸入SMS訊息,然後選取 [ 傳送簡訊 ] 按鈕。
注意
美國 和加拿大現在強制使用免付費電話號碼的簡訊驗證。 若要啟用 SMS 訊息,您必須在電話號碼購買之後提交驗證。 雖然本教學課程不會進行該程式,但您可以從 Azure 入口網站 中的 Azure 通訊服務 資源選取電話語音和簡訊 -->法規檔,並新增必要的驗證檔。
確認您收到電子郵件和簡訊。 只有在您提交先前附註中所述的法規檔時,SMS 功能才能運作。 提醒您,電子郵件訊息會傳送至定義的
CUSTOMER_EMAIL_ADDRESS
值,並將SMS訊息傳送至 .env 檔案中定義的CUSTOMER_PHONE_NUMBER
值。 如果您無法提供 美國 型電話號碼,以用於 SMS 訊息,您可以略過此步驟。注意
如果您在收件匣中看不到您在 .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
包含phone
和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) ); }
中的
AcsService
函sendEmail()
式會執行下列工作:- 呼叫 函式,
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
對象發出信號,並將回應傳送給呼叫端。
探索SMS代碼
返回 您稍早開啟的 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()
式,並傳遞 SMS 訊息和客戶電話號碼。 因為資料庫包含範例數據,CUSTOMER_PHONE_NUMBER
因此會使用 環境變數,this.data.customerPhoneNumber
而不是 。 在真實世界中,this.data.customerPhoneNumber
將會使用值。 sendSms()
訂閱服務中的acsService
函式。 此函式會傳回 RxJS 可觀察,其中包含來自用戶端服務的回應。- 如果 SMS 訊息已成功傳送,則會將
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 SMS 功能互動。 開啟acs.ts檔案。 檔案的完整路徑是 伺服器/typescript/acs.ts ,並找出 函
sendSms()
式。函
sendSms()
式會執行下列工作:建立新的
SmsClient
物件,並將ACS連接字串傳遞給它(此值是從ACS_CONNECTION_STRING
環境變數擷取)。const smsClient = new SmsClient(connectionString);
呼叫 函式並
smsClient.send()
傳遞 ACS 電話號碼 (from
)、客戶電話號碼 (to
) 和 SMS 訊息:const sendResults = await smsClient.send({ from: process.env.ACS_PHONE_NUMBER as string, to: [customerPhoneNumber], message: message }); return sendResults;
傳回呼叫端的回應。
您可以在下列文章中深入瞭解 ACS 電子郵件和 SMS 功能:
繼續進行下一個練習之前,讓我們先檢閱本練習中涵蓋的重要概念:
- acs.service.ts檔案會封裝用戶端應用程式所使用的 ACS 電子郵件和 SMS 功能。 它會處理對伺服器的 API 呼叫,並將回應傳回給呼叫端。
- 伺服器端 API 會使用 ACS
EmailClient
和SmsClient
對象來傳送電子郵件和簡訊。
既然您已瞭解如何傳送電子郵件和簡訊,讓我們將焦點切換為將組織數據整合到應用程式中。
組織數據:建立Microsoft專案標識碼應用程式註冊
將組織資料(電子郵件、檔案、聊天和行事曆事件)直接整合到您的自定義應用程式中,以提高用戶生產力。 藉由使用 Microsoft Graph API 和 Microsoft Entra ID,您可以順暢地擷取及顯示應用程式內的相關數據,減少使用者切換內容的需求。 無論是參考傳送給客戶的電子郵件、檢閱 Teams 訊息或存取檔案,使用者都可以快速找到所需的資訊,而不需要離開您的應用程式、簡化其決策程式。
在本練習中,您將會:
- 建立Microsoft Entra ID 應用程式註冊,讓 Microsoft Graph 可以存取組織數據,並將其帶入應用程式。
- 從 Microsoft Teams 中找出
team
和channel
標識碼,這些 Teams 需要將聊天訊息傳送至特定頻道。 - 使用您Microsoft Entra ID 應用程式註冊中的值更新專案的 .env 檔案。
建立Microsoft項目標識碼應用程式註冊
移至 [Azure 入口網站],然後選取 [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 (本教學課程的前置條件中提及)。
登入之後,請展開小組,並尋找您想要從應用程式傳送訊息的頻道。 例如,您可以選取 [公司 團隊] 和 [一般 ] 頻道(或您想要使用的任何小組/頻道)。
在小組標頭中,按兩下三個點(...),然後選取 [ 取得小組的連結]。
在彈出視窗中顯示的連結中,小組標識碼是 之後
team/
的字母和數位字串。 例如,在連結 “https://teams.microsoft.com/l/team/19%3ae9b9.../", team ID 為 19%3ae9b9... 最多到下列/
字元。複製小組標識碼,並將它指派給
TEAM_ID
.env 檔案。在通道標頭中,按兩下三個點(...),然後選取 [ 取得通道的連結]。
在彈出視窗中顯示的連結中,通道標識碼是 後面
channel/
字母和數位的字串。 例如,在連結 “https://teams.microsoft.com/l/channel/19%3aQK02.../", 信道標識符是 19%3aQK02... 最多到下列/
字元。複製通道標識碼,並將它指派給
CHANNEL_ID
.env 檔案中。繼續之前,請先儲存 env 檔案。
啟動/重新啟動應用程式和 API 伺服器
根據您完成的練習,執行 下列其中一 個步驟:
如果您在先前的練習中啟動資料庫、API 伺服器和網頁伺服器,您必須停止 API 伺服器和 Web 伺服器,然後重新啟動它們以挑選 .env 檔案變更。 您可以讓資料庫保持執行狀態。
找出執行 API 伺服器和網頁伺服器的終端機視窗,然後按 CTRL + C 以停止它們。 在每一個終端機視窗中輸入
npm start
,然後按 Enter 鍵,再次啟動它們。 繼續進行下一個練習。如果您尚未啟動資料庫、API 伺服器和網頁伺服器,請完成下列步驟:
在下列步驟中,您將在 Visual Studio Code 中建立三個終端機視窗。
以滑鼠右鍵按兩下 Visual Studio Code 檔案清單中的 .env 檔案,然後選取 [在整合式終端機中開啟]。 在繼續之前,請確定您的終端機位於專案的根目錄 - openai-acs-msgraph 。
從 下列其中一個選項 中選擇,以啟動 PostgreSQL 資料庫:
如果您已安裝 並執行 Docker Desktop ,請在終端機視窗中執行
docker-compose up
,然後按 Enter。如果您已安裝 Podman-compose 並執行 Podman,請在終端機視窗中執行
podman-compose up
,然後按 Enter。若要使用 Docker Desktop、Podman、nerdctl 或您已安裝的另一個容器運行時間直接執行 PostgreSQL 容器,請在終端機視窗中執行下列命令:
Mac、Linux 或 Windows 子系統 Linux 版 (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
進入 客戶端 資料夾,然後執行下列命令來安裝相依性並啟動網頁伺服器。npm install
npm start
瀏覽器將會啟動,而且系統會帶您前往 http://localhost:4200。
組織數據:登入使用者並取得存取令牌
用戶必須使用 Microsoft Entra 識別碼進行驗證,才能讓 Microsoft Graph 存取組織數據。 在此練習中,您將瞭解如何使用 Microsoft Graph 工具組的 mgt-login
元件來驗證使用者及擷取存取令牌。 接著,可以使用存取令牌來呼叫 Microsoft Graph。
注意
如果您不熟悉 Microsoft Graph,您可以在 Microsoft Graph 基本概念學習路徑中深入瞭解。
在本練習中,您將會:
- 瞭解如何將Microsoft Entra ID 應用程式與 Microsoft Graph 工具組產生關聯,以驗證使用者並擷取組織數據。
- 瞭解範圍的重要性。
- 瞭解如何使用 Microsoft Graph 工具組的 mgt-login 元件來驗證使用者及擷取存取令牌。
使用登入功能
在上一個練習中,您已在 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 和其他可用來登入使用者及擷取及顯示組織數據的 Web 元件。開啟 client/src/main.ts ,並注意下列從套件匯
@microsoft/mgt-components
入。 匯入的符號可用來註冊應用程式中所使用的Microsoft Graph 工具元件。import { registerMgtLoginComponent, registerMgtSearchResultsComponent, registerMgtPersonComponent, } from '@microsoft/mgt-components';
捲動至檔案底部,並記下下列程序代碼:
registerMgtLoginComponent(); registerMgtSearchResultsComponent(); registerMgtPersonComponent();
此程式代碼會
mgt-login
註冊、mgt-search-results
和mgt-person
Web 元件,並讓它們可用於應用程式。若要使用 mgt-login 元件來登入使用者,Microsoft Entra ID 應用程式的用戶端標識碼(儲存
ENTRAID_CLIENT_ID
在 .env 檔案中)需要參考及使用。開啟 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 用戶端識別碼,以及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 標識符進行驗證。 新增 旗標以容納用戶選擇只完成教學課程中的 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 Graph
me
API 進行呼叫,以擷取使用者的配置檔(me
代表目前登入的使用者)。 程式this.userLoggedIn.emit(me)
代碼語句會從元件發出事件,以將配置檔數據傳遞至父元件。 在此案例中,父元件app.component.ts檔案,這是應用程式的根元件。若要深入瞭解 mgt-login 元件,請流覽 Microsoft Graph 工具組 檔。
既然您已登入應用程式,讓我們看看如何擷取組織數據。
組織數據:擷取檔案、聊天和傳送訊息給Teams
在現今的數字環境中,使用者會使用各式各樣的組織數據,包括電子郵件、聊天、檔案、行事曆活動等等。 這可能會導致頻繁的內容班次—在工作或應用程式之間切換,這可能會中斷焦點並減少生產力。 例如,處理項目的使用者可能需要從目前的應用程式切換至 Outlook,以在電子郵件中尋找重要詳細數據,或切換至 商務用 OneDrive 以尋找相關檔案。 這種來回動作會干擾焦點,浪費時間,以便更好地花在手邊的任務。
為了提高效率,您可以將組織數據直接整合到使用者每天使用的應用程式。 藉由將組織數據帶入您的應用程式,使用者可以更順暢地存取和管理資訊,將內容轉移降至最低,並提升生產力。 此外,這項整合提供寶貴的見解和內容,讓用戶能夠做出明智的決策,並更有效率地工作。
在本練習中,您將會:
- 瞭解 Microsoft Graph 工具組中的 mgt-search-results Web 元件如何用來搜尋檔案。
- 瞭解如何直接呼叫 Microsoft Graph,以從 商務用 OneDrive 擷取檔案,並從 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 的相關組織數據。 若要新增一些範例數據,請至少執行下列其中一個動作:
造訪 https://onedrive.com 並使用您的 Microsoft 365 開發人員租用戶認證來新增檔案。
- 選取 左側導覽中的 [我的檔案 ]。
- 從功能表中選取 [+ 新增 ],然後 選取 [資料夾上傳 ]。
- 從您複製的項目選取 openai-acs-msgraph/customer documents 資料夾。
使用您的 Microsoft 365 開發人員租使用者認證來流覽 https://teams.microsoft.com 並登入,以新增聊天訊息和行事曆事件。
選取 左側導覽中的Teams 。
選取小組和頻道。
選取 [ 開始文章]。
針對 主旨輸入 Adatum Corporation 的新訂單,以及您想要在訊息本文中新增的任何其他文字。 選取 [過帳] 按鈕。
您可以隨意新增其他聊天訊息,以提及應用程式中使用的其他公司,例如 Adventure Works Cycles、 Contoso Pharmaceuticals 和 Tailwind Traders。
選取左側導覽中的 [ 行事曆 ]。
選取 [ 新增會議]。
輸入標題和本文的「與 Adatum Corporation 關於專案排程的會面」。
選取 [儲存]。
造訪 https://outlook.com 並使用您的 Microsoft 365 開發人員租用戶認證來新增電子郵件。
選取 [ 新增郵件]。
在 [ 收 件者] 欄位中輸入您的個人電子郵件位址。
針對主體輸入 Adatum Corporation 的新訂單,以及您想要本文的任何專案。
請選取傳送。
返回瀏覽器中的應用程式,然後重新整理頁面。 針對 Adatum Corporation 數據列再次選取 [檢視相關內容]。 您現在應該會看到索引標籤中顯示的數據,視您在上一個步驟中執行的工作而定。
讓我們探索可在應用程式中啟用組織數據功能的程序代碼。 為了擷取數據,應用程式的用戶端部分會使用您稍早查看的 mgt-login 元件所擷取的存取令牌,來呼叫 Microsoft Graph API。
探索檔案搜尋程序代碼
提示
如果您使用 Visual Studio Code,您可以選取下列專案來直接開啟檔案:
- Windows/Linux: Ctrl + P
- Mac: Cmd + P
然後輸入您要開啟的檔案名。
讓我們從查看如何從 商務用 OneDrive 擷取檔案數據開始。 開啟 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 中的檔案。屬性
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 中的檔案,因此
driveItem
會如同您稍早在mgt-search-results
元件中看到的一樣使用。 這與您稍早看到的 mgt-search-results 元件中傳遞driveItem
至entity-types
相同。 然後,參數query
會與 一起ContentType:Document
新增至queryString
篩選。const filter = { "requests": [ { "entityTypes": [ "driveItem" ], "query": { "queryString": `${query} AND ContentType:Document` } } ] };
接著會使用
Providers.globalProvider.graph.client.api()
函式對/search/query
Microsoft Graph 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 工具組控件來處理此作業。 以下是將 屬性系結data
至其中一個名為 的元件屬性files
,並在使用者與檔案互動時處理itemClick
事件的範例。<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,並將結果轉換成物件數位,其中包含 、 和messageId
符合搜尋字詞的資訊teamId
channelId
。 - 若要擷取 Teams 頻道訊息,系統會對 API 進行
/teams/${chat.teamId}/channels/${chat.channelId}/messages/${chat.messageId}
第二次呼叫,並teamId
傳遞 、channelId
和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 頻道。 呼叫 Microsoft Graph 的端點即可完成此作業
/teams/${teamId}/channels/${channelId}/messages
。在下列程序代碼中,您會看到已建立包含
teamId
和channelId
值的 URL。 環境變數值用於此範例中的小組標識碼和通道標識碼,但這些值也可以使用 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 從 商務用 OneDrive Microsoft Teams 擷取檔案,以及 Microsoft Graph 工具組中的 mgt-search-results 元件。 您也瞭解如何將訊息傳送至 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-results 元件來擷取訊息之外,Microsoft Graph 還提供數個 API,可用來搜尋電子郵件。
/search/query
您稍早看到的 API 當然可以使用,但更直接的選項是messages
API。若要查看如何呼叫此 API,請返回 graph.service.ts 並找出函
searchEmailMessages()
式。 它會建立 URL,可用來呼叫messages
Microsoft Graph 的端點,並將值指派query
給$search
參數。 然後,程式代碼會提出 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,以用來呼叫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 Graph API 提出 GET 要求,並將結果傳回給呼叫端。如同先前的元件,行事歷事件元件(calendar-events.component.ts檔案)會呼叫
search()
並顯示結果。override async search(query: string) { this.data = await this.graphService.searchCalendarEvents(query); }
注意
您也可以從自定義 API 或伺服器端應用程式進行Microsoft Graph 呼叫。 檢視下列教學課程,以查看從 Azure 函式呼叫 Microsoft Graph API 的範例。
您現在已看到使用 Microsoft Graph 來擷取檔案、聊天、電子郵件訊息和行事曆事件的範例。 相同的概念也可以套用至其他Microsoft Graph API。 例如,您可以使用 Microsoft Graph 使用者 API 來搜尋組織中的使用者。 您也可以使用 Microsoft Graph 群組 API 來搜尋組織中的群組。 您可以在檔案中檢視Microsoft圖形 API 的完整清單。
恭喜!
您已完成本教學課程
恭喜! 在本教學課程中,您已瞭解如何:
- Azure OpenAI 可用來提升用戶生產力。
- Azure 通訊服務 可用來整合通訊功能。
- Microsoft Graph API 和元件可用來擷取和顯示組織數據。
藉由使用這些技術,您可以建立有效的解決方案,藉由將內容轉變降到最低,並提供必要的決策資訊來提升用戶生產力。
清除 Azure 資源
清除您的 Azure 資源,以避免對您的帳戶產生更多費用。 移至 Azure 入口網站 並移除下列資源:
- Azure AI 搜尋資源
- Azure 儲存體 資源
- Azure OpenAI 資源 (請確定您先刪除模型,然後再刪除 Azure OpenAI 資源)
- Azure 通訊服務資源
後續步驟
文件集
- Azure OpenAI 檔
- 資料上的 Azure OpenAI
- Azure 通訊服務檔
- Microsoft Graph 檔
- Microsoft Graph 工具組檔
- Microsoft Teams 開發人員檔
訓練內容
在這個區段有遇到問題嗎? 如果有,請提供意見反應,好讓我們可以改善這個區段。