report-analytics 的 budget RPC 跑過完整 high/medium/low confidence pattern,直接 generalize 即可。業界做法很類似(LinkedIn 的 cold-start ranker、Spotify 新用戶推薦)。
{version, intent, widgets, sources, ...})。這是重造輪子:
tool_use blocks,LLM 可以多次呼叫generateObject() + Zod schema 一行解UIResponse schema,丟給 Anthropic 的 output_format。LLM 自動只能輸出合法物件,違規直接 retry。不要自己寫 validator。
// v1 思路(4 hops)
boss_q → classifier_llm → orchestrator_logic → manifest_llm → ui
// v2 簡化(2 hops)
boss_q → claude_sonnet(tools=[...], outputSchema=UIResponse) → ui
↓ ↑
執行 tools ──────────────────
少 50% 系統複雜度,延遲少一半。
KPICard / LineChart / BarComparison / InsightCallout / DataTabletrust = data_volume × 0.5 + algorithm × 0.3 + statistical × 0.2 然後 map bronze/silver/gold。聽起來精準,實際上失真:
{ data, sample_size, confidence_method },UI 根據 metadata 決定渲染樣式。不要一刀切。
| 產品 / 文章 | 對標哪部分 | 能學什麼 |
|---|---|---|
| Tableau Pulse (Salesforce, 2024) |
整體 generative BI | 「Metrics + AI insights + 動態 dashboard」三件套。每個 metric 卡片有 AI 自動生的 insight 段落 + drill-down。最接近 ChefsMate 想做的。 |
| Snowflake Cortex Analyst (2024) |
自然語言 → SQL 的 safety | 「Semantic Model」概念 — 不讓 LLM 自己生 SQL,而是定義 metrics / dimensions / measures,LLM 只能組合既有概念。你的「tool whitelist」就是這個。 |
| Vercel AI SDK Generative UI (2024-2025 持續) |
動態 widget 渲染 | 用 React Server Components 讓 AI 「pick」 components from registry。Zod schema 限制輸出。直接用 SDK,別自己造 manifest。 |
| Anthropic「Building Effective Agents」 (Dec 2024,Anthropic 官方文章) |
架構決策 | 「Use the simplest pattern that works. Add complexity only when measurable need.」直接打中 v1 的過度工程問題。 |
| Glean / Notion AI Q&A | Source citation UX | 每個 AI 回答都連到具體 source document + paragraph。Glean 內部叫「Trust Graph」。你的「source panel」就是這個。 |
| Hex Magic | Notebook-style 互動探索 | AI 給「建議下一格 cell」(SQL / chart / explanation),老闆可以接受、改、忽略。比 NotebookLM 更接近 BI 場景。 |
| Perplexity / OpenAI 的 ChatGPT Search | Followup questions 設計 | 每次回答後給 3-5 個 followup 建議(你 v1 的 followup_prompts 對)。Perplexity 做最好。 |
| OpenAI Structured Outputs (2024-08) |
嚴格 schema 強制 | 把 Zod schema 餵 OpenAI,保證 100% 符合 schema(token-by-token grammar enforcement)。Anthropic 用「prefill + tool_use」達同效。 |
anthropic.com/research/building-effective-agents)sdk.vercel.ai/docs)streamUI() 讓 LLM 直接 stream React component。Zod schema 限制 props。┌────────────────────────────────────────────────────────────────┐
│ Layer B — Renderer (Web / iOS) │
│ - 對話介面 + 預設問題 chips │
│ - 動態 component canvas(Vercel AI SDK Generative UI pattern) │
│ - Zod schema 限制 AI 只能輸出合法 component │
└──────────────────────▲─────────────────────────────────────────┘
│ Streamed UIResponse (schema-validated)
┌──────────────────────┴─────────────────────────────────────────┐
│ Layer A — Single LLM with Tools (Claude Sonnet 4) │
│ - 一個 LLM call,參數帶 tools[] + outputSchema │
│ - tools[] = 既有 report-analytics 7 演算法 wrap + 新加 5 個 │
│ - outputSchema = Zod-defined UIResponse │
│ - 自動 tool_use(可能多輪)→ 最後輸出 UIResponse │
└────────────┬─────────────────────────────────────▲─────────────┘
│ tool calls │ tool results
▼ │
┌────┴──────────────────────────────────────┴────┐
│ Tool Registry(各 tool = TypeScript function) │
│ - 每個 tool 有 Zod input/output schema │
│ - RLS gate 在每個 tool 內 │
│ - 直接 query Supabase RPC,不過 manifest 層 │
└────────────────────────────────────────────────┘
// tools/registry.ts
const tools = {
get_revenue_summary: {
description: '取得指定期間總營收 / 客單價 / 來客數',
input_schema: z.object({
period: z.enum(['today', 'this_week', 'this_month', /*...*/]),
restaurant_id: z.string().uuid(),
}),
execute: async (input, ctx) => {
// RLS check + Supabase RPC
return { revenue: 12345, guest_count: 45, ... }
},
},
// 其他 tools...
}
// ui-schema.ts
export const UIResponseSchema = z.object({
summary_md: z.string(),
trust_label: z.enum(['high', 'medium', 'low', 'insufficient_data']),
widgets: z.array(z.discriminatedUnion('type', [
KPICardSchema,
LineChartSchema,
BarComparisonSchema,
InsightCalloutSchema,
DataTableSchema,
])),
sources: z.array(z.object({ tool: z.string(), params: z.any() })),
followups: z.array(z.string()).max(3),
})
// api/smart-report/ask.ts
export async function POST(req) {
const { question, restaurant_id } = await req.json()
// 一個 LLM call 搞定
const result = await anthropic.messages.create({
model: 'claude-sonnet-4',
tools: Object.entries(tools).map(([name, def]) => ({
name,
description: def.description,
input_schema: def.input_schema,
})),
messages: [
{ role: 'user', content: question },
],
// 強制最終輸出符合 UIResponse schema
// (Anthropic 用 prefill + tool_use 達同效;OpenAI 用 response_format)
})
// tool_use 結果丟回 LLM 直到他輸出 UIResponse
// 整個過程是 streaming,UI 可以 progressive render
return NextResponse.json(UIResponseSchema.parse(result.final_output))
}
| 議題 | 該怎麼做 |
|---|---|
| Caching |
- 同 restaurant + 同問題 + 30 分鐘內:直接 return cached UIResponse - Tool results 自己 cache(今日營收 1 分鐘 / 月營收 1 小時) - Vercel Edge Functions unstable_cache 或 Supabase 內建 cache
|
| Streaming UI |
- 用 Vercel AI SDK streamUI()- 老闆按 enter → 0.5s 內看到 「思考中...」 - 1-3s 內陸續 stream 出 widget(像 ChatGPT) - 比 wait 5 秒一次 render 體感快很多 |
| Cost observability |
- 每次 LLM call 記 token 用量 + 成本到 llm_usage 表- Daily report 給老闆「今天 AI 花了 NT$X」 - 預算超過 alert(Telegram 推 admin) |
| Audit log |
- 每個問題 + 結果存 smart_report_history 表- 老闆可以回看「上週問過什麼」 - 也是法規 / 信任的基礎(老闆能 prove 他做決策時看的是什麼資料) |
| Hallucination 防護 |
- System prompt 嚴格約束:「所有數字必須來自 tool result,不可自己算 / 估計」 - Schema validation:UIResponse 內每個數字必須有 source: tool_call_id- 沒 source 的數字 = reject + 重生 - Random spot-check:1% 的回答自動跑 evaluator LLM 檢查是否符合 tool result |
| Multi-restaurant aggregation |
- 集團老闆(海邊小巫 + 溪邊小巫)會問「兩家比起來?」 - Tool 內建 restaurant_ids[] 支援- Permission gate:check 老闆對每家都有讀權 |
| Offline / cache miss fallback |
- LLM API 失敗:回 「智慧問答暫時無法,顯示固定報表」+ 既有 ReportAPIClient - 不要 hard fail |
| Weekly digest email |
- Cron 週一早上自動跑 5 個固定問題 → AI summarize → email 給老闆 - 這是 Tableau Pulse 殺手鐧 — 老闆不用主動問就有 insight |
KPICard / LineChart / BarComparison / InsightCallout(70% 場景覆蓋)我的 v1 spec 方向對,但過度工程約 30-40%。真實業界做法更簡單:
Phase 0 改 1 週可結束(原本估 1-2 週),因為大部分基礎設施都不用自造:
等老闆回答上面 6 個問題,可以 1-2 週內出 MVP demo。