如何讓工作流程如下所示

聊天對話

AI 提示訊息

「你是一位專業的旅遊助手。你需要分三個不同的階段進行操作,並且必須遵循每個階段的嚴格格式規則。

請根據使用者的請求與其進行對話、問候,並顯示相應的按鈕。

階段 1:目的地探索

觸發條件:初始問候。

操作:呼叫 get_destinations。

格式:以 [TYPE:DESTINATION] 開頭,接著是問候語,然後是原始 JSON […]。

階段 2:城市探索

觸發條件:使用者選擇一個國家。

操作:呼叫 get_cities。

格式:以 [TYPE:CITY] 開頭,接著是簡短的選擇提示,然後是原始 JSON […]。

階段 3:套餐探索

觸發條件:使用者選擇一個城市。

操作:使用承載值呼叫 tour_packages。

取得城市 ID 的說明:當使用者選擇一個城市時,你將收到一條包含承載值的訊息。此承載值是該城市的 term_id。在呼叫 tour_packages 工具時,你必須使用此特定值。如果你沒有 term_id,請要求使用者澄清他們的城市選擇

格式:以 [TYPE:PACKAGES] 開頭,接著是簡短的確認訊息,然後是原始 JSON […]。

關鍵協議:

路由標題:每條訊息必須以 [TYPE:DESTINATION]、[TYPE:CITY] 或 [TYPE:PACKAGES] 開頭。

無摘要:永遠不要寫成對話式列表或計數。僅提供標籤、簡短問候語和原始 JSON。

機器可讀:JSON […] 必須是每條訊息的最後部分。

工具使用:始終使用提供的工具來擷取資料。永遠不要虛構套餐或城市。」

重要事項:

  • 僅返回有效的 JSON
  • 不包含解釋
  • 不轉義引號
  • 不在 JSON 中包含 [TYPE:…]
  • 輸出必須能直接被 JSON.parse 解析

工作流

全域解析器

const rawOutput = $input.first().json.output || “”;

// =========================
// 幫助函式:標準返回
// =========================
function formatResponse(data) {
return [{ json: data }];
}

// =========================
// 偵測 TYPE
// =========================
function detectType(text) {
if (text.includes(“[TYPE:DESTINATION]”)) return “DESTINATION”;
if (text.includes(“[TYPE:CITY]”)) return “CITY”;
if (text.includes(“[TYPE:PACKAGES]”)) return “PACKAGES”;
return null;
}

// =========================
// 提取清潔 JSON
// =========================
function extractJSONArray(text) {
if (!text) return null;

// 移除 TYPE 標籤
let cleaned = text.replace(/\[TYPE:[A-Z]+\]/g, "").trim();

// 移除佔位符
if (cleaned.includes("[...]")) return null;

// 取得 JSON 邊界
const start = cleaned.indexOf('[');
const end = cleaned.lastIndexOf(']');

if (start === -1 || end === -1) return null;

let jsonString = cleaned.substring(start, end + 1);

// 🔥 修正常見的 AI 問題
jsonString = jsonString
    .replace(/\\\//g, "/")     // 修正轉義斜線
    .replace(/;\s*$/, "")      // 移除尾部分號
    .trim();

try {
    let parsed = JSON.parse(jsonString);

    // 處理雙重編碼的 JSON
    if (typeof parsed === "string") {
        parsed = JSON.parse(parsed);
    }

    return parsed;
} catch (e) {
    throw new Error("JSON 解析失敗:\n" + jsonString);
}

}

// =========================
// 主程式
// =========================
const type = detectType(rawOutput);
const parsed = extractJSONArray(rawOutput);

// =========================
// 目的地
// =========================
if (type === “DESTINATION” && parsed) {
return formatResponse({
recipient: { id: “123456” },
message: {
text: “選擇一個國家:”,
quick_replies: parsed[0].data.map(i => ({
content_type: “text”,
title: i.destination,
payload: i.id
}))
}
});
}

// =========================
// 城市
// =========================
if (type === “CITY” && parsed) {
return formatResponse({
recipient: { id: “123456” },
message: {
text: “選擇一個城市:”,
quick_replies: parsed[0].data.map(i => ({
content_type: “text”,
title: i.city || i.name,
payload: i.id
}))
}
});
}

// =========================
// 套餐
// =========================
if (type === “PACKAGES” && parsed) {
const packages = parsed[0].data;

const elements = packages.map(pkg => ({
    title: pkg.title,
    subtitle: pkg.subtitle || "最佳旅遊套餐",
    image_url: pkg.image_url,
    buttons: (pkg.buttons || []).map(btn => ({
        type: btn.type === "web_url" ? "web_url" : "postback",
        title: btn.title,
        url: btn.url,
        payload: btn.payload
    }))
}));

return formatResponse({
    recipient: { id: "123456" },
    message: {
        attachment: {
            type: "template",
            payload: {
                template_type: "generic",
                elements: elements
            }
        }
    }
});

}

// =========================
// 備用方案
// =========================
return formatResponse({
message: {
text: rawOutput || “沒有可用的回應。”
}
});

我的問題

  • 我想要讓對話進行,並讓 AI 代理自行搜尋並提供結果。
  • 目前它只能根據按鈕點擊和承載值來運作。
  • 在使用者點擊程式詳細資訊後,AI 將只為該套餐擷取資料並進行對話。

為了讓所有功能都能運作,請建議我如何制定工作流程、如何設計系統,我需要使用多少個 AI 代理,以及如何告訴工作流程在何時使用哪個工具?

請幫助我進行腦力激盪或提供指引,非常感謝。

好的,在閱讀了你的文章後,以下是我考慮完所有需求後的意見,希望有所幫助。

目標和方法

  • 構建一個模組化、自學習的對話流程,能夠獲取超越預定義按鈕payload的資料。

  • 使用一小組專門的AI代理(或工具)來涵蓋探索、選擇、打包和敘述對話。

  • 在工作流中以聲明式方式協調工具使用,讓系統在正確的時間選擇正確的工具。


系統設計概覽

  • 主要元件

    1. 聊天協調器(n8n或微服務):驅動對話狀態,路由到代理/工具。

    2. AI代理層:一小組專門的代理(探索、城市、套餐、敘述),範圍明確。

    3. 資料與工具:get_destinations、get_cities、tour_packages,以及任何外部資料來源。全部透過穩定的API存取。

    4. 狀態與上下文存儲:儲存使用者工作階段資料、選定的國家/城市和選定的套餐,加上用於可追溯性的ID。

    5. 訊息格式化工具:強制執行[TYPE:DESTINATION]/[TYPE:CITY]/[TYPE:PACKAGES]路由標頭和最終JSON payload。

    6. 可觀測性:結構化日誌、追蹤ID和錯誤儀表板。

  • 資料流模式

    1. 使用者問候 → 探索代理提供目的地。

    2. 使用者選擇一個目的地 → 城市代理提供該國家的城市。

    3. 使用者選擇一個城市 → 套餐代理獲取套餐;對話繼續進行套餐詳情。

    4. 選擇套餐後,系統獲取詳細的套餐資料並用豐富的內容更新對話。


AI代理設計與角色

  • 代理A:探索

    • 目的:獲取並呈現目的地;將JSON payload保持為單一的真實來源。

    • 輸出:[TYPE:DESTINATION]標頭 + 問候語 + 原始JSON。

  • 代理B:城市

    • 目的:為選定的目的地獲取城市;提供簡潔的提示以選擇城市。

    • 輸出:[TYPE:CITY]標頭 + 簡短提示 + 原始JSON。

  • 代理C:套餐

    • 目的:當選擇城市時為該城市獲取套餐;提供緊湊、易於瀏覽的詳情。

    • 輸出:[TYPE:PACKAGES]標頭 + 簡短確認 + 原始JSON。

  • 代理D:敘述/對話

    • 目的:生成參考選定目的地/城市/套餐的類人對話,並引導使用者。

    • 輸出:由當前上下文指導的自然語言訊息,在需要時仍發出所需的TYPE標頭。

  • 代理E:驗證與安全

    • 目的:確保資料準確性、防止幻覺、強制資料隱私,並驗證所有步驟中的payload完整性。
  • 可選:資料豐富化代理

    • 目的:獲取額外的中繼資料(季節性、天氣、本地提示)來豐富套餐描述。
  • 從4個核心代理(探索、城市、套餐、敘述)加上一個輕量級驗證代理開始。你之後可以添加豐富化功能。

工作流如何決定使用哪個工具/代理

  • 在協調器中實現路由層:

    • 如果不存在TYPE或初始問候 → 探索。

    • 如果使用者選擇了國家 → 城市。

    • 如果使用者選擇了城市 → 套餐。

    • 如果使用者要求敘述或更深層次的詳情 → 敘述。

    • 如果涉及資料完整性或敏感資料 → 驗證。

  • 使用明確的狀態轉換:

    • state: { phase: “discovery” | “city” | “packages” | “narrative”, country_id, city_id, package_id, user_id }
  • 定義集中式工具註冊表,包含:

    • 名稱、類型(API呼叫、本地函數、LLM)、輸入模式、輸出模式、錯誤策略。

對話模式與格式化

  • 在每則訊息中保持所需的路由標頭:

    • [TYPE:DESTINATION]

    • [TYPE:CITY]

    • [TYPE:PACKAGES]

  • 確保最終的機器可讀JSON payload是訊息的最後一個區塊。

  • 對話流程示例(高層級)

    1. 探索:

      • 輸出:[TYPE:DESTINATION] + 問候語 + 原始JSON目的地
    2. 城市:

      • 輸出:[TYPE:CITY] + 提示 + 原始JSON城市
    3. 套餐:

      • 輸出:[TYPE:PACKAGES] + 確認 + 原始JSON套餐
    4. 敘述:

      • 輸出:具有上下文的自然語言訊息,或詳細的套餐敘述,必要時仍需標記。
  • 持久性和可追溯性

    • 將session_id和request_id附加到每則訊息,以關聯各個步驟。

工作流協調藍圖(面向n8n)

  • 節點配置(模組化區塊)

    1. 觸發器:HTTP或聊天webhook以啟動工作階段。

    2. 狀態載入器:從資料存儲載入工作階段上下文。

    3. AI網關:根據階段選擇適當的代理。

    4. 代理呼叫:呼叫get_destinations / get_cities / tour_packages或敘述內容。

    5. 回應格式化器:用正確的[TYPE:*]標頭和最終JSON進行包裝。

    6. 狀態保存器:持久化更新的上下文和選擇。

    7. 輸出:發送給使用者,記錄互動。

  • 資料合約

    • Destinations API返回:[{ id, country, name, description, image }, …]

    • Cities API返回:[{ id, city, country_id, region }, …]

    • Packages API返回:[{ id, title, summary, image_url, price, details }, …]

  • 錯誤處理

    • 對於暫時性API錯誤,使用指數退避的重試策略。

    • 如果API失敗,提供備用訊息,附帶清晰的追蹤ID。

    • 在每個步驟進行驗證,確保所需欄位存在後再繼續。

  • 安全性與隔離

    • 每個容器使用專用網路和作用域限定於代理的API金鑰。

    • 透過保管庫管理密鑰;定期輪換。

    • 在Docker Compose或Kubernetes清單中應用最小權限原則