身為程序員,一直把「增進人類全體之生活」當成使命,沒能力為人類產生貢獻,能「增進家人之生活」也好。

長輩請了外籍看護,國語跟英文只到勉強溝通,最後 LINE 加翻譯軟體竟是目前效果最好的溝通方式。結果我做了件「用大砲打小鳥」的事 - 用 ASP.NET Core 寫 LINE Bot 接 ChatGPT 在 LINE 群組幫忙翻譯,專案代號就命名為「翻譯鳳梨酥 」。

群組回話 LINE Bot串 ChatGPT API 之前玩過了,這回只是把兩個功能摻在一起,主程式不用一小時寫完,小有快刀斬亂麻的暢快。
(所以說,平日老為賦新辭強說愁,四處找雞毛蒜皮的無聊題目練習,看似沒意義,但說不準能在某個關鍵時刻派上用場,「你練過的功不會背叛你」再添一例。)

使用方式是將「翻譯鳳梨酥」加入群組偷聽對話,看到 # 語系標記它就會跳出來翻譯,貼心設計是翻譯內容一併附上英文供對照,方便核結果正確性。

初始版本有點小問題,像是 "早上七點幫阿公量血壓" 被翻成 "I measured my grandpa's blood pressure at seven in the morning",英文對照發揮作用糾出問題。

加上 "請" 字強調指示是個 Workaround,但時時需要注意措辭是小看了 ChatGPT 的能耐,也讓開發者面子掛不住,這類問題一般可以透過調整 ChatGPT 提示改善。我的原本的初始化提示是:

你是一名多國語言翻譯,請將以下文字內容分別翻譯成英文以及{指定言語},用語需口語化及生活化。

修改成:

你是一名語言翻譯,負責協助僱主與外籍看護溝通,主要工作為將僱主的中文指令翻成外語,將外語翻成中文,用語需口語化及生活化。現在請將以下文字內容分別翻譯成英文以及{指定言語}。

Prompt 調整後,效果明顯改善,目前沒再看到明顯錯誤。

簡單說下程式寫法。ChatGPT 部分我是直接拿 ChatGPT 聊天程式練習 的 ChatGptService 來用,每次當成全新對談,不必帶入先前內容,省 Token (ChatGPT API 以 Token 計費)也加快處理速度。LINE Bot API 需一次送出完整內容,故也用不到 Streaming 技巧。

餘下的全部程式邏輯我寫在 Minimal API Program.cs,整個專案程式就一個 Program.cs 加一個 ChatGptService.cs。Program.cs 如下,LINE API 串接細節由 David 老師的 LineBot SDK 包辦,我的實作邏輯不到一百行,其中三分之一還是資安強迫症發作,我硬是加上 LINE 群組 ID 白名單機制管控權限,防止機器人被加到其他群組盜用。

using System.Security.Cryptography;
using System.Text;
using isRock.LineBot;

NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

var builder = WebApplication.CreateBuilder(args);

Func<string, string> readConfig = (key) => builder.Configuration.GetValue<string>(key) ?? throw new ArgumentNullException(key);

var pwdChk = readConfig("GrantPwd");
var chatSvc = new GptTranslatorService(readConfig("OpenAiUrl"), readConfig("OpenAiKey"), readConfig("OpenAiDepName"));
var lineBotKey = readConfig("LineBotToken");
var lineBot = new Bot(lineBotKey);

var app = builder.Build();

app.MapGet("/", () => "Line GPT Translator Bot");

List<string> groupIds = new List<string>();
var grpIdPath = Path.Combine(app.Environment.ContentRootPath, "data", "groupIds.txt");
LoadGroupIds();

app.MapPost("/Grant", async (context) =>
{
    var grpId = context.Request.Form["grpId"].ToString();
    var pwd = context.Request.Form["pwd"].ToString();
    if (pwd == pwdChk)
    {
        if (!groupIds.Contains(grpId))
        {
            logger.Info($"Grant {grpId}");
            groupIds.Add(grpId);
            SaveGroupIds();
        }
        await context.Response.WriteAsync("OK");
    }
    else
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Unauthorized");
    }
});

app.MapPost("/MsgCallback", async (context) =>
{
    var body = context.Request.Body;
    using var reader = new StreamReader(body);
    var json = await reader.ReadToEndAsync();
    var msg = Utility.Parsing(json);
    foreach (var evt in msg.events)
    {
        if (evt.type == "message")
        {
            if (evt.source.type == "group" && evt.message.type == "text")
            {
                var grpId = evt.source.groupId;
                var text = evt.message.text;
                logger.Trace("訊息[{0}]: {1}", grpId, text);
                if (text.StartsWith("#REG"))
                {
                    lineBot.ReplyMessage(evt.replyToken, "註冊序號 - " + grpId);
                    break;
                }
                if (!groupIds.Contains(grpId))
                {
                    lineBot.ReplyMessage(evt.replyToken, "群組未註冊");
                    break;
                }
                var src = text.Substring(3);
                if (text.StartsWith("#") && Enum.TryParse<Langs>(text.Substring(1, 2).ToUpper(), out var lang))
                {
                    var transText = await chatSvc.Translate(lang, src);
                    logger.Trace("翻譯:\n" + transText);
                    lineBot.ReplyMessage(evt.replyToken, transText);
                }
            }
        }
    }
    await context.Response.WriteAsync("OK");
});

app.UsePathBase("/line-gpt-trans-bot");

app.Run();

void LoadGroupIds()
{
    if (File.Exists(grpIdPath))
    {
        groupIds = File.ReadAllLines(grpIdPath).Where(o => !string.IsNullOrEmpty(o)).ToList();
    }
}
void SaveGroupIds()
{
    File.WriteAllLines(grpIdPath!, groupIds.Distinct().ToArray());
}

會寫程式真好!

Example of LINE Bot which using Azure OpenAI ChatGPT API to translate between languages in group conversation.


Comments

# by AMBER

請問可以分這個應用程式給需要的人安裝嗎? 我家中的印尼外看也是語言很不好,照顧媽媽有許多困難,因為語言不通的關係,請問這個翻譯鳳梨酥可以分享讓我使用嗎?

# by Jeffrey

to AMBER,這個服務背後要串接 ChatGPT API,會以使用量計費,而以翻譯這件小事來說,動用 AI 成本過高(所以我說是大砲打小鳥)。 仲介有介紹我們另個免費 LINE 服務 https://www.ligobot.com/ 也可以同時翻譯英文跟印尼文,效果還行,提供你參考。

Post a comment