iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
AI 自動化

用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文系列 第 11 篇

Day 11|標籤越長越亂?先讀既有標籤,讓 AI 重用同一個詞

  • 分享至 

  • xImage
  •  

昨天講完 AI 回傳的 JSON 怎麼解析、怎麼驗證,結尾留了一個問題:getExistingTags 抓來的既有標籤,是怎麼餵給 AI 的,又為什麼常常還是攔不住標籤越生越亂。今天就從這支函式開始講。

先看 getExistingTags 在做什麼:

async function getExistingTags(env, sampleSize = 100) {
  try {
    const res = await fetch(
      `https://api.notion.com/v1/databases/${env.NOTION_DATABASE_ID}/query`,
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${env.NOTION_TOKEN}`,
          'Notion-Version': '2022-06-28',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          sorts: [{ timestamp: 'created_time', direction: 'descending' }],
          page_size: sampleSize,
        }),
      },
    );

    if (!res.ok) return [];

    const data = await res.json();
    const tagSet = new Set();
    for (const page of data.results) {
      const tags = page.properties[PROP.tags]?.multi_select || [];
      for (const t of tags) tagSet.add(t.name);
    }
    return [...tagSet];
  } catch {
    return []; // 標籤參考只是輔助,抓失敗不影響主流程
  }
}

邏輯不複雜:查最近建立的 100 筆頁面,把每一筆的標籤欄位(Multi-select)攤開,丟進一個 Set 去重,最後轉成陣列回傳。用 Set 是因為同一個標籤會在很多筆筆記裡重複出現,不去重的話陣列裡會塞滿一堆同名的東西,沒有意義。

這個陣列會在 handleTextMessage 裡,分類之前先抓好:

const existingTags = await getExistingTags(env);
const result = await classify(env, { url, content, userNote, existingTags });

再進到 classify(),變成 system prompt 裡的一行:

已經用過的標籤(優先從中挑選相同概念的詞,避免同義詞發散,例如已有「AI應用」就不要再造「AI工具」):${existingTags.join('、')}

只有既有標籤都不貼切時,才建立新標籤。

Day 9 提過,標籤(Multi-select)跟分類(Select)不一樣,分類有 CATEGORIES 這個固定清單卡著,標籤沒有清單、本來就是要越用越多。可是「越用越多」跟「越用越亂」只有一線之隔,同一個概念,AI 這次寫「AI應用」,下次心情不同寫成「AI工具」、「人工智慧」、「AI相關」,語意上都對,但標籤庫裡就多了三個只出現一次的詞,之後想用標籤篩選反而更難篩。getExistingTags 想解決的就是這個問題:把過去用過的詞先攤給 AI 看,讓它知道「AI應用」這個詞已經有人用過,優先重複用,而不是每次都憑感覺重新造一個。

但這招其實沒有真的解決問題,只是把發散的機率壓低一點,理由要拆成兩層來看。

第一層是抓樣本的方式。sampleSize 預設 100,查詢條件只有「依建立時間排序、抓前 100 筆」,沒有分頁抓完整個資料庫。存的筆記一旦超過 100 筆,比較早期存的那些筆記用過的標籤,就不會出現在這份參考清單裡。這代表如果你三個月前存過一篇貼了「副業」標籤的文章,現在資料庫已經累積兩百多筆,getExistingTags 查回來的 100 筆裡如果剛好沒有那篇,AI 完全不知道「副業」這個標籤存在過,同樣主題的文章很可能就被貼成「兼職」或「side project」,標籤還是照樣發散,只是這次不是 AI 的問題,是它壓根沒看到那份參考資料。

第二層才是真正的關鍵,也是這個設計故意留下的一個縫:這句話終究只是 prompt 裡的一句「請求」,不是程式碼裡的規則。回頭看 Day 10 提過的那段驗證邏輯:

return {
  title: parsed.title || url,
  category: CATEGORIES.includes(parsed.category) ? parsed.category : '生活其他',
  tags: Array.isArray(parsed.tags) ? parsed.tags.slice(0, 4) : [],
  summary: parsed.summary || '',
};

category 有 CATEGORIES.includes() 這道白名單卡著,AI 亂回一個不在清單裡的分類,程式會直接攔下來改成「生活其他」。但 tags 這一行只檢查是不是陣列、切到最多 4 個,完全沒有拿 existingTags 回頭比對:AI 到底有沒有照著提示重複用既有標籤,程式從頭到尾不會知道,也不會做任何事。這是刻意的取捨:分類只有七個選項,做白名單很合理;標籤本來就該持續長大,硬做白名單反而會卡死新概念的出現,所以標籤這一關只能靠 prompt 去「拜託」AI 自律,擋不住它自己心血來潮造新詞。

所以「讀既有標籤」這個設計比較貼切的說法,是把標籤發散的速度放慢,而不是解決。它靠的是免費模型在多數情況下確實會照著提示做(畢竟 prompt 把例子都寫出來了),加上抓最近 100 筆已經涵蓋了大部分「最近常用」的詞,兩者疊起來,日常使用時標籤庫看起來還算收斂。但只要模型狀態不穩、或標籤剛好落在抓不到的那 100 筆之外,同義詞照樣會生出來,這個系統目前也沒打算做更嚴格的比對或合併機制。對一個純粹自己用的工具來說,標籤庫偶爾長出幾個孤兒詞,還沒嚴重到值得再花一層邏輯去治理。

明天要換個角度,講抓網頁內文跟 AI 分類都要打外部 API,這些免費額度的服務隨時可能掛掉或超額,line-notion-bot 的三層 fallback 是怎麼設計的。


上一篇
Day 10|AI 回傳格式壞掉怎麼辦:JSON 解析與驗證
下一篇
Day 12|免費模型隨時會掛:三層 fallback 設計
系列文
用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言