面向接口/協(xié)議?看DuerOS的技能開發(fā)
一般地,開發(fā)一個對話系統(tǒng)或者機器人問答系統(tǒng)會涉及諸多領(lǐng)域的技術(shù),除了硬件系統(tǒng)之外,還包括語言識別,自然語言處理/識別,知識圖譜的搭建,自然語言生成及TTS播報等等,這對于企業(yè)及開發(fā)者個人而言,幾乎是難以完成的任務(wù)。
有幸的是,對話式AI操作系統(tǒng)例如DuerOS的誕生,使我們可以直面業(yè)務(wù)邏輯,相對輕松地完成語音類服務(wù)的開發(fā)與實現(xiàn)。在過去的研發(fā)中,經(jīng)常使用的往往是面向接口的設(shè)計方法,那么,面向?qū)υ捠讲僮飨到y(tǒng)的開發(fā),是如何實現(xiàn)的呢?
接口
在OO的時候,通常會講到SOLID原則:
- 單一職責
- 開閉原則
- 里氏替換
- 接口隔離
- 依賴反轉(zhuǎn)
其中接口隔離是其中的一項重要原則。接口的概念外延較廣泛,wiki上的解釋是這樣的:
- An interface is a shared boundary across which two or more separate components of a computer system exchange information. The exchange can be between software, computer hardware, peripheral devices, humans, and combinations of these.
接口大體上從載體上分為硬件接口和軟件接口,從交互上可以分為人機接口和機-機(M2M)接口。軟件中的接口為組件可以提供了常量、數(shù)據(jù)類型、過程類型、異常規(guī)范和方法簽名等。有時,公共變量也被定義為接口的一部分。接口是一種很高的抽象,很多時候都會涉及到接口,因此要注意上下文和具體的應(yīng)用場景。
在面向?qū)ο蟮木幊陶Z言中,尤其是不支持多繼承的編程語言,往往有著特殊的具體含義,interface 一詞成為了關(guān)鍵字,例如Java。Java接口中所用的方法一般都是抽象方法和常量,但是在Java8及以后的版本中,開始支持靜態(tài)成員以及默認的函數(shù)實現(xiàn),例如:
- public interface MyTestInterface {
- int MAX_STORAGE_SIZE = 1024;
- default void doTest() {
- System.out.println("Hello, Test!");
- }
- }
盡管如此,作為一個從Java 1.2 開始使用的老程序員而言,Java的發(fā)展還是略顯遲緩。
關(guān)于接口描述語言(IDL),大約最早出現(xiàn)在是CORBA的規(guī)范中,自己在1999年初次使用IDL的時候,非常感嘆它的神奇,在當時簡直就是跨平臺開發(fā)的基礎(chǔ)。實際上,OMG IDL不是作為程序設(shè)計語言體現(xiàn)在CORBA體系結(jié)構(gòu)中的,而是用來描述產(chǎn)生對象調(diào)用請求的客戶對象和服務(wù)對象之間的接口的語言。IDL文件描述數(shù)據(jù)類型和方法框架,而服務(wù)對象則為一個指定的對象實現(xiàn)提供上述數(shù)據(jù)和方法。一般地,IDL文件描述了服務(wù)器提供的服務(wù)功能,客戶機可以根據(jù)該接口文件描述的方法向服務(wù)器提出業(yè)務(wù)請求。在大多數(shù)CORBA產(chǎn)品中都提供了IDL到相關(guān)編程語言的編譯器。 在Android的應(yīng)用開發(fā)中,AIDL實際上就是IDL的一個領(lǐng)域應(yīng)用而已。
那么接口和協(xié)議有什么聯(lián)系和區(qū)別呢?
協(xié)議
協(xié)議,一般是指通信協(xié)議,例如大家熟知的互聯(lián)網(wǎng)協(xié)議——TCP/IP。
在編程語言中,協(xié)議指示的是調(diào)用方和目標對象之間的交互鏈。一般描述為:
- 對象可以理解的消息。
- 這些消息可能附帶的參數(shù)。
- 這些消息返回的結(jié)果類型。
- 盡管修改了對象的狀態(tài),但仍然保留的不變量。
- 需要由客戶端處理到對象的異常情況。
- 對于通信而言,還包括方法的調(diào)用序列和決策點,例如在UML交互圖中的表示:通信圖、序列圖、交互概述圖/活動圖等。
在面向?qū)ο蟮木幊陶Z言中,同樣地,也有把protocol作為關(guān)鍵字的編程語言,例如Objective-C。在Objective-C中,同樣不支持多繼承,即不允許一個類有多個父類,于是Objective-C提供了類似的實現(xiàn)方法,也就是協(xié)議。協(xié)議有點類似于Java里的接口,不同點就是在協(xié)議里,可以提供可選的方法,不要求全部繼承。例如:
- @protocol myprotocolName
- - (void)requiredMethod;
- @optional
- - (void)anOptionalMethod1;
- - (void)anOptionalMethodn;
- - (void)otherOptionalMethod;
- @required
- - (void)anotherRequiredMethod;
- @end
Objective-C的協(xié)議包括正式協(xié)議和非正式協(xié)議,這里不再贅述。
協(xié)議和接口在很多時候是交疊的,但視角不同,接口面向的是實體對象,而協(xié)議聚焦在交互上。本質(zhì)上,任何的協(xié)議都是有字典和語法兩部分組成,從而形成通信上的共識。
對程序員而言,往往更關(guān)注傳輸協(xié)議和應(yīng)用協(xié)議。傳輸協(xié)議主要完成數(shù)據(jù)裝配,多路復(fù)用,差錯檢測和流量控制。應(yīng)用協(xié)議才是具體業(yè)務(wù)的數(shù)據(jù)和狀態(tài)描述及內(nèi)容交互。
那么,對話系統(tǒng)的應(yīng)用開發(fā)是如何面向接口和協(xié)議的呢?
對話系統(tǒng)的應(yīng)用技能開發(fā)
DuerOS 是對話式人工智能交互系統(tǒng),簡稱對話式AI系統(tǒng)。應(yīng)用DuerOS的典型產(chǎn)品之一就是智能音箱。
談到在智能音箱上的應(yīng)用開發(fā),往往讓人聯(lián)想到使用音箱提供的SDK,例如Android SDK 或者 Linux的SDK,然后將應(yīng)用下載到音箱上。這種理解還停留在多年前的APP開發(fā)階段,對人工智能操作系統(tǒng)存在著極大的誤區(qū),可以參見感知人工智能操作系統(tǒng)。
在基于DuerOS的設(shè)備例如智能音箱上,應(yīng)用的開發(fā)和Web服務(wù)的開發(fā)沒什么區(qū)別。 簡單地,可以把智能音箱理解成瀏覽器,只是原來的鼠標點擊和鍵盤輸入換成了語音交互即可。在智能設(shè)備上開發(fā)應(yīng)用是通過DuerOS Bot Platform(簡稱DBP)實現(xiàn)的,交互協(xié)議稱為DuerOS Conversation Service(簡稱DCS),包括了智能終端與DuerOS之間的協(xié)議,和DuerOS與應(yīng)用服務(wù)(Bot)之間的協(xié)議。
作為開發(fā)者, 我們主要實現(xiàn)DuerOS與應(yīng)用服務(wù)(Bot)之間的協(xié)議,方便起見,把它也叫做DBP協(xié)議。
DBP 協(xié)議淺析
DBP協(xié)議把HTTP/HTTPS 作為傳輸協(xié)議,關(guān)于http的相關(guān)內(nèi)容可以參見溫故知新,HTTP/2。 DBP協(xié)議中應(yīng)用協(xié)議的數(shù)據(jù)是通過JSON來表述。
請求與響應(yīng)
智能音箱上的應(yīng)用實際上就是對DBP協(xié)議中的請求作出響應(yīng)的Web服務(wù)。 一個典型的request 結(jié)構(gòu)示例如下:
- {
- "version": "2.0",
- "session": {
- },
- "context": {
- "System": {
- "user": {
- },
- "application": {
- },
- "device": {
- "deviceId": "{{STRING}}",
- "supportedInterfaces": {
- "VoiceInput": {},
- "VoiceOutput": {},
- "AudioPlayer": {},
- "VideoPlayer": {},
- "Display": {}
- }
- }
- },
- "AudioPlayer": {},
- "VideoPlayer": {}
- },
- "request": {}
- }
session表示用戶會話信息,一次session過程是從開始用戶調(diào)起技能到結(jié)束,表示用戶與技能的一次會話。
Context描述了設(shè)備端的狀態(tài)數(shù)據(jù),能力配置參數(shù)以及用戶相關(guān)信息,包括System即系統(tǒng)參數(shù)和播放器的狀態(tài)。
request 才是具體的payload, 是經(jīng)DuerOS 經(jīng)過AI處理后的用戶請求。
Response 是開發(fā)者實現(xiàn)的主要內(nèi)容, 結(jié)構(gòu)示例如下:
- {
- "version" : "2.0",
- "context" : {
- "intent" : {
- "name" : "{{STRING}}",
- "slots" : {
- "{{STRING}}" : {
- }
- }
- },
- "expectResponse" : [
- {}
- ],
- },
- "session" : {
- "attributes" : {
- "{{STRING}}": "{{STRING}}"
- },
- },
- "response" : {
- "outputSpeech" : {
- "type" : "{{STRING}}",
- "text" : "{{STRING}}",
- "ssml" : "{{STRING}}",
- },
- "reprompt" : {
- "outputSpeech" : {
- "type" : "{{STRING}}",
- "text" : "{{STRING}}",
- "ssml" : "{{STRING}}",
- }
- },
- "card" : {}
- "directives" : [],
- "expectSpeech": {{BOOLEAN}},
- "shouldEndSession" : {{BOOLEAN}}
- }
- }
Context用于反饋給DuerOS的intent結(jié)果;Intent是技能應(yīng)用認為對本次query更加合理的意圖解析,expectResponse用于推測用戶可能的回復(fù),DuerOS會在后續(xù)的query中優(yōu)化意圖解析模型。關(guān)于意圖和槽位,可以參見感知自然語言理解(NLU)。
session存儲了在DuerOS 會話的屬性數(shù)據(jù),如果本次session不結(jié)束,那么在下一個發(fā)送給技能的請求中,在session.attributes字段會攜帶這些屬性給到至技能應(yīng)用,相當于數(shù)據(jù)到終端上繞了一圈。
response是技能應(yīng)用回復(fù)給DuerOS的payload,包括語音播報的內(nèi)容和風格,展現(xiàn)輸出和技能指令等。
一對Request/Response構(gòu)成了DBP協(xié)議的主體。
事件和指令
事件和指令是DBP協(xié)議中的重要消息形式,從智能終端和DuerOS發(fā)往我們開發(fā)的技能應(yīng)用的消息稱為事件,從技能應(yīng)應(yīng)用發(fā)往智能終端和DuerOS的消息稱為指令。
在用戶通過語音輸入后,DuerOS會對語音請求進行識別和理解,并將理解結(jié)果發(fā)送給技能應(yīng)用。有三個事件相當于定義了應(yīng)用技能的生命周期:
- LaunchRequest:代表開啟技能應(yīng)用
- IntentRequest:相當于進入技能應(yīng)用的消息處理循環(huán)
- SessionEndedRequest:相當于被動結(jié)束技能應(yīng)用,當然,技能應(yīng)用可以主動退出。
智能終端上的處理狀態(tài)事件是通過DuerOS透傳給技能應(yīng)用的,主要包括音頻播放器audioplayer的音頻播放事件集和視頻播放器videoplayer的視頻播放事件集,對于有屏終端而言,還包括form事件,即用戶屏幕上的點擊事件等。
具體地,音頻事件包括:
- AudioPlayer.PlaybackStarted事件
- AudioPlayer.PlaybackStopped事件
- AudioPlayer.PlaybackFinished事件
- AudioPlayer.PlaybackNearlyFinished事件
- AudioPlayer.ProgressReportIntervalElapsed事件
視頻播放事件包括:
- VideoPlayer.PlaybackStarted事件
- VideoPlayer.PlaybackStopped事件
- VideoPlayer.PlaybackFinished事件
- VideoPlayer.PlaybackNearlyFinished事件
- VideoPlayer.ProgressReportIntervalElapsed事件
- VideoPlayer.ProgressReportDelayElapsed事件
- VideoPlayer.PlaybackStutterStarted事件
- VideoPlayer.PlaybackStutterFinished事件
- VideoPlayer.PlaybackPaused事件
- VideoPlayer.PlaybackResumed事件
- VideoPlayer.PlaybackQueueCleared事件
- PlaybackScheduledStopReached事件
Form事件包括顯示控件的點擊事件,主要包括Form.ButtonClicked和Form.RadioButtonClicked事件等。為了方便開發(fā),增強展示形式的表現(xiàn)力,DBP協(xié)議還提供了展現(xiàn)卡片和展現(xiàn)模版,對于的事件包括Display.ElementSelected事件和Display.ButtonClicked事件。
對這些事件的處理,技能應(yīng)用相當于得到了用戶、智能設(shè)備狀態(tài)和DuerOS數(shù)據(jù)的輸入,然后通過指令(directive)的形式完成交互。
在對話過程中,技能應(yīng)用可以發(fā)出的指令有Dialog.ElicitSlot, Dialog.ConfirmSlot,Dialog.ConfirmIntent和Dialog.Delegate,分別用于槽位的填充和確認,意圖的確認,以及通知DuerOS來處理NLU。
在音/視頻播放中,技能應(yīng)用可以發(fā)出的指令有AudioPlayer.Play和AudioPlayer.Stop 來通知音頻播放器開始和停止播放,VideoPlayer.Play和 VideoPlayer.Stop來通知視頻播放器開始和停止播放 VideoPlayer.ClearQueue用來清除播放的資源隊列。
在有屏的智能設(shè)備上,F(xiàn)orm的事件處理可以理解成一般的HTML表單處理,而展示模版的指令只有一個Hint,用于展現(xiàn)技能應(yīng)用的引導(dǎo)詞。技能引導(dǎo)詞是技能展現(xiàn)的提示信息,引導(dǎo)用戶與技能應(yīng)用進行交互。每個技能都可以設(shè)計引導(dǎo)詞,讓用戶更快速的使用技能。
由此可見,DBP協(xié)議的主要部分相對簡單,清晰明了。
小結(jié)
即使理解了協(xié)議的格式和內(nèi)容,自己實現(xiàn)整個DBP協(xié)議也是需要一定的工作量的。所幸的是,DBP平臺提供了多種編程語言的SDK,對DBP協(xié)議的實現(xiàn)進行了封裝,基于這些SDK(Java/JavaScript/Go/PHP/Python),我們的開發(fā)變得相對簡單,使我們可以聚焦于應(yīng)用的業(yè)務(wù)邏輯。
DBP平臺還提供了大量的技能開發(fā)模版,相當于簡化的開發(fā)框架,使對話式AI系統(tǒng)的技能應(yīng)用開發(fā)更為簡單。另外,DBP平臺提供的小技能開發(fā),更是無需編程即可實現(xiàn)一些簡單技能應(yīng)用的開發(fā)。
參考資料
https://dueros.baidu.com/dbp
Pugh Ken,“Interface-Oriented Design”,Pragmatic Bookshelf,2006
威廉·斯托林斯,《數(shù)據(jù)與計算機通信(第十版)》,電子工業(yè)出版社,2015
【本文來自51CTO專欄作者“老曹”的原創(chuàng)文章,作者微信公眾號:喔家ArchiSelf,id:wrieless-com】