一種在智能對話中實現(xiàn)上下文功能的方法

原文地址:http://blog.csdn.net/speeds3/article/details/78302774

智能對話中有一個經(jīng)典的場景:

Q:上海的天氣
A:上海的天氣是……
Q:那北京的呢
A:北京的天氣是……

第二個問句是一個特殊的問句宋梧,它的語義和前一句關聯(lián)是尖,但單獨說它沒有明確的意圖翁狐。目前的olami開放平臺提供的IDS模塊(應用管理->配置模塊->對話系統(tǒng)模塊)自身可支持上下文,但對平臺用戶自己開發(fā)的NLI模塊卻沒有提供直接的支持正驻。不過我們可以通過一些辦法實現(xiàn)這種功能。下面就介紹一下解決方案炬搭。

分析

這種上下文的語句有兩種類型漏益,一種是對前文的語義進行一部分的修改,另一種是補充前文語義缺失的部分粘舟。修改前文語義的語句一般都是問更多的信息熔脂,所以這里把處理這種語句的grammar稱為more grammar;補充前文語義柑肴,一般都是補充osl語言描述的“slot”信息霞揉,所以處理它的grammar在本文叫做slot grammar。前面的例子就是修改前文語義嘉抒,下例是補充前文語義的情況:

Q:查天氣
A:你要查哪里的天氣
Q:上海
A:上海的天氣是……

大多數(shù)時候more grammar可以很好的解決問題零聚,但有些情況就不適用。比如:

Q:買機票
A:請問你要從哪里出發(fā)
Q:上海                    ⑴
A:請問你要去哪里
Q:北京                    ⑵
A:上海到北京的機票

⑴和⑵兩句的類型完全相同些侍,但slot名稱不同隶症,只能用同一句grammar來抓取。如果用more grammar來處理岗宣,拿到語義后還要糾正slot的名稱蚂会,這和一般的more grammar有一定的區(qū)別。所以將其設定為特殊的more grammar —— slot grammar耗式,在這當中進行設置slot的操作胁住。

利用前面定好的規(guī)則,我們希望實現(xiàn)如下場景:

Q:查天氣
A:你要查哪里的天氣
Q:上海
A:上海的天氣是……
Q:那北京的呢刊咳?
A:北京的天氣是……
Q:南京的呢
A:南京的天氣是……

另外彪见,如果有多個應用,不應該出現(xiàn)一個模塊的more處理了另一個模塊上文的情況:

Q:導航去南京
A:到南京的路線……
Q:那北京的呢娱挨?    // 這是天氣的more
A:北京的天氣是……  // 不應該出現(xiàn)這種情況

最后余指,兩個應用模塊共同的more語法要能被正確的模塊處理:

Q:查天氣
A:你要查哪里的天氣
Q:上海
A:上海的天氣是……
Q:導航
A:你要去哪里
Q:上海
Q:到上海的路線是……

總結(jié)起來,我們的目標有:

  1. 模塊的more可以繼承上文的部分語義組合成一個完整的語義跷坝;

  2. 一個模塊的more不應該繼承另一個模塊的上文酵镜;

  3. 相同的句式(例如整句是一個地點)能被正確的模塊處理碉碉。

方案

計劃用兩個功能模塊和一個公共模塊來演示上下文的功能。兩個功能模塊一個是天氣淮韭,功能是查指定城市的天氣垢粮;另一個是導航,考慮到簡化流程靠粪,只接收目的地的設置蜡吧,出發(fā)地理解為當前地點。公共模塊用來處理一些兩個模塊都要支持的more語句庇配,例如“北京”這種斩跌。

實現(xiàn)

語法平臺的操作

首先在平臺上建三個新的模塊,分別是weather(天氣)捞慌,navi(導航)和common(公共模塊)耀鸦。

然后,由于三個模塊都需要一個地點的slot啸澡,所以每個模塊都添加上:

增加location slot

高級設置中設置驗證類別袖订,之后應用時系統(tǒng)會自動驗證抓到的內(nèi)容是不是這個類型。同時引用類型設置為地點嗅虏,以后可以用”那里“來引用上文的地點:

設置slot類型

接下來依次添加需要的grammar洛姑,注意要覆蓋上文需要實現(xiàn)場景中的所有情況。普通的grammar和平常一樣寫皮服,more的grammar將modifier設置為more:

weather

不帶location:[我要|幫我|我想]查[[一]下]天氣<{@=query}>

帶location的完整grammar:(<location>|(那里|那兒)<{location@=last}>)的天氣[怎[么]樣]<{@=query}>

more grammar:([那]<location>的[呢|怎么樣|如何]|查<location>[的]|<location>(呢|怎么樣|如何))<{@=more}>

navi

(幫我|我要)導航<{@=navi}>

導航(去|到)(<location>|(那里|那兒)<{location@=last}>)<{@=navi}>

([那](到|去)<location>[的]呢|[我要|我想]去<location>)<{@=more}>

common

抓整句作為location slot:(<location>|(那兒|那里)<{location@=last}>)<{@=slot}>

客戶端的內(nèi)容

本文用swt來實現(xiàn)客戶端楞艾。導入olami java client sdk之后就能使用其提供的nli接口了。由于olami服務器目前只能提供一句話的語義解析龄广,而不能指定上下文硫眯,所以客戶端需要保存一些狀態(tài)。這些狀態(tài)包括:

  1. 處理上一回合語義的模塊择同。用于判斷本回合的more語義是否要進行處理两入。

  2. 上一回合模塊是否處于等待slot輸入的狀態(tài),以及需要的slot名稱敲才。這樣同類型不同名稱的slot不會被混淆裹纳。

  3. 上一回合的語義。目的是和本回合的more或slot語義進行合并紧武,得到完整的語義剃氧。

和nli接口的交互封裝在NliService類中。另外我們定義的每一個應用模塊都有一個相應的app類處理其語義阻星。這些類由NliService統(tǒng)一管理她我。NliService收到服務器返回語義后,會做一些簡單的處理,如果是公共模塊的語義就交給正確的上文app番舆,其他的語義按照它的app名稱進行分配。下面是NliService的代碼:

package moredemo;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

import ai.olami.cloudService.APIConfiguration;
import ai.olami.cloudService.APIResponse;
import ai.olami.cloudService.APIResponseData;
import ai.olami.cloudService.TextRecognizer;
import ai.olami.nli.NLIResult;
import ai.olami.nli.Semantic;
import app.App;
import app.NaviApp;
import app.WeatherApp;

public class NliService {

    private static final String appkey = "85d85d62d2b3450c97c2f547c7d8de48";
    private static final String appSercert = "57467df93d4c4f65a176ab06cca660ee";
    private HashMap<String, App> appService = new HashMap<>();

    private static String lastapp = null;

    private TextRecognizer recognizer = null;

    public void init() {
        APIConfiguration config = new APIConfiguration(appkey, appSercert, APIConfiguration.LOCALIZE_OPTION_SIMPLIFIED_CHINESE);
        recognizer = new TextRecognizer(config);

        // 初始化app服務,key與nli系統(tǒng)中的app名稱對應矾踱,方便使用恨狈。
        appService.put("weather", new WeatherApp());
        appService.put("navi", new NaviApp());
    }

  // 通過用戶輸入得到處理結(jié)果
    public String process(String input) {
        String result = null;
        if (recognizer != null) {
            try {
                APIResponse response = recognizer.requestNLI(input);
                result = handleResponse(response);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    private String handleResponse(APIResponse response) {
        if (response.hasData()) {
            APIResponseData data = response.getData();
            if (data.hasNLIResults()) {
                NLIResult nliResult = data.getNLIResults()[0];
                String content = nliResult.getDescObject().getReplyAnswer();
                if (nliResult.getDescObject().getStatus() == 0 && nliResult.hasSemantics()) {
                    Semantic sem = nliResult.getSemantics()[0];
                    String appname = sem.getAppModule();
                    if (appname.equals("common")) {
            // 處理公共語義,交給lastapp處理
                        if (lastapp != null) {
                            App app = appService.get(lastapp);
                            if ("slot".equals(app.status())) {
                                return app.getResult(sem);
                            } else {
                                return "抱歉呛讲,你說的我還不懂";
                            }
                        }
                    } else {
                        App app = appService.get(appname);
                        if (app != null) {
                            String result = app.getResult(sem);
                            lastapp = appname;
                            return result;
                        } else {
                            return "錯誤:未知服務類型";
                        }
                    }
                } else {
                    return content;
                }
            }
        }
        return "抱歉禾怠,你說的我還不懂";
    }

    public static String getLastApp () {
        return lastapp;
    }
}

模塊app中保存了上一回合的語義,以及一個回合結(jié)束之后app的狀態(tài)贝搁。通過語義中的特殊標記(“more”或”slot)和普通的modifier吗氏,與slot信息一起得到結(jié)果。由于沒有數(shù)據(jù)源雷逆,所以結(jié)果都只用一句話來代替弦讽。為了清晰的展示返回結(jié)果是由哪個app處理的,代碼中在每個回答前加上了【app名稱】的前綴膀哲。下面是weatherapp的代碼:

package app;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ai.olami.nli.Semantic;
import ai.olami.nli.slot.Slot;
import moredemo.NliService;

public class WeatherApp implements App {

    private List<String> modifiers = new ArrayList<>();
    private List<String> lastmods = new ArrayList<>();

    private Map<String, Slot> slots = new HashMap<>();

    // 保存當前的app狀態(tài)往产,目前有"slot"一種可能,表示拿到的語義缺少slot信息某宪,需要用戶繼續(xù)輸入仿村。
    // 此時slotgrammar才會生效
    private String status = null;

    private String op = null;

    @Override
    public String getResult(Semantic sem) {
        // 保存前一個回合的modifier,便于處理more和slot的情況
        List<String> temp = lastmods;
        lastmods = modifiers;
        modifiers = temp;
        modifiers.clear();

        // 設置新的modifier
        for (String mod : sem.getGlobalModifiers()) {
            modifiers.add(mod);
        }

        // 目前的功能只需要兴喂,并且語法中的語句都只有一個modifier蔼囊,擴展的話需另作處理
        if (modifiers.size() == 1) {
            String modifier = modifiers.get(0);
            if (modifier.equals("more")) {
                // 通過nliservice中的lastapp是否是這個app判斷是否要解析此more語義
                if (!"weather".equals(NliService.getLastApp())) {
                    return "【weather】抱歉,我不明白你的意思";
                } else {
                    setSlots(sem.getSlots());
                    return result();
                }
            } else if (modifier.equals("slot")) {
                // lastapp是此app且此app處于“slot”狀態(tài)時才處理此slot語義
                if (!"weather".equals(NliService.getLastApp())) {
                    return "【weather】你給我的信息太少了";
                } else if (!"slot".equals(status)) {
                    return "【weather】我不明白你跟我說的是什么";
                } else {
                    setSlots(sem.getSlots());
                    return result();
                }
            } else {
                // app的一般流程衣迷,這里表示modifier是“query”的情況
                reset();
                op = modifier;
                setSlots(sem.getSlots());
                return result();
            }
        } else {
            return "【weather】我的神經(jīng)發(fā)生了混亂";
        }
    }

    // 通過保存的op和slots的組合確定當前執(zhí)行哪種操作畏鼓,并設置對應的app狀態(tài)
    private String result() {
        if (op.equals("query")) {
            if (slots.containsKey("location")) {
                String location = slots.get("location").getValue();
                status = null;
                return "【顯示" + location + "的天氣】";
            } else {
                status = "slot";
                return "【weather】你要查哪里的天氣呢?";
            }
        } else {
            status = null;
            return "【weather】我好像出了點問題蘑险?";
        }
    }

    // 把參數(shù)slots中的slot信息保存起來
    private void setSlots(Slot[] slots) {
        for (Slot slot : slots) {
            this.slots.put(slot.getName(), slot);
        }
    }

    private void reset() {
        slots.clear();
        op = null;
    }

    @Override
    public String status() {
        return status;
    }

}

navi的代碼和它大同小異滴肿。

運行結(jié)果

加上一個簡單的界面,就可以測試輸出結(jié)果佃迄。

  1. 可以進行追問:
天氣的追問
  1. 天氣的more grammar不會處理導航的上文:
天氣more與導航上文
  1. 相同的句式不會混淆:
相同句式

這樣前文期望的目標基本都實現(xiàn)了泼差。

總結(jié)與展望

通過給語法做標記,再在客戶端做一些工作呵俏,我們可以實現(xiàn)一些基本的上下文處理堆缘。這已經(jīng)可以滿足相當一部分應用對上下文理解的需求。當然由于不是在語法匹配上支持的上下文普碎,這里的辦法有一些局限性吼肥,當應用數(shù)量和復雜度提高時很可能會出現(xiàn)問題。希望olami平臺能盡快在服務端增加上下文的功能,這樣使用起來就可以更加簡便缀皱,應用的處理邏輯也可以更加簡明和清晰斗这。

源碼地址:https://gitee.com/stdioh_cn/moredemo.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市啤斗,隨后出現(xiàn)的幾起案子表箭,更是在濱河造成了極大的恐慌,老刑警劉巖钮莲,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件免钻,死亡現(xiàn)場離奇詭異,居然都是意外死亡崔拥,警方通過查閱死者的電腦和手機极舔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來链瓦,“玉大人拆魏,你說我怎么就攤上這事≡杓ǎ” “怎么了唯灵?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵丁稀,是天一觀的道長萎庭。 經(jīng)常有香客問我告材,道長,這世上最難降的妖魔是什么步鉴? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任揪胃,我火速辦了婚禮,結(jié)果婚禮上氛琢,老公的妹妹穿的比我還像新娘喊递。我一直安慰自己,他們只是感情好阳似,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布骚勘。 她就那樣靜靜地躺著,像睡著了一般撮奏。 火紅的嫁衣襯著肌膚如雪俏讹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天畜吊,我揣著相機與錄音泽疆,去河邊找鬼。 笑死玲献,一個胖子當著我的面吹牛殉疼,可吹牛的內(nèi)容都是我干的梯浪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瓢娜,長吁一口氣:“原來是場噩夢啊……” “哼挂洛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恋腕,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抹锄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后荠藤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡获高,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年哈肖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片念秧。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡淤井,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摊趾,到底是詐尸還是另有隱情币狠,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布砾层,位于F島的核電站漩绵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肛炮。R本人自食惡果不足惜止吐,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侨糟。 院中可真熱鬧碍扔,春花似錦、人聲如沸秕重。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溶耘。三九已至二拐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汰具,已是汗流浹背卓鹿。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留留荔,地道東北人吟孙。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓澜倦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杰妓。 傳聞我的和親對象是個殘疾皇子藻治,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容