

客戶在南美 智利 這專案看起來還真有挑戰性!
我連需求都看不太懂 我先跟 ai 討論案主需求
這個職缺本質上是在做一個:
AI 語音客服 / AI 電話業務代理平台(SaaS)
而且已經有 MVP 了,現在要往「可擴張商業產品」升級。
一句話總結
他們在做的是:
一個可以接電話、回 WhatsApp、串後台資料、可視化管理的 AI 語音代理平台。
類似:
- AI 電話客服
- AI 外呼銷售
- AI 預約機器人
- AI Call Center SaaS
要做平台的話 這需求滿複雜的
首先 我想先知道 客服這種 agent 如果不是單一流程跑到底 要怎麼處理多種情境
Pattern Note #06:AI Agent 的 dynamic routing 與 dynamic tools
在調查各種平台服務的時候 我看到類似這樣的 ui


這個專案 應該也需要類似的流程管理吧
這樣複雜的流程 該不會用一大串 prompt 寫到底吧
我研究了一下 flow/DAG/graph 這種東西
Pattern Note #07:使用狀態機套件重構 AI Agent
嘗試了一些實作 但是非常不滿意 我好像搞錯方向
做成 saas 的重點好像不是狀態機 而是 別把流程寫在 code 內 應該要寫在 data 內
Pattern Note #08:基於 graph engine 驅動的 AI Agent
Pattern Note #09:把 graph engine 加上 actions 支援外部動作
前端面板 UI
https://github.com/xyflow/xyflow
拖拉式 節點編輯器套件
omnisend 這種節點式服務的 schema
分為 Definition layer 與 Runtime layer
- Definition layer:模板/工作流本身(可編輯、可發布)
- Runtime layer:每次執行的 session/run(狀態、log、結果)
Definition layer
automations
- id, store_id, name, status(active/paused), created_at
automation_versions
- id, automation_id, version, status(draft/published), published_at
- 版本是必要的:不然一改 flow,正在跑的人會炸
automation_nodes
- id, automation_version_id
- node_key(畫布上的唯一 id)
- type(trigger / wait / condition / action_send / split / end …)
- position_x, position_y
- config_json(每種 node 的參數)
automation_edges
- id, automation_version_id
- source_node_key, target_node_key
- condition_json(例如 “if opened within 24h” / “order_total > 1000”)
- label
automation_triggers
- automation_version_id
- event_type(signup, order_created, cart_abandoned, product_viewed…)
- filter_json(特定商品/金額/來源/UTM/國家…)
- dedupe_policy_json(同事件多久只觸發一次)
- Omnisend 的 trigger 多半是「某個事件發生」+「事件的條件」。
Runtime layer
Omnisend 最關鍵:同一個 flow 會對 每個 contact 建立一個 instance。
contacts
- id, store_id
- email, phone, push_token…
- consent_state(訂閱狀態、SMS 同意)
- attributes_json(姓名、生日、tags…)
events
- id, store_id, contact_id
- type(order_created, email_opened, product_viewed…)
- payload_json(訂單金額、商品 id、browser info…)
- occurred_at
- external_id(Shopify order id 等)
- 索引很重要:(store_id, type, occurred_at)、(contact_id, occurred_at)
automation_instances / enrollments(這個人被塞進這條 flow)
- id, automation_version_id, contact_id
- status(active/paused/completed/exited)
- current_node_key
- state_json(例如等待到期時間、已發送哪些訊息、分支選擇)
- entered_at, updated_at
等待節點(wait 2 days / until next Monday)會落到 job queue:
scheduled_tasks
- id, enrollment_id, run_at, task_type, payload_json, status
回到案主需要的 Saas 資料庫結構
一樣分 definition layer vs runtime layer
1) workflows
- id (pk)
- workspace_id (tenant)
- name
- description
- status (active/archived)
- created_at
- 一個 workflow = 一個“自動化/agent/flow”的產品單位。
2) workflow_versions
- id (pk)
- workflow_id (fk)
- version (int)
- status (draft/published/deprecated)
- published_at
- created_by
- created_at
- Omnisend/Zapier 類產品一定有版本,不然一改線上就炸。
3) workflow_nodes
- id (pk)
- workflow_version_id (fk)
- node_key (string, e.g. "login.collect_details") // 你 UI 的節點 id
- type (start/router/tool/llm/wait/webhook/handoff/end...)
- name (給 UI 顯示)
- position_x, position_y (畫布座標)
- config_json (jsonb) // 該 node 的參數,如 tool_name、prompt_template_id、routes...
- created_at
- node 的配置永遠用 jsonb,因為節點種類會一直長。
4) workflow_edges
- id (pk)
- workflow_version_id (fk)
- source_node_key
- target_node_key
- condition_json (jsonb)
- 例:{ "type":"intent", "equals":"billing" }
- 或:{ "type":"expr", "expr":"tools.preview.success == true" }
- label (string, UI 顯示用)
- created_at
- edge 表達「轉移條件」。UI 拉線就是新增 edge。
5) workflow_triggers
- id (pk)
- workflow_id 或 workflow_version_id(看你要綁定發布版還是工作流本體)
- channel (voice/whatsapp/webhook/widget)
- type (incoming_call/incoming_message/schedule/webhook/manual)
- config_json (jsonb)
- e.g. phone_number, webhook_path, schedule_cron, keyword filter
- is_enabled
- created_at
- Omnisend 的 trigger 就是:訂閱、下單、點擊… 你這裡是:來電、來訊息、webhook、排程。
然後 runtime layer 就是
8) workflow_runs
- id (pk)
- workspace_id
- workflow_id
- workflow_version_id (重要:跑的是哪個發布版)
- channel (voice/whatsapp/web)
- external_thread_id (Twilio callSid / WhatsApp conversation id)
- status (running/waiting/ended/error)
- current_node_key
- state_json (jsonb) // slot/state/flags(你現在的 $orderState)
- created_at, updated_at
- 這張表是你現在 chat_sessions + agent_states 的合體。
Q&A
schema naming
saas 會叫 workflow
omnisend 這種自動化 會叫 automation
但兩者基本是一樣結構 是嗎
是的,結構本質上是一樣的,只是語意與產品定位不同。
可以這樣理解:
- workflow = 技術中性詞(engineering term)
- automation = 商業應用詞(marketing / SaaS term)
底層 schema 幾乎完全相同。
一句話總結 -> automation 是 workflow 的一種「產品化命名」。
為什麼 SaaS 會用不同名稱?
這是產品語意問題,不是技術問題。
- Engineering world:叫 workflow
因為它是通用概念:
- GitHub Actions workflow
- Temporal workflow
- Airflow workflow
- Zapier workflow
工程師習慣用 workflow。
- Marketing automation world:叫 automation
因為他們賣的是:
「自動化你的業務」
不是「工作流程」。
對使用者來說:
- automation = abandoned cart automation
- automation = welcome automation
比 workflow 更直覺。
Omnisend、Klaviyo、Mailchimp 都用 automation。
- AI world:叫 agent / flow / assistant
例如:
- OpenAI → assistant
- LangChain → agent
- n8n → workflow
- Voiceflow → flow
- Retell → agent
本質仍然是 workflow graph。
從資料庫角度看,完全相同
從 execution engine 看,更明顯是同一東西
current_node = X
execute node
evaluate edges
move to next_node
這樣的話 如果是 omnisend 他們實際資料庫 會叫 workflow 給工程師 還是 automation 統一商業語境更方便
結論:大多數像 Omnisend 這類產品,資料庫與程式碼會統一使用 automation,而不是 workflow。
原因不是技術,而是產品語境一致性(ubiquitous language)。
| Product |
UI term |
DB term(通常) |
| Omnisend |
automation |
automations |
| Klaviyo |
flow |
flows |
| Mailchimp |
journey / automation |
automations |
| Zapier |
workflow / zap |
zaps |
| n8n |
workflow |
workflows |
| Retell |
agent |
agents |
| Voiceflow |
agent / flow |
agents / flows |
| Temporal |
workflow |
workflows |
關鍵原則:DB naming 應反映 domain,而不是 implementation
否則 會導致
- 工程師 confusion
- API naming mismatch
- cognitive overhead
但 execution engine 內部仍會叫 workflow(概念層)
只是對外不用 workflow 這個詞。
心得
這個專案依然很有挑戰性
光是 SaaS 本身架構設計 就需要相當經驗了
再加上 llm 整合 非常有挑戰性
但是這套研究下來 我也比較有個底、知道大方向了