無法載入檔案或組件 ‘log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=692fbea5521e1304’ 或其相依性的其中之一

最近同事詢問他們使用 Crytal Report Viewer 時,會發生 log4net 版本衝突的問題。如下圖所示,
CrystalReportViewer Runtime Error

如果是不同版本衝突的話,可以參考 如何讓不同 PublicKeyToken 的 DLL assemblyBinding 到可以用版本? 透過 assemblyBinding 的方式就可以解決。 但剛好我們的 AP 自已用的 log4net 版本也是 1.2.10.0,所以是 相同版本,不同的 PublickToken 的衝突。 詳細訊息如下,

沒有辦法解決 “log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821” 和 “log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=692fbea5521e1304” 之間的衝突。
任意選擇 “log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821”。

有查到 Error: Could not load log4net assembly 似乎是因為不同的平台(x86/x64) 所用的 log4net 會不同。
x86(32位元)用的 PublicToken 是 692fbea5521e1304,x64(64位元)或是 AnyCPU 用的 PublicToken 則是 1b44e1d426115821。
而我們 ap 平台是 x64 的,但 Crystal Report Viewer 卻使用了 x86(32位元) 的 log4net 。

嗯… 我想應該是那裡設定出了錯吧 …

跟同事討論後,同事又說只有在某台機器上才會出現那個錯誤,如果是透過 Browser 直接使用系統就正常,透過 VS.NET 啟動程式去 Debug 時,就會出現那個錯誤。
我想是因為如果透過 Browser 去使用系統的話,是連到 IIS ,而 IIS 設定的是 x64(64位元);如果透過 VS.NET 則是連到 IIS Express,預設它是使用 x86(32位元)。
於是筆者將 IIS 上的應用程式集區設定成 啟用 32 位元,再執行程式,則一樣會出現 log4net 的衝突狀況(  重現問題了 …^_^)。
所以就請同事在 VS.NET 中設定讓 IIS Express 使用 64 位元就沒問題了哦!
工具 -> 選項 -> (search textbox 中輸入 iis) , 並勾選 將 64 位元版本的 IIS Express 用於網站和專案(U) 選項勾起來就可以了哦! 如下,
將 64 位元版本的 IIS Express 用於網站和專案(U)

如果您是系統出了錯,請 Check 應用程式集區是否設定成了 「啟用32位元應用程式」哦!

分享 Call Language Understanding (LUIS) predictions api 的 3 種方式

在前一篇 「線上客服 + Chatbot = 智慧客服」我們透過 LUIS + Vital ESP(FAQ系統)  來打造智慧客服 Chatbot 。 在上線之前,需要先透過一堆問題去 Call LUIS  的 api ,來驗證我們在 LUIS  中設定的意圖及 Entities 是否正確。

即然是 Call LUIS  api 雲端服務,就會有費用問題,所以我們先來看一下 LUIS api 的價格說明,

luis.ai api 價格說明

  1. LUIS API – 免費: 每月最多可以 Call 10,000 次
  2. LUIS API – 基本: 每月最多可以 Call 10,000 次 (免費),超過後,每 Call 1,000 次約台幣 45.09 元
    詳細請參考 認知服務定價 – Language Understanding (LUIS)

目前我們可以在 LUIS 上手動測試意圖,但如果想要更有彈性的話,就要透過程式去呼叫它的 api ,所以以下我們就介紹 Call api 的 3 種方式,

Call luis.ai predictions api 的 3 種方式

在 LUIS predictions GET api 中有各語言的使用範例,以C#來看,使用非常方便,只要設定一些參數就可以取回 LUIS 解析的資料,本文將跟大家分享 Call 正式 api 的 GET/POST 方式,及 Call 測試 api 的方式,共 3 透方式。

  • call 正式 api 使用 GET,如下,
    static async Task<string> GetLuisResult(string reqString)
    {
    //最多是 500 個 ascii chars,中文我先取 230 個
    if (reqString.Length > 230)
    {
    reqString = reqString.Substring(0, 230);
    }
    var client = new HttpClient();
    var queryString = HttpUtility.ParseQueryString(string.Empty);
    const string appId = “你的appid”;
    const string subscriptionKey = “你的訂閱key”;
    // Request headers
    client.DefaultRequestHeaders.Add(“Ocp-Apim-Subscription-Key”, subscriptionKey);
    // Request parameters
    //如果回傳的內容中需要各 Intent 的話,請將 verbose 設定為 true
    queryString[“verbose”] = “true”;
    //queryString[“spellCheck”] = “{boolean}”;
    //queryString[“staging”] = “{boolean}”;
    //queryString[“bing-spell-check-subscription-key”] = “bing key”;
    //queryString[“log”] = “{boolean}”;
    var uri = $”https://eastasia.api.cognitive.microsoft.com/luis/v2.0/apps/{appId}?q=” + HttpUtility.UrlEncode(reqString)+ “&” + queryString;;
    var response = await client.GetAsync(uri);
    var result = await response.Content.ReadAsStringAsync();
    return result;
    }
  • call 正式 api 使用 POST (筆者試了老半天都試不出來,後來詢問 MS Herman 大大才知道,POST 的 body 前後要加 雙引號),如下,
    static async Task<string> GetLuisResultPost(string reqString)
    {
    //最多是 500 個 ascii chars,中文我先取 230 個
    if (reqString.Length > 230)
    {
    reqString = reqString.Substring(0, 230);
    }
    var client = new HttpClient();
    var queryString = HttpUtility.ParseQueryString(string.Empty);
    const string appId = “你的appid”;
    const string subscriptionKey = “你的訂閱key”;
    // Request headers
    client.DefaultRequestHeaders.Add(“Ocp-Apim-Subscription-Key”, subscriptionKey);
    // Request parameters
    //如果回傳的內容中需要各 Intent 的話,請將 verbose 設定為 true
    queryString[“verbose”] = “true”;
    //queryString[“spellCheck”] = “{boolean}”;
    //queryString[“staging”] = “{boolean}”;
    //queryString[“bing-spell-check-subscription-key”] = “bing key”;
    //queryString[“log”] = “{boolean}”;
    var uri = $”https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/{appId}?” + queryString;
    HttpResponseMessage response;
    // Request body,前後要加上 雙引號 哦!
    byte[] byteData = Encoding.UTF8.GetBytes(“\”” + reqString + “\””);
    using (var content = new ByteArrayContent(byteData))
    {
    response = await client.PostAsync(uri, content);
    var result = await response.Content.ReadAsStringAsync();
    return result;
    }
    }
  • 註: 在 LUIS 設定好,按下 Train 後,請記得要到 PUBLISH , 按下 「Publish to production slot」,這樣才會部到各區域去哦!

知道如何 Call Production 的 api 之後,如果我們想要像 線上客服 + BOT 之路 一文中,先用個 Excel 檔去試看看的話(目前 luis.ai 上 support 上傳 json , 整批測試),多試個幾次,免費的 quota 應該一下子就沒了吧! 如下所示,
超過免費可呼叫的Quota

那怎麼辦呢?

  • 1.到 Azure 那買個服務,然後再將訂閱的 Key 加到 luis.ai 之中,如下,從Azure買付費的方案
  • 2.使用 luis.ai 測試的 api ,您可以在測試時,錄一下 network ,如下,luis.ai 測試的 api

所以 Call luis.ai 測試 api 的方式如下,

static async Task<string> GetLuisResultTest(string reqString)
{
//最多是 500 個 ascii chars,中文我先取 230 個
if (reqString.Length > 230)
{
reqString = reqString.Substring(0, 230);
}
var client = new HttpClient();
var queryString = HttpUtility.ParseQueryString(string.Empty);
const string appId = “你的appid”;
const string subscriptionKey = “你的免費訂閱key”;
// Request headers
client.DefaultRequestHeaders.Add(“Ocp-Apim-Subscription-Key”, subscriptionKey);
var uri = $”https://westus.api.cognitive.microsoft.com/luis/webapi/v2.0/apps/{appId}/versions/0.1/predict?example=” + HttpUtility.UrlEncode(reqString);
var response = await client.GetAsync(uri);
var result = await response.Content.ReadAsStringAsync();
return result;
}

這個測試的 api 跟正式機的差別除了內容不太相同,它取出的 Entity 中間會有空白,所以再使用上需要將空白清掉哦! 因為它是測試用的,所以在整批 Call 的時候,有時會 block 一段時間,我的做法是如果出錯就停個 15 秒,讓它再重試一次,後來就可以正常再 Call 了哦!

正式 api 的結果如下 ( verbose 參數為 true, 內容才會有 intents ),

{
“query”: “下載下來可以用了,可是什麼是網址識別”,
“topScoringIntent”: {
“intent”: “FAQ”,
“score”: 1.0
},
“intents”: [
{
“intent”: “FAQ”,
“score”: 1.0
},
{
“intent”: “None”,
“score”: 0.05330094
}
],
“entities”: [
{
“entity”: “網址”,
“type”: “Keyword”,
“startIndex”: 14,
“endIndex”: 15,
“score”: 0.9081365
},
{
“entity”: “識別”,
“type”: “Keyword”,
“startIndex”: 16,
“endIndex”: 17,
“score”: 0.9102927
},
{
“entity”: “下載”,
“type”: “Action”,
“startIndex”: 0,
“endIndex”: 1,
“score”: 0.9999258
}
]
}

測試 api 的結果如下( entityPredictions 中的 phrase 字之間會有空白哦!!! ),

{
    "id": null,
    "text": "下載下來可以用了,可是什麼是網址識別",
    "alteredText": "下載下來可以用了,可是什麼是網址識別",
    "tokenizedText": [
        "下"
    ],
    "intentPredictions": [
        {
            "id": "9e70ed0a-561c-45f4-b8a4-2439de42acb1",
            "name": "FAQ",
            "score": 1.0
        },
        {
            "id": "8d898fa2-667f-471d-b87f-309011627b5d",
            "name": "None",
            "score": 0.05
        }
    ],
    "entityPredictions": [
        {
            "id": "cb403a63-347c-448c-9b08-3e48d6ac0c61",
            "entityName": "Keyword",
            "startTokenIndex": 14,
            "endTokenIndex": 15,
            "phrase": "網 址",
            "entityType": 1
        },
        {
            "id": "cb403a63-347c-448c-9b08-3e48d6ac0c61",
            "entityName": "Keyword",
            "startTokenIndex": 16,
            "endTokenIndex": 17,
            "phrase": "識 別",
            "entityType": 1
        },
        {
            "id": "1771cbab-fb7b-4a83-b46d-4c085ba88341",
            "entityName": "Action",
            "startTokenIndex": 0,
            "endTokenIndex": 1,
            "phrase": "下 載",
            "entityType": 1
        }
    ],
    "tokenMetadata": null
}

預設 call 正式 api 時,會 log 查詢的資料,最近幾筆資料可以從 「 Review endpoint utterances 」 看到,如下
Review endpoint utterances

那如果我需要的是全部的 Log 呢? 可以到「 My apps 」,在那個 app 右邊的 … ,選取「 Export Endpoint Log 」,就可以了哦,如下,
Export Endpoint Logs

線上客服 + Chatbot = 智慧客服

相信很多產品都會有線上客服系統及線上 FAQ 系統,提供給使用者線上詢問或查詢來排除操作系統所發生的問題。
如果加上 FAQ Chatbot 可以服務更多的使用者(當線上客服人員下班時),也可以讓線上客服人員可以服務復雜的問題。
我們要如何加上 FAQ Chatbot 呢? 讓我們繼續看下去 …

繼續閱讀 “線上客服 + Chatbot = 智慧客服”