在開發 Alexa 技能時,處理購買請求和實作逐步回應是提升使用者經驗的關鍵技術。處理購買請求需要驗證購買結果,並根據結果更新會話屬性。逐步回應則允許技能在執行耗時操作時,例如網路請求,提供 interim 回應,避免使用者等待超時。本文將示範如何使用 Python 和 aiohttp 函式庫實作這些功能,並說明如何整合逐步回應 API,讓技能在取得外部資源時保持互動性。

使用者購買商品後,技能需驗證購買結果並更新會話屬性。透過檢查 purchaseResult 的值,可以判斷購買是否成功。若購買成功,則更新會話屬性,例如設定 paid_jokes 標記。若購買被拒絕,則根據商品的購買狀態提供相應的提示。取消購買則需移除相關的會話屬性。退款流程需要引導使用者聯絡 Amazon 處理。網路請求可以使用 aiohttp 函式庫實作,透過非同步操作避免阻塞主執行緒。逐步回應則需要使用 SendDirectiveRequest 傳送 SpeakDirective 指令,告知使用者技能正在處理請求。在處理太空人數量 Intent 時,可以結合網路請求和逐步回應,先傳送 interim 回應,然後再傳送網路請求取得太空人數量,最後將結果傳回給使用者。

處理購買請求

當使用者發起購買請求時,Alexa 會向技能傳送一個請求,包含購買相關的資訊。為了處理這個請求,開發者需要實作一個 handler 函式,負責接收和處理這個請求。

logger.info("In CancelResponseHandler")
in_skill_response = in_skill_product_response(handler_input)
product_id = handler_input.request_envelope.request.payload.get("productId")

在上述程式碼中,首先我們記錄了一條日誌資訊,表示我們現在處於 CancelResponseHandler 中。然後,我們呼叫 in_skill_product_response 函式來取得與購買相關的資訊,並從請求 payload 中提取 product_id

驗證購買結果

接下來,我們需要驗證購買結果,以確定使用者是否成功購買了商品或服務。

if in_skill_response:
    product = [l for l in in_skill_response.in_skill_products if l.product_id == product_id]
    speech = None
    reprompt = None
    purchase_result = handler_input.request_envelope.request.payload.get("purchaseResult")
    purchasable = product[0].purchasable

在這段程式碼中,我們首先檢查是否有有效的購買回應。如果有,我們則從回應中找到與當前 product_id 匹配的商品,並初始化 speechreprompt 變數。同時,我們也從請求 payload 中提取 purchaseResult 以及檢查商品是否可以被購買。

處理購買結果

根據購買結果的不同,我們需要進行不同的處理。例如,如果購買被接受,我們可能需要更新使用者的會話屬性。

if purchase_result == PurchaseResult.ACCEPTED.value:
    session_attr = handler_input.attributes_manager.session_attributes
    session_attr["paid_jokes"] = False
    handler_input.attributes_manager.session_attributes = session_attr

在上述程式碼中,如果購買結果為接受,我們則更新使用者的會話屬性,設定 paid_jokesFalse,並儲存這些變化。

內容解密:
  • in_skill_product_response 函式用於取得與購買相關的資訊。
  • handler_input.request_envelope.request.payload.get("productId") 用於提取商品 ID。
  • purchaseResult 用於判斷購買結果。
  • 根據購買結果的不同,進行不同的處理邏輯。

圖表翻譯:

  flowchart TD
    A[收到購買請求] --> B[驗證購買結果]
    B --> C[更新會話屬性]
    C --> D[傳回回應]

此圖表示了處理購買請求的流程,從收到購買請求開始,到驗證購買結果、更新會話屬性,最終傳回回應。

處理使用者付費笑話存取權的取消

當使用者要求取消付費笑話存取權時,系統需要進行一系列的處理步驟。首先,系統需要檢查使用者的購買結果,如果使用者拒絕了購買,則系統會根據使用者的購買狀態(PurchasableState)來決定如何回應。

處理購買結果

如果購買結果為 DECLINED,則系統會檢查使用者的購買狀態。如果使用者的購買狀態為 PURCHASABLE,則系統會告知使用者他們目前沒有付費笑話存取權。否則,系統會提示使用者要求一個免費的笑話。

if purchase_result == PurchaseResult.DECLINED.value:
    if purchasable == PurchasableState.PURCHASABLE:
        speech = "您目前沒有付費笑話存取權。"
    else:
        speech = "請要求一個免費的笑話"

處理取消請求

如果使用者要求取消付費笑話存取權,系統會立即取消使用者的存取權,並移除相關的會話屬性。

# 刪除 paid_joke 會話屬性
del session_attr['paid_joke']

處理錯誤

如果在處理取消請求時出現錯誤,系統會記錄錯誤訊息,並回應使用者一個錯誤訊息。

else:
    logger.log("Connections.Response indicated failure. Error: {}".format(
        handler_input.request_envelope.request.status.message))
    return handler_input.response_builder.speak(
        "處理您的取消請求時出現錯誤。請再試一次或聯絡我們尋求幫助").response

退款工作流程

雖然系統可以立即取消使用者的付費笑話存取權,但退款工作流程並不會自動處理退款。相反,系統會導致客戶聯絡 Amazon 申請退款。當退款完成後,客戶的授權狀態將被更新為 NOT_ENTITLED

參考資源

更多關於處理付費笑話存取權和退款工作流程的資訊,可以參考以下資源:

透過這些步驟和資源,開發者可以實作一個完整的付費笑話存取權管理系統,包括購買、取消和退款工作流程。

逐步回應 - 存取網際網路

11.1 簡介

逐步回應是一種可以在您的技能取得進一步資訊之前生成臨時回應的方法。您的技能可能需要存取其他網站的資訊才能回應使用者。例如,它可能需要等待計程車公司或外送餐廳的時間才能回應,這可能需要一段時間。在此期間,您的技能可以使用逐步回應告知使用者它仍在工作中。這可以是語音或音樂。然而,您的技能只有 8 秒的時間才能超時。

您的程式碼可以使用 pypi aiohttp 函式存取網際網路。基本程式碼如下:

import aiohttp

async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

11.2 傳送逐步回應的步驟

要傳送逐步回應,請呼叫逐步回應 API 並傳送指令:

  • 取得 apiAccessToken 和 requestId,並構建您的 API 請求。
  • 呼叫逐步回應 API 並傳送指令(例如,VoicePlayer.Speak)。
  • 完成您的正常技能處理。
  • 一旦逐步回應呼叫完成,傳回您的完整技能回應物件。

11.3 逐步回應範例

我們將呼叫逐步回應,然後存取網站以查詢太空中的人數,但新增延遲以展示其工作原理。這是在以下程式碼中使用 get_progressive_response 函式執行的:

directive_request = SendDirectiveRequest(...)  # 取得存取權杖
directive_service_client.enqueue(directive_request)  # 傳送請求

程式

  1. 啟動新的 Python Alexa-Hosted 技能(我將其命名為「逐步回應」)。
  2. 我們將新增一個新的意圖,該意圖查詢目前在太空中的人數。新增一個新的意圖,命名為「getAstronauts」。
  3. 新增一些陳述式:
    • 「太空中有多少宇航員」
    • 「太空中的宇航員」
    • 「太空中的人」
    • 「太空中有多少人」
  4. 儲存並建置模型。
  5. 點選程式碼標籤並編輯 requirements.txt。新增:aiohttp == 3.8.0
  6. 新增匯入:

import aiohttp import asyncio import json import time

    與其他「from」匯入一起。

#### 內容解密:

上述程式碼使用 `aiohttp` 庫存取網際網路。`get` 函式是一個非同步函式,使用 `aiohttp.ClientSession` 來傳送 GET 請求並取得回應文字。

```mermaid
flowchart TD
    A[開始] --> B[取得 apiAccessToken  requestId]
    B --> C[構建 API 請求]
    C --> D[呼叫逐步回應 API]
    D --> E[傳送指令]
    E --> F[完成正常技能處理]
    F --> G[傳回完整技能回應物件]

圖表翻譯:

此流程圖展示了傳送逐步回應的步驟。首先,取得 apiAccessTokenrequestId,然後構建 API 請求。接下來,呼叫逐步回應 API 並傳送指令。完成正常技能處理後,傳回完整技能回應物件。

  sequenceDiagram
    participant 客戶端
    participant 伺服器
    Note over 客戶端,伺服器: 客戶端傳送請求
    客戶端->>伺服器: GET /astronauts
    伺服器->>客戶端: 回應文字
    Note over 客戶端,伺服器: 客戶端取得回應文字

圖表翻譯:

此序列圖展示了客戶端和伺服器之間的互動。客戶端傳送 GET 請求至 /astronauts 端點,伺服器回應相關文字。客戶端取得回應文字後,可以使用它來更新使用者介面或執行其他動作。

進階 Alexa 技能開發:使用 Progressive Response 和 Internet 連線

在這個章節中,我們將探討如何使用 Progressive Response 和 Internet 連線來增強 Alexa 技能的功能。首先,我們需要建立一個 CustomSkillBuilder 並設定 API client。

from ask_sdk_model.services.directive import (
    SendDirectiveRequest, Header, SpeakDirective)
from ask_sdk_core.skill_builder import CustomSkillBuilder
from ask_sdk_core.api_client import DefaultApiClient

sb = CustomSkillBuilder(api_client=DefaultApiClient())

接下來,我們需要定義兩個函式:getget_progressive_responseget 函式用於向指定 URL 傳送 GET 請求並傳回回應內容,而 get_progressive_response 函式則用於傳送 Progressive Response。

import aiohttp
import time

async def get(url):
    time.sleep(5)  # 只是為了提供長時間的回應
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

def get_progressive_response(handler_input):
    request_id_holder = handler_input.request_envelope.request.request_id
    directive_header = Header(request_id=request_id_holder)
    speech = SpeakDirective(speech="Ok - wait")
    directive_request = SendDirectiveRequest(
        header=directive_header, directive=speech)
    directive_service_client = handler_input.service_client_factory.get_directive_service()
    directive_service_client.enqueue(directive_request)
    return

現在,我們需要修改 LaunchRequest 的 speak_output 程式碼:

speak_output = "Hi, you can ask for how many people in space"

同時,我們也可以修改 “Hello world” 意圖的 speak_output 程式碼。

接下來,我們需要新增 GetAstronauts 意圖的程式碼。這個意圖將使用 get 函式向指定 URL 傳送 GET 請求並傳回回應內容。

class GetAstronautsIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # 意圖名稱必須與在 build 中定義的意圖名稱一致
        return ask_utils.is_intent_name("GetAstronautsIntent")(handler_input)

    def handle(self, handler_input):
        # 在這裡實作 GetAstronauts 意圖的邏輯
        get_progressive_response(handler_input)
        url = "https://api.example.com/astronauts"
        response = get(url)
        speak_output = f"There are {response} astronauts in space"
        return handler_input.response_builder.speak(speak_output).response

最後,別忘了註冊這個意圖處理器:

sb.add_request_handler(GetAstronautsIntentHandler())

透過這些步驟,你可以建立一個使用 Progressive Response 和 Internet 連線的 Alexa 技能,提供更豐富和動態的使用者經驗。

處理太空人數量 Intent 的 Handler

def can_handle(self, handler_input):
    # type: (HandlerInput) -> bool
    return ask_utils.is_intent_name("getAstronauts")(handler_input)

def handle(self, handler_input):
    speech_text = "OK - wait"
    try:
        get_progressive_response(handler_input)
        htmlData = asyncio.run(get(url))
        
        # 解析資料
        jsonResponse = json.loads(htmlData)
        
        people = jsonResponse.get('number')
        speech_text = "目前太空中有 " + str(people) + " 人"
    except Exception as ex:
        speech_text = "發生異常"
    
    return (
        handler_input.response_builder
       .speak(speech_text)
        #.ask(speech_text)
       .response
    )

將 Intent Handler 加入 Skill Builder

在程式碼的最後面,加入 Intent Handler 到 Skill Builder 中。如果您尚未這樣做,請註解掉提供的 sb = SkillBuilder()

# sb = SkillBuilder()
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(GetAstronautsIntentHandler())

儲存、佈署、測試和啟用開發

儲存您的程式碼,佈署到 Alexa 平臺,測試您的技能,並啟用開發模式。請注意,開發者主控臺可能無法正確執行進階回應,因此建議在實際裝置上測試。

從技術架構視角來看,本文深入探討了在 Alexa 技能開發中整合 Progressive Response 和網際網路連線的技巧。透過 aiohttp 函式庫實作非同步網路請求,技能得以在取得外部資源(例如太空人數量)的過程中,利用 Progressive Response 提供即時回饋,提升使用者經驗。然而,非同步操作和外部 API 的整合也帶來了額外的複雜性,例如錯誤處理和延遲管理。開發者需要妥善處理 try-except 區塊中的異常狀況,並考量網路請求的潛在延遲對技能效能的影響。展望未來,隨著 Alexa 技能生態的發展,更便捷的非同步程式設計框架和更穩定的 API 服務將進一步簡化此類別整合,並催生更多功能豐富的語音應用。對於追求更佳使用者經驗的開發者而言,掌握 Progressive Response 和網路連線技術至關重要。建議優先將這些技術應用於需要即時資訊更新和網路互動的核心功能,以最大化其價值。