iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Build on Google AI

LOCAL:30 天打造 LINE × Google AI 地方服務 Agent系列 第 28 篇

Day 28|把 LOCAL 交給開發者:產品卡、Quickstart 與開源交付規格

  • 分享至 

  • xImage
  •  

把 Repo 網址傳給地方夥伴,對方再交給工程師,第一個問題往往不是用了哪個模型,而是「這能替我做什麼,現在從哪裡開始?」今天我把 LOCAL 整理成可採用的開發者入口:首頁產品卡、一行本機驗證、健康檢查診斷範例,以及 MIT 與貢獻指南。再把志工認領需要的資料契約寫清楚,讓下一位接手的人知道哪些能力能重跑、哪些接線仍須驗收。交付的目的,是少一通求救電話,也少一次把未知當成完成的誤會。

LOCAL connects local-service questions on LINE with Google AI and explicit backend decisions. This chapter delivers a developer-facing product card, a one-line offline starting point, a reproducible FastAPI routing example, and an MIT-based contribution and release checklist. It also specifies the Firestore claim adapter needed to connect volunteer acceptance with the original request. Local examples are tested separately from cloud integration; the production 404 diagnosis, dual-phone acceptance, and demo release remain subject to their own evidence.

一、今日契約卡:開源產品卡與開發者交付清單

在彰化做地方服務,我常把流程比喻為活動報到:初次到訪者應能自主找到入口。GitHub 專案也一樣:如果讀者要先讀完 27 篇才能執行命令,入口就還留在維護者腦中。

今天現場的一句話:第一次打開 Repo,要先讀故事,還是先讓它動一次?
後端決定的規則:同一份已確認詢問沿用原單,模型沒有認領權。
Google AI 的位置:Gemini 與 ADK 處理理解和工具安排,最初體驗先選免金鑰路徑。
三分鐘入口:取得固定程式後,執行第三節的一行指令。
本日交付範圍:已放進兩份 README 的首頁產品卡(Product Card,像專案身分證與規格卡,讓工程師在 30 秒內看懂用途與限制)、已公開的貢獻指南與發布清單、可重跑的本機入口,以及健康檢查與認領接線範例。

開發者要做的事 本篇交付 判讀依據
決定是否適用 中英文首頁產品卡(已進 README) 用途、前提、現在能做什麼
第一次執行 既有 SQLite 備援入口 五項測試與本機報告
接回線上服務 404 診斷排除與 Firestore 認領候選 原始入口、資料契約、後續真機紀錄
修改並分享 MIT、CONTRIBUTING、發布規格 授權文字、回報格式、固定來源

本篇讀取的程式基線是 b8d6277;Day 28 檔案已於 1d3ac87 公開,產品卡於 7fae9b0 併入兩份 README,21 項測試已由專用 CI(Run 38230133310)全數驗證通過。Day 26 預告的 10/12 雙視窗示範,今天只完成接線設計與離線契約,實機接線排在總驗收之後。[1]

這 21 項測試已有專用工作流程:.github/workflows/day28.yml 在 GitHub Actions 的 Ubuntu 執行器上安裝 requirements-dev.txt,用 Python 3.13.5 跑一般與 -O 模式,日誌與 JSON 保留 14 天。Commit a4cc0c9 的 Run 38230133310 全數通過,任何人皆可在 Actions 頁面核對;這條 CI 不呼叫模型、不碰金鑰,測試階段的網路隔離沿用前篇做法。[2]

二、為什麼開源專案需要產品卡?從「看不懂怎麼用」到 30 秒理解價值

許多開發者初次看到會好奇:「為什麼開源專案需要這張產品卡?」我把這份 README 決策入口放在逐日目錄之前,幫初次到訪的工程師理解用途、判斷是否適用,再找到一行啟動指令。30 秒是閱讀目標,不是已量測的上手成績。

這份決策入口的核心價值主張為:為地方長輩與志工設計,把 LINE 詢問、Google AI 理解與可查核的服務狀態串在一起。 緊貼可執行範例,讀者先知道能拿走什麼,才有理由深入原理。

英文入口也能獨立帶路。完整版文字如下(README 放精簡版):

LOCAL — Local Service Agent for LINE
Understand local-service questions; keep decisions auditable.

Start here: run the offline SQLite example with Python 3.10+.
Observe: one request, a blocked claim while paused, and one accepted claim.
No API key is needed for this first step; no LINE message is sent.

Then explore: Gemini + ADK lookup, consent, persistence, and UI recovery.
Cloud integration has separate credentials, costs, and acceptance checks.
License: MIT. Contribution guide: CONTRIBUTING.md.

中文版首頁產品卡完整對應如下:

LOCAL — LINE 地方服務 Agent
理解地方服務提問,保持決策可被查核。

從這裡開始:使用 Python 3.10+ 執行純本機離線 SQLite 範例。
觀察重點:單號唯一不重複、暫停狀態熔斷拒絕認領、恢復後由一位志工成功接手。
第一步完全不需 API 金鑰,不發送任何 LINE 訊息。

進階探索:Gemini 與 ADK 意圖分析、經同意記憶、持久化與 UI 介面復原。
雲端整合具備獨立憑證、費用與驗收關卡。
開源授權:MIT。貢獻指南:CONTRIBUTING.md。

產品卡下方用三個責任階層解釋架構,而不是把服務名稱排成裝飾。

責任階層 元件與動作 安全關卡
互動 LINE Flex 呈現摘要、確認與進度 驗證 Webhook,識別實際操作人員
理解 Cloud Run 內的 ADK 呼叫 Gemini,安排唯讀工具 工具白名單、輸入契約、未知保留
決策與保存 後端確認流程、Firestore 單據、志工認領 本人同意、授權、交易與版本條件

把兩條流程分開畫,就比較不會教錯:

LINE 提問 → Cloud Run 簽章驗證(Signature Verification) → Gemini/ADK 唯讀查詢 → 摘要與確認卡
LINE 確認 → 後端核對同意 → Firestore 保存原單 → 通知 → 志工認領

志工接線為本日候選設計,完成前標註「合約驗收候選」。箭頭代表責任關係,圖例說明現有證據。

LOCAL Day 28 圖 1:README 首頁產品卡與三個責任階層架構
圖 1:README 首頁產品卡與三個責任階層架構。分清互動、理解、決策與保存邊界;標籤「合約驗收候選」代表本日交付、尚未接線。

這三個責任階層每天在 LINE 面對真實長輩。實機測試中,長輩點選彰化市店家,系統依蔬食地圖命中「John 手作蔬食」(華山路78號)並附電話,提醒營業另行確認,不瞎編店家。當長輩說改全素,系統先回確認卡:寫明只存一項偏好、12:26 前有效且可查看更正;長輩按「同意記住」,偏好才寫入。

LOCAL Day 28 圖 2:彰化在地生活實測 — 蔬食友善推薦與隱私授權記憶
圖 2:彰化在地生活實測 — 蔬食友善推薦與隱私授權記憶。左側為查詢「John 手作蔬食」之真實 LINE 截圖;右側為「飲食偏好授權記憶」之真實 LINE 截圖(2026/10/12 實測)。

三、三分鐘 Quickstart:給外部開發者的一行啟動與重現手冊

我先讓讀者看見單號沒多一張,再邀請他設定 Gemini。少掉金鑰門檻,卻能體驗關鍵行為:暫停時擋住認領,恢復後志工唯一接手。

取得 固定快照 並解壓,在含 replay_day27.py 目錄開終端機;Python 3.10+(Windows 用 py -3),範例僅依賴標準函式庫。[1]

python3 -m unittest test_day27_replay -v && python3 replay_day27.py --out out/day28/quickstart-01

指令包含兩動作:先測試再示範,&& 確保前測失敗時中斷。三分鐘是環境就緒後的體驗目標。

執行後打開 out/day28/quickstart-01/report.json。下列是要核對的欄位節錄,不是新造的一份完整執行日誌:

{
  "scope": "SYNTHETIC_IDENTITIES_REAL_SQLITE",
  "status": "PASS",
  "request_count": 1,
  "final_state": "human_claimed",
  "claim_version_before": 1,
  "claim_version_after": 2,
  "second_claim_succeeded": false,
  "external_model_calls": 0,
  "line_calls": 0,
  "live_handoff": "NOT_RUN"
}

重複執行同一行會被主動拒絕以保留舊報告;重測時換成 quickstart-02 即可獨立留存。

四、追查 Cloud Run 的 404 謎團:路由宣告、中介軟體與健康檢查修復

前篇記錄 /healthz 回 404,未帶簽章的 /webhook 回 401。若看到 404 就盲目新增端點易改錯地方:examples/day12/main.py 原本就宣告了 GET /healthz。真正該問的是執行實例啟動了哪個模組;子應用掛載與代理前綴也可能改變路徑,不能只搜尋函式名稱便結束診斷。[1][4]

在 Google Cloud 專案確認帳號與區域後執行唯讀查詢(PROJECT_ID 與 REGION 代入實際值),不部署、不切流量:

gcloud run services describe local-day12-agent --project="$PROJECT_ID" --region="$REGION" --format=json > service-snapshot.json

接著核對服務入口、revision、映像摘要與啟動命令。摘要的 100% 僅為流量分配,tag URL 仍可直通其他版本,須由日誌確認探測指向的實際 revision。[3]

觀察 下一個檢查 修復條件
程式有路由,執行實例沒有 容器 command、args、模組與 app 名稱 修正啟動目標,再測路由
子應用掛在前綴下 mount 與代理轉送路徑 對齊探測路徑,不盲目新增路由
中介軟體先回應 原始回應本文、請求日誌與處理順序 僅調整必要路徑政策,保留 Webhook 簽章驗證
正確入口仍回傳 404 精確映像與實際載入的程式 建立最小重現後才變更部署

下面是我為這篇建立的本機反例:健康路由確實存在,卻註冊在另一個 app。這段在 FastAPI TestClient 中執行,不啟動公開服務,也不是把線上未知原因改寫為這個案例。

from fastapi import FastAPI
from fastapi.testclient import TestClient


def add_health(app: FastAPI) -> None:
    @app.get("/healthz")
    async def healthz():
        return {"status": "ok"}


configured = FastAPI()
add_health(configured)
wrong_export = FastAPI()

with TestClient(wrong_export) as client:
    wrong_status = client.get("/healthz").status_code
with TestClient(configured) as client:
    correct_status = client.get("/healthz").status_code

if (wrong_status, correct_status) != (404, 200):
    raise RuntimeError("ROUTE_EXAMPLE_CHANGED")
print({"wrong_export": wrong_status, "correct_export": correct_status})

本機反例得到 404 與 200,掛在 /api 下須查 /api/healthz。這教我區分「函式寫好」與「服務載入」。修正後要測正確與錯誤路徑,避免為健康檢查開了太大例外。

LOCAL Day 28 圖 3:Cloud Run 404 路由診斷對照
圖 3:Cloud Run 404 路由診斷對照。左下為路徑政策分支節錄,其他請求仍走既有簽章驗證;展示實例導出錯誤與前綴偏移之本機合成實測,非線上已確認根因。

健康端點只回應行程狀態,不呼叫 Gemini 或掃描 Firestore;名稱叫 /healthz 不會自動啟用探測,回 200 也不等於服務單已有人接手。[3]

五、志工怎麼接手同一張單:Firestore 認領接線與雙視窗驗收規格

手機 A 已保存詢問,畫面顯示「待人工覆核」,接下來才輪到志工 B。這段最容易犯的錯,是看到 req-… 就把它當成 Firestore 文件路徑。我讀取既有程式後,確認單據建立用的是 tx.create('requests', oid, row);畫面單號放在 row['request_id'],文件鍵則是 operation_id。[1]

Firestore 配接器還限定文件識別為 64 碼雜湊,集合位於既有隔離命名空間。認領必須沿原操作找到原文件,核對其中的單號,不能另外用畫面單號建立一張同名新單。

另一個接點是讀回契約:Day 11 的 _check_row() 及 Day 13 卡片驗證,原先只接受 pending_human_review。只改寫成 human_claimed,會讓合法進度被舊驗證拒收。調整時保留擁有者、內容指紋與操作綁定,按狀態增加必要欄位,而不是刪掉所有檢查。

我將本日認領設計成三個交易讀取對象:原單、志工授權與停止控制。新增的 claim_version、claimed_by、volunteer_grants、service_controls 都是接線設計;舊單缺少版本時先走明確遷移,不能讀不到就默默當成 1。

下列節錄使用既有 get/put/atomic 介面。actor 由驗證後入口提供,型別本身不是身分憑證;完整設計另核對原始 binding、本人及服務範圍。模型與 LINE 傳送都留在交易外。[5]

def claim_in_tx(tx, actor, operation_id, request_id, expected_version):
    row = tx.get("requests", operation_id)
    binding = tx.get("bindings", operation_id)
    check_original_binding(row, binding, operation_id, request_id)
    service_id = row["service_id"]
    grant = tx.get("volunteer_grants", scope_key(
        actor.tenant_id, actor.user_id, service_id))
    control = tx.get("service_controls", scope_key(
        actor.tenant_id, service_id))
    patch = claim_patch(row, grant, control, actor, expected_version)
    if patch is None:
        return dict(row)
    updated = {**row, **patch}
    tx.put("requests", operation_id, updated)
    return updated

# 接線形式:傳入既有 store.atomic,所有讀取先於寫入。
# actor、operation_id 與 request_id 由可信入口解析。
result = store.atomic(lambda tx: claim_in_tx(
    tx, actor, operation_id, request_id, expected_version))

這是控制流節錄,check_original_binding 核對原操作與綁定。claim_patch 檢查授權、SERVING、待覆核狀態與版本,再一次更新狀態、認領者與版本;另一位志工不能覆蓋已認領資料。

版本條件表達應用端 CAS 意圖,原子性仰賴 store.atomic() 的 Firestore 交易;並行控制依資料庫設定而定,不可簡化為單一樂觀鎖。[5]

examples/day28 的合約在 macOS Python 3.13 執行 21 項離線檢查(日誌在證據目錄),涵蓋 17 項認領契約與四項健康路由檢查,一般與 -O 模式各跑同一組;其中認領使用記憶體交易模擬,不代表 Firestore 競爭或手機流程已通過。真機驗收依下表獨立留證:

驗收操作階段 執行角色與操作行為 留存證據與驗收標準
A 確認 長輩手機 A 送出「留下服務詢問」確認 原 operation_id 與 request_id 綁定,保存一次,不重複建單
B 接收 志工手機 B 收到推播通知卡片 通道接受回條與手機可見卡片分開保存,狀態維持待審核
PAUSED 下認領 志工在系統維護或停止服務時嘗試認領 熔斷閘門攔截,操作遭拒,原文件內容與版本號維持不變
SERVING 後認領 兩位志工在恢復服務後同時搶單認領 CAS 原子交易成功,認領人唯一,版本號精確增加一次
A 查回及舊操作 長輩查詢進度或不慎重送舊確認 本人讀回合法進度,重送原確認冪等防重放,不增加單據

這次手機截圖能確認的是:輪椅與志工問題得到範圍說明及三個入口,尚未呈現這則新需求的確認或建單。依既有確認流程,使用者後續選「留下服務詢問」並明示同意,才可建立單據。志工認領仍是合約規則:在交易內核對授權、停止狀態及版本,不把離線契約當成 Firestore 或雙手機成果。

LOCAL Day 28 圖 4:從手機詢問到後端認領 — 志工認領與 CAS 時序
圖 4:從手機詢問到後端認領。左側為長輩在 LINE 詢問無障礙與志工的真實截圖(2026/10/12);右側為候選的 Firestore 認領交易時序,只在離線合約測試中驗過,尚未接上 Firestore 與真機。

六、版本怎麼凍結:Git Tag v1.0.0-demo 與開源交付規格(MIT / CONTRIBUTING)

開源希望地方夥伴能衍生修改,也讓接手者明瞭責任。目前 Repo 已具備 MIT LICENSE,權利人為 Jia-Sin Chen & ChiBuApp Inc.,今日重點是釐清入口。[1]

MIT 允許使用與修改,散布時保留著作權聲明且不提供擔保;它不強制衍生介面放置商標,介紹來源屬友善請求而非授權義務。第三方店家文字與照片須各自確認權利範圍。[6]

CONTRIBUTING.md 則讓善意有路可走。指南(中英文)要求回報者附上來源版本、Python 與系統、最小重現步驟、預期與實際結果,最後是去識別日誌。秘密外洩走私人通報;一般問題才放公開 Issues。

v1.0.0-demo 是本次預定發布標籤,查核時尚未建立。標籤指向來源,Release 提供說明與附件;一般 Git tag 並不是防竄改保險箱。維護者完成驗收後建立帶註解標籤、核對遠端 Commit,再發布候選包,不以移動舊標籤掩蓋變更。[7]

若啟用 GitHub 不可變發布,先在草稿備妥附件再發布以保護標籤;凍結的承諾是「能辨識變更」,而非外部世界沒有漂移。

正式發布條件留在技術清單:Quickstart 可重跑、相符 CI、授權來源、接線驗收、清冊與回復方式。條件未齊保留候選狀態,不因名稱有 v1.0.0 就暗示正式營運保證。

七、從一人專案到開源生態:工程成熟度在交付的同理心

志工不該為了一張單重填資料,接手的工程師也不該為找入口重讀整段開發史。這是同一種同理心:把我已知的事,整理成別人首次來也能使用的說明。程式能跑只是起點,出錯時還要知道該找哪份紀錄。

我不會急著把一人維護寫成社群成形,先讓一份回報能被重現,一個小修正能被測試。英文頁面提供前提與失敗處理,中文連載保留地方情境與取捨,兩邊使用相同入口與完成條件:契約在 CI 驗,模型在本機測。

首頁清楚界定適用範圍:這是地方服務 Agent 的工程範例,不是付款或救援承諾。AI 協助翻譯產品卡與整理測試,但各項狀態仍回到原始結果。少寫一句滿分宣言,多提供可查入口,更能讓人放心。

八、結語與明天預告:100 筆地方契約評測大驗收與完整流程架構總圖

今天把 LOCAL 的閱讀入口、第一次操作、問題回報與發布責任整理到同一條路上。讀者可以先用本機理解單據規則,再決定是否進入 Google AI 與 LINE 的實網設定;線上 404 和志工認領則用各自的資料完成後續驗收。

Day 29 會整理 100 筆地方契約與架構總圖。100 應是有獨立識別、輸入、來源與期待行為的案例,不把 20 題重跑五次換個名字就湊數,也不拿今天 21 項單元測試混入模型成績。架構圖將跟著一則詢問前進,讓完成和缺口都找得到位置。

把專案交給開發者,不是把程式碼丟上 GitHub 就算完成;而是用一張清楚的產品卡說明價值,用一行指令消除重現阻礙,並用負責任的開源授權保障彼此的信任。

程式與參考資料

[1] LOCAL:固定基線 b8d6277、Day 27。Day 28 於 Commit 1d3ac87,產品卡於 7fae9b0,專用 CI 於 Run 38230133310 通過。
[2] Docker:空建置上下文、None 網路驅動。
[3] Google Cloud:Cloud Run 修訂版、健康檢查。
[4] FastAPI:測試、子應用掛載。
[5] Firestore:交易、並行控制。
[6] Open Source Initiative:MIT License。
[7] Git:git-tag;GitHub:不可變發布。


上一篇
Day 27|成果展示要鎖住什麼?Demo 版本凍結、三分鐘劇本與離線備援
下一篇
Day 29|100 筆地方契約評測大驗收與完整流程架構總覽
系列文
LOCAL:30 天打造 LINE × Google AI 地方服務 Agent 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言