Summary Customization
目錄
概述
VAS 摘要生成支援兩種互斥模式:
builtin— 套用 VAS 內建模板(會議、訪談、課程、醫療等場景),客戶只需指定templateslug。custom— 客戶完整提供 prompt 取代內建模板規則。系統仍會自動處理輸出語言與純文字後處理。
兩種模式在 REST / WebSocket / SSE 三條入口的請求欄位語意一致,差異僅在欄位命名前綴。
適用場景
| 模式 | 適用情境 |
|---|---|
builtin | 一般會議、訪談、課程、醫療諮詢等可直接套用內建模板的場景 |
custom | 客戶有自家 prompt 規則(特定欄位輸出、客製格式、領域專用結構等),需要完全自定義摘要生成行為 |
互斥規則
| 模式 | template | prompt / prompt_slug |
|---|---|---|
builtin | 必填 | 禁帶 |
custom | 禁帶 | 必填(兩者皆必填) |
違反互斥規則 → summary_mode_field_mismatch(REST 400 / SSE 422)。
前置準備
1. 取得 API Key
詳見 認證機制。
2.(builtin 路徑)瀏覽可用模板
呼叫 GET /api/v1/summary-templates 取得內建模板列表(支援 ?category=summary|medical|legal|all 篩選)。
需要參考特定模板的設計時,可呼叫 GET /api/v1/summary-templates/{slug} 取得模板內容作為設計參考。
3.(custom 路徑)設計自家 prompt
撰寫 prompt 時需自行包含:
- 輸出語言要求(如「請以繁體中文輸出」)
- 結構與欄位描述(要萃取哪些資訊、章節順序)
- 輸出格式要求(JSON / 條列 / 段落)
custom mode 下客戶 prompt 取代內建模板規則,這些要素必須由客戶在 prompt 內自帶。
兩種模式
builtin mode
{
"mode": "builtin",
"template": "meeting",
"language": "zh-TW",
"plain_text": true
}
效果:
- 套用指定 slug 的內建模板生成摘要
templateslug 寫入後端記錄- 後端記錄寫入
summary_mode: "builtin"、summary_template: <slug>(可透過init_summaryevent 取回)
custom mode
{
"mode": "custom",
"prompt": "你是皮膚科專科助手。請從逐字稿萃取:1) 主訴 2) Fitzpatrick 分型 3) 過敏原史。輸出 JSON。",
"prompt_slug": "skin-clinic-acme-v2",
"language": "zh-TW",
"plain_text": true
}
效果:
- 不查詢內建模板,客戶 prompt 取代模板規則
prompt_slug寫入後端記錄(pass-through,供歷史查詢)prompt原文強制 snapshot 為summary_prompt_snapshot欄位(唯一重建依據,可透過init_summaryevent 取回)
prompt_slug命名建議帶版本(如acme-v1→acme-v2),供整合方追溯。
三條入口
三條入口語意一致、欄位命名前綴不同:
| 入口 | 欄位前綴 | 用途 |
|---|---|---|
REST POST /api/v1/summary | (無前綴)mode / template / prompt / prompt_slug | 對任意逐字稿生成摘要 |
WebSocket start action | summary_* | 即時錄音結束後自動生成 |
SSE regenerate/summary | (無前綴,SSE 使用 camelCase)mode / template / prompt / promptSlug | 對既有錄音重新生成摘要 |
REST POST /api/v1/summary
對任意逐字稿(含外部來源)生成摘要,回應以 SSE 串流逐段送出。
完整規格:reference/rest/summary.md
curl -N -X POST "https://vas-poc.vurbo.ai/api/v1/summary" \
-H "Authorization: Bearer vas_..." \
-H "Content-Type: application/json" \
-d '{
"content": "...逐字稿...",
"mode": "custom",
"prompt": "你是會議分析助手...",
"prompt_slug": "acme-meeting-v1",
"language": "zh-TW",
"plain_text": true
}'
⚠️ 此端點認證使用
Authorization: Bearer <api_key>,與其他 REST 端點的X-API-Key不同。
WebSocket start action
即時錄音的 start action 帶入摘要設定,session 結束後自動生成摘要並透過 summary_done event 通知。
完整規格:reference/websocket/voice-translation.md
{
"type": "voice-translation",
"data": {
"action": "start",
"transcription_languages": ["zh-TW"],
"translation_languages": ["en-US"],
"type": "transcribe",
"audio_format": "pcm",
"summary_mode": "custom",
"summary_prompt": "你是會議分析助手...",
"summary_prompt_slug": "acme-meeting-v1",
"summary_language": "zh-TW",
"summary_plain_text": true
}
}
摘要完成事件:
{
"type": "summary_done",
"data": {
"summary_mode": "custom",
"summary_template": "acme-meeting-v1",
"summary_plain_text": true,
"tokens_used": 890,
"summary_fallback_level": null,
"summary_dropped_segments": null
}
}
summary_template為 effective slug — custom mode 下回傳客戶 slug(即送出時的summary_prompt_slug)。
SSE regenerate/summary
對既有錄音重新生成摘要,拆成兩個端點:
| 方法 | 用途 | 寫 DB | 儲存逐字稿 | 計費 |
|---|---|---|---|---|
GET /api/v1/sse/regenerate/summary/{taskId} | 預覽(試跑、比較不同 prompt 結果) | ❌ | ❌ | ✅ |
POST /api/v1/sse/regenerate/summary/{taskId} | 存檔(正式儲存) | ✅ | ✅ + bump revision | ✅ |
完整規格:reference/sse/regenerate-summary.md
# 預覽
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/regenerate/summary/550e8400-...?mode=custom&prompt=...&promptSlug=acme-v2&plainText=true" \
-H "X-API-Key: vas_..."
# 存檔
curl -N -X POST "https://vas-poc.vurbo.ai/api/v1/sse/regenerate/summary/550e8400-..." \
-H "X-API-Key: vas_..." \
-H "Content-Type: application/json" \
-d '{
"mode": "custom",
"prompt": "...",
"promptSlug": "acme-v2",
"plainText": true
}'
doneevent 帶persisted: bool— 客戶端可直接從 payload 判斷此次是否已儲存,不需要再依 HTTP method 推斷。
逐字稿記錄欄位
摘要完成後,下列欄位寫入逐字稿記錄的 top-level(不是 nested 在 summary 物件下):
| 欄位 | 出現條件 | 說明 |
|---|---|---|
summary_mode | 永遠 | "builtin" / "custom" |
summary_template | 永遠 | effective slug — builtin → 內建 slug;custom → 客戶 slug |
summary_plain_text | 永遠 | bool |
summary_prompt_snapshot | 僅 custom mode | 客戶原樣傳入的 prompt 內容(強制 snapshot,是唯一重建依據) |
summary_fallback_level | 僅 fallback 觸發時 | 值為 2 或 3,代表本次摘要實際走的內容過濾 fallback 路徑。標準模式直接成功則 omit |
summary_dropped_segments | 僅 summary_fallback_level=3 時 | 被省略的逐字稿段 indices(原序整數陣列) |
GET /api/v1/sse/history/transcribe/{taskId} 的 init_summary 事件也帶上述欄位(custom mode 才有 prompt_snapshot、fallback 觸發時才有 fallback_level / dropped_segments)。
兩個欄位的角色
summary_prompt_snapshot= 客戶意圖(原 prompt 內容)summary_fallback_level= 實際執行路徑(標準 / 中性 / 段落省略)
兩欄位互補:snapshot 可用於審計與重建;fallback_level 可用於告知用戶該次摘要實際發生了什麼。
敏感詞與不雅字眼處理
VAS 處理敏感詞(不雅字眼、情緒性或敏感內容)的方式依「來源」不同分為三條路徑。
API 層不會主動拒絕含敏感詞的請求,所有處理都在 runtime 進行降級或遮罩。客戶若希望事前擋下,請於前端 UI 自行做關鍵字檢查(見前端前置驗證)。
速查表
| 來源 | 觸發機制 | 摘要會生成嗎? | 客戶端如何得知 |
|---|---|---|---|
客戶 prompt 本身含敏感詞 | 自動進入中性模式 | ✅ | summary_done.summary_fallback_level === 2 |
| 逐字稿原文(STT 層) | options.profanity_handling 遮罩 / 移除 | ✅ | 逐字稿原文已處理,無額外通知 |
| 逐字稿原文(摘要層) | 自動省略觸發段落 | ✅ | summary_done.summary_fallback_level === 3 |
| 多條路徑都失敗 | summary_error | ❌ | error_code: llm_content_filtered |
來源 1:客戶 prompt 含敏感詞 → 中性模式
當系統偵測到客戶 prompt 內容觸發內容過濾時:
| 行為 |
|---|
| 自動改以中性指令生成摘要,不拒絕、不報錯 |
客戶原 prompt 仍以 summary_prompt_snapshot 形式儲存,作為審計依據 |
summary_done event 帶 summary_fallback_level: 2 通知客戶端 |
UI 建議顯示:「您的自訂指令含過濾無法處理的詞彙,已使用中性模式產生摘要」。
來源 2:逐字稿原文(STT 層遮罩)
WebSocket start action 的 options.profanity_handling 欄位控制語音辨識輸出中的不雅字處理:
| 值 | 行為 |
|---|---|
mask(預設) | 不雅字以 *** 遮罩 |
removed | 不雅字從逐字稿中移除 |
raw | 保留原文不處理 |
詳見 WebSocket - Voice Translation 的 options 欄位。
此選項僅影響 STT 輸出(逐字稿原文)。與客戶
prompt中的敏感詞無關。
來源 3:逐字稿原文(摘要層段落省略)→ 段落省略模式
若 STT 層的 profanity_handling 設為 raw(或遮罩後仍觸發過濾),系統會在摘要生成時自動省略觸發過濾的段落:
| 行為 |
|---|
summary_done event 帶 summary_fallback_level: 3 |
summary_dropped_segments 列出被省略的逐字稿段 indices(原序整數陣列) |
| 客戶端可據此告知用戶實際省略了哪些區段 |
UI 建議顯示:「逐字稿含 N 段無法處理,已省略相關內容後產生摘要」(N = summary_dropped_segments.length)。
全數失敗 → llm_content_filtered
當客戶 prompt 與逐字稿同時含過量敏感內容、自動降級也無法產出合法摘要時:
| 行為 |
|---|
觸發 summary_error event,error_code: llm_content_filtered |
| 該次摘要不會儲存 |
UI 建議顯示:「本段內容無法產生摘要(內容過濾規則限制)」。
計費影響
觸發 fallback 時,系統可能需要多次呼叫 LLM 服務,所有呼叫都會進 usage_logs 計費。建議客戶將「觸發 fallback 的摘要」視為正常但較高 token 成本的事件。
規格範圍
| 路徑 | Fallback 整合狀態 |
|---|---|
| WebSocket realtime 摘要(錄音結束自動生成) | ✅ V1.5.5 起 |
| 檔案匯入摘要 | ✅ V1.5.5 起 |
SSE regenerate/summary 端點 | ⚠️ 尚未整合 — 觸發內容過濾時仍直接回 llm_content_filtered。若需自動降級,建議改用 POST /api/v1/summary REST 端點 |
客戶端對應實作
function renderSummary(summary, fallbackLevel, droppedSegments) {
switch (fallbackLevel) {
case undefined:
case null:
case 1:
return summary;
case 2:
return summary + '\n\n(您的自訂指令含過濾詞彙,已使用中性模式產生摘要)';
case 3:
return summary + `\n\n(逐字稿含 ${droppedSegments.length} 段無法處理,已省略相關內容後產生摘要)`;
}
}
前端前置驗證(選用)
若整合方希望在送出請求前自行擋下含敏感詞的 prompt(節省 fallback 帶來的額外 token 計費),可在前端 UI 加上關鍵字檢查。
VAS 不提供 API 層的 prompt 預檢端點 — 是否做前置驗證、用哪份關鍵字清單,完全由客戶端決定。
安全與限制
內建安全防護
Custom mode 下系統會自動為客戶 prompt 加上安全防護機制,包含:
- 內容中性化指引:要求 LLM 對於原文中可能出現的口語化、情緒性或敏感詞彙,以中性、客觀的語言概括其意旨,避免逐字引用。目的是降低標準模式觸發內容過濾的機率。
- 指令注入防護:避免客戶 prompt 中的指令意外覆蓋系統規則。
防護機制不曝露給客戶端設定,客戶 prompt 原文仍透過 summary_prompt_snapshot 欄位儲存作為審計依據。
⚠️ 防護機制非萬無一失。客戶應不要將不可信的終端用戶輸入直接拼進
prompt,prompt injection 風險自負。
字元與長度限制
| 欄位 | 限制 |
|---|---|
prompt / summary_prompt | ≤ 2000 字元 |
prompt_slug / summary_prompt_slug | ≤ 64 字元、Unicode、禁控制字元(\n / \r / \t / \0 等) |
content(REST POST /api/v1/summary 的逐字稿輸入) | ≤ 100,000 字元 |
跨租戶隔離
客戶 prompt 與摘要結果跨租戶完全隔離(Session-scope,無記憶持久化)。
Server log
VAS server log 不會 記錄 prompt 或逐字稿全文(僅 log 長度與 slug)。LLM 錯誤訊息會被 sanitize 不曝露 raw error 給客戶端,僅標示 provider。
完整範例
Node.js — REST POST /api/v1/summary(custom mode)
const response = await fetch('https://vas-poc.vurbo.ai/api/v1/summary', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: transcriptText,
mode: 'custom',
prompt: `你是會議分析助手。請從逐字稿萃取:
1. 主要決議事項(每項一句話)
2. 待辦工作(含負責人、期限)
3. 風險與阻礙
輸出格式:JSON。請以繁體中文輸出。`,
prompt_slug: 'acme-meeting-v2',
language: 'zh-TW',
plain_text: true,
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value);
for (const line of buffer.split('\n\n')) {
if (line.startsWith('event: chunk')) {
const data = JSON.parse(line.split('\ndata: ')[1]);
process.stdout.write(data.content);
} else if (line.startsWith('event: done')) {
const data = JSON.parse(line.split('\ndata: ')[1]);
console.log('\n\n--- 完成 ---');
console.log(`tokens: input=${data.tokens_used.input}, output=${data.tokens_used.output}`);
console.log(`prompt_snapshot 已儲存:${data.prompt_snapshot ? '是' : '否'}`);
}
}
}
Python — SSE 重新生成摘要(custom mode 預覽再存檔)
import requests
BASE = "https://vas-poc.vurbo.ai/api/v1/sse/regenerate/summary"
TASK_ID = "550e8400-e29b-41d4-a716-446655440000"
HEADERS = {"X-API-Key": "vas_..."}
# 1. 先用 GET 預覽
preview_params = {
"mode": "custom",
"prompt": "你是醫療摘要助手...",
"promptSlug": "clinic-acme-v3",
"plainText": "true",
}
with requests.get(f"{BASE}/{TASK_ID}", params=preview_params, headers=HEADERS, stream=True) as r:
for line in r.iter_lines():
if line.startswith(b"data: "):
print(line[6:].decode())
# 2. 客戶確認後用 POST 存檔
persist_body = {
"mode": "custom",
"prompt": "你是醫療摘要助手...",
"promptSlug": "clinic-acme-v3",
"plainText": True,
}
with requests.post(f"{BASE}/{TASK_ID}", json=persist_body, headers=HEADERS, stream=True) as r:
for line in r.iter_lines():
if line.startswith(b"data: "):
print(line[6:].decode())
WebSocket — 即時錄音 custom 摘要
ws.send(JSON.stringify({
type: 'voice-translation',
data: {
action: 'start',
transcription_languages: ['zh-TW'],
translation_languages: [],
type: 'transcribe',
audio_format: 'pcm',
summary_mode: 'custom',
summary_prompt: '你是法律會議助手。請輸出:1) 爭點 2) 立場 3) 結論。',
summary_prompt_slug: 'legal-firm-acme-v1',
summary_language: 'zh-TW',
summary_plain_text: true,
},
}));
ws.addEventListener('message', (evt) => {
const msg = JSON.parse(evt.data);
if (msg.type === 'summary_done') {
console.log('摘要完成', msg.data);
if (msg.data.summary_fallback_level === 2) {
showBanner('您的自訂指令含過濾詞彙,已使用中性模式產生摘要');
} else if (msg.data.summary_fallback_level === 3) {
showBanner(`逐字稿含 ${msg.data.summary_dropped_segments.length} 段無法處理,已省略`);
}
} else if (msg.type === 'summary_error') {
console.error('摘要失敗', msg.data.error_code, msg.data.message);
}
});
錯誤碼
| 錯誤碼 | HTTP | 觸發條件 |
|---|---|---|
summary_invalid_mode | 422(SSE) / 400(其他) | mode 不是 builtin / custom |
summary_mode_field_mismatch | 422 / 400 | mode 與欄位組合不符(必填缺漏 / 禁帶被帶入) |
summary_prompt_too_long | 422 / 400 | prompt 超過 2000 字元 |
summary_prompt_slug_too_long | 422 / 400 | prompt_slug 超過 64 字元 |
summary_prompt_slug_invalid | 422 / 400 | prompt_slug 含控制字元(\n / \r / \t / \0 等) |
template_not_found | 404 | (builtin mode)指定 slug 的模板不存在或已停用 |
llm_content_filtered | 400 | 內容過濾擋下(客戶 prompt 或逐字稿含敏感詞),且 fallback chain 全數失敗或該端點未整合 fallback |
summary_failed | 500 | 摘要生成失敗 |
summary_timeout | 504 | 生成逾時 |
完整錯誤碼一覽見 錯誤碼參考。
相關文件
| 文件 | 說明 |
|---|---|
| POST /api/v1/summary | REST 摘要端點完整規格 |
| Summary Templates API | 內建模板列表與模板查詢 |
| SSE - 重新生成摘要 | GET 預覽 / POST 存檔兩端點規格 |
| WebSocket - Voice Translation | start action 的 summary_* 欄位 |
| SSE - 歷史紀錄 | init_summary event 的 mode / template / prompt_snapshot 欄位 |
| 錯誤碼參考 | 完整錯誤碼一覽 |
| 版本紀錄 V1.5.5 | builtin/custom 互斥規格與內容過濾自動降級推出版本 |
版本:V1.5.7 最後更新:2026-05-20