昨天成功將 AI 處理的結果寫入 Notion,今天的目標不只是要加入 LINE 通知,而且我希望能整個系統架構進行一次最佳化,解決在本地執行 AI 模型時遇到的效能瓶頸。
首先要先在 LINE 的開發者平台建立一個用於發送訊息的 Bot。
Provider 是管理 Channel 的單位,可以想像成是「開發團隊」或「公司」。
GIGI 工作室
,然後點擊「Create」。Channel 就是我們要建立的 Bot。
GIGI 工作室
Provider 內,點擊「Create a Messaging API channel」。M2A Agent Bot
在 Bot 的主頁面,點擊右上角的「設定」。
在設定頁面中,點擊「Messaging API」。
會看到頁面顯示「狀態」為「使用中」,這表示官方帳號已經是一個可以透過 API 操作的 Channel。
回到開發者網站 LINE Developers
GIGI 工作室
Provider 下,可以看到剛剛建立的 M2A Agent Bot
點擊進入 Channel 。回到 n8n M2A Agent 的工作流,將工作流改造為非同步模式,以解決前端請求超時的問題。
JSON
,並將 Response Body 重新修改為以下內容{
"status": "processing_started",
"message": "M2A Agent has received the request and is processing it in the background. Notification will be sent via LINE."
}
使用 HTTP Request
節點來發送 API 請求,因此需要先設定好認證。
LINE M2A Agent Credential
Authorization
(這是 HTTP Header 的欄位名稱,必須完全符合)Bearer CHANNEL_ACCESS_TOKEN
CHANNEL_ACCESS_TOKEN
替換為在 Step 1.5 取得的「Token」。Bearer
後面有一個空格)。為了讓 LINE 通知訊息乾淨易讀,必須在發送前,將 AI 生成的 Markdown 內容轉換為純文字。
Run Once for All Items
。JavaScript
。const item = $input.item;
function markdownToPlainText(markdown) {
if (!markdown || typeof markdown !== 'string') return '';
let text = markdown;
text = text.replace(/(\*|_)(.*?)\1/g, '$2');
text = text.replace(/^#{1,6}\s+/gm, '');
text = text.replace(/(\*\*|__)(.*?)\1/g, '$2');
text = text.replace(/~~(.*?)~~/g, '$1');
text = text.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1');
text = text.replace(/!\[.*?\]\(.*?\)/g, '');
text = text.replace(/^\s*[\*\-\+]\s+/gm, '• ');
text = text.replace(/^\s*\d+\.\s+/gm, '');
text = text.replace(/^(---|\*\*\*|___)\s*$/gm, '');
text = text.replace(/``````/g, '');
text = text.replace(/`([^`]+)`/g, '$1');
text = text.replace(/\n{3,}/g, '\n\n');
return text.trim();
}
const summary = $('AI 摘要與任務提取').item.json.summary;
const tasks = $('AI 摘要與任務提取').item.json.tasks;
const notionUrl = $('寫入會議紀錄').item.json.url;
item.json.summary = summary;
item.json.tasks = tasks;
const lineMessageText = `會議處理完成通知
- 專案:GIGI 工作室內部專案
- 日期:${new Date().toLocaleDateString('zh-TW')}
- Notion 頁面:${notionUrl}
--- 會議摘要 ---
${markdownToPlainText(summary)}
--- 行動任務 ---
${markdownToPlainText(tasks)}`;
item.json.lineApiPayload = {
"messages": [
{ "type": "text", "text": lineMessageText }
]
};
return item;
將 LINE 通知功能加入到 M2A Agent
工作流。
M2A Agent
工作流,在「Markdown 格式化處理」節點之後,新增一個「HTTP Request」節點。POST
https://api.line.me/v2/bot/message/broadcast
Generic Credential Type
Header Auth
LINE M2A Agent Credential
JSON
Fixed
切換為 Expression
。單行表達式輸入框:{{ $('Markdown 格式化處理').item.json.lineApiPayload }}
Retries
為 3
次,Retry Interval
為 60
秒。「AI 摘要與任務提取」節點是整個流程的大腦,我們將其最佳化為一次呼叫,以提升處理效率,且可以取得所有需要的資訊,並加入錯誤處理。
將「AI 摘要與任務提取」節點內的內容修改為以下的內容
const webhookData = $input.first().json.body;
const inputText = webhookData.text;
const instruction = webhookData.instruction || "請幫我整理出重點並提取任務";
console.log('開始處理 AI 摘要與任務提取 (合併模式)...');
// 一次請求,同時取得摘要與任務
async function generateSummaryAndTasks(text) {
const payload = {
model: "qwen2.5-taiwan-7b-instruct-i1",
messages: [
{
role: "system",
content: "你是一個專業的會議記錄分析師。你的任務是從提供的文本中,生成一份包含摘要和行動任務的 JSON 物件。這個 JSON 物件必須且只能包含兩個鍵:'summary' 和 'tasks'。'summary' 的值是會議摘要的字串,'tasks' 的值是行動任務的字串。請確保在這兩個字串的值中使用 Markdown 語法來格式化內容 (例如 **粗體** 和項目符號)。不要在你的回應中加入任何額外的解釋或 Markdown 程式碼區塊,直接回傳純粹的 JSON 物件。"
},
{
role: "user",
content: `請根據上述指令,分析以下會議內容:\n\n${text}`
}
],
temperature: 0.5,
max_tokens: 1024
};
try {
const response = await this.helpers.httpRequest({
method: 'POST',
url: 'http://host.docker.internal:1234/v1/chat/completions',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
timeout: 300000
});
const responseData = typeof response === 'string' ? JSON.parse(response) : response;
if (responseData.choices && responseData.choices[^0] && responseData.choices[^0].message) {
let aiContentString = responseData.choices[^0].message.content;
const jsonMatch = aiContentString.match(/``````/);
if (jsonMatch && jsonMatch[^1]) {
aiContentString = jsonMatch[^1];
}
const structuredResult = JSON.parse(aiContentString);
return structuredResult;
} else {
throw new Error('合併API回應格式異常');
}
} catch (error) {
return { summary: `處理失敗:${error.message}`, tasks: "因處理失敗,無法提取任務" };
}
}
// 主要處理流程
try {
const aiResult = await generateSummaryAndTasks(inputText);
const result = {
summary: aiResult.summary,
tasks: aiResult.tasks,
processing_info: {
input_length: inputText.length,
summary_length: (aiResult.summary || '').length,
tasks_length: (aiResult.tasks || '').length,
processed_at: new Date().toISOString(),
status: 'success'
}
};
return [{ json: result }];
} catch (error) {
return [{
json: {
summary: `處理失敗:${error.message}`,
tasks: "無法提取任務",
processing_info: { status: 'error', error_message: error.message }
}
}];
}
可以使用之前建立過的測試程式來觸發整個流程。
M2A Agent Bot
為好友需要檢查三個地方
M2A Agent Bot
發送的通知,且訊息格式乾淨、沒有 Markdown 語法。會議處理完成通知
- 專案:GIGI 工作室內部專案
- 日期:2025/9/19
- Notion 頁面:https://www.notion.so/
--- 會議摘要 ---
會議討論了使用者認證系統的完成時間、開發環境的準備以及錯誤處理機制的研究,並安排了下週的壓力測試腳本編寫工作。
--- 行動任務 ---
• 志明 負責在週五前完成 Flask 應用程式的 JWT 集成和整個項目的 Dark 化,需在週三前準備好 DarkField 和 DarkCompose.yml。
• 志明 協助小美研究 N8n 的錯誤處理機制,並解決 Timeout 問題。
• 小美 完成 N8n 的工作流設計,特別是 Webhook 接收資料後寫入 notion 部分,在週四前準備測試版本供團隊測試,並開始規劃晚上的客製化規則以監控非預期的 API 呼叫。
• 志明 和 小美 分別在週五下午三點前完成系統壓力測試腳本並推上 GitHub,以便週末進行完整的整合測試。
Respond
模式,並搭配 Respond to Webhook
節點,讓系統在接收到請求的當下就立即回覆「任務已開始」,然後在背景繼續執行耗時的 AI 運算與後續流程。Code
節點「Markdown 格式化處理」。利用 JavaScript 的字串取代 (replace
) 功能,撰寫一個 markdownToPlainText
函式,有效過濾各種 Markdown 符號,同時保留原始 Markdown 內容傳給 Notion 節點,達成「一源多用」的效果。✅ 完成項目
今天學會了如何處理「異質性輸出」的問題,同一個 AI 生成的內容,在 Notion 中需要保留 Markdown 格式以獲得良好的排版,但在 LINE 中需要純文字。
透過在 n8n 中建立一個中間處理節點,成功地讓一份資料以不同的格式滿足不同平台的需求,讓我學到了自動化流程中「資料轉換」的精髓。
這些經驗讓我學會,一個穩定的自動化系統不僅是節點的串接,更重要的是對資料流的精準控制與轉換,還有漸漸開始真正的設計一個系統,從同步到非同步的架構轉變。
🎯 明天計劃
使用 Gradio 建立前端,讓使用者可上傳音訊與輸入處理指令,並且將專案結構標準化。