feat: W7 功能標籤分類、對白檢索與注入 (#8)

用 $.model.complete(Haiku)判斷回應的功能標籤,從角色索引取同標籤對白,
以只增不改的訊息附在使用者訊息之前;失敗一律退回不注入。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jeffery (jiantw83)
2026-10-14 17:44:09 +00:00
co-authored by Claude Sonnet 5.5
parent 1c4e0c5d98
commit d9e50d196b
12 changed files with 385 additions and 4 deletions
+51
View File
@@ -0,0 +1,51 @@
import { FUNCTIONS, isFunctionTag, type FunctionTag } from './tags'
export const CLASSIFY_TIMEOUT_MS = 3000
// 判斷的是「角色接下來最自然的回應」屬於哪種功能,不是使用者這句話本身的功能。
export function buildClassifyPrompt(userText: string): string {
const choices = Object.entries(FUNCTIONS)
.filter(([key]) => key !== 'other')
.map(([key, label]) => `${key}:${label}`)
.join('\n')
return [
'使用者正在請助理協助做事。判斷在他這句話之後,一位親切的角色最自然的第一句回應屬於下列哪一種功能。',
'只回答功能代號一個詞,不要解釋;都不適合就回答 other。',
'',
choices,
'',
'<使用者的話>',
userText.slice(0, 600),
'</使用者的話>',
].join('\n')
}
// 只認回覆開頭的第一個詞,且必須在白名單內;其餘一律視為判斷失敗。
export function parseFunction(reply: string): FunctionTag | null {
const word = reply.trim().toLowerCase().match(/^[a-z_]+/)?.[0]
return isFunctionTag(word) ? word : null
}
export type Complete = (request: {
model: string
prompt: string
maxTokens: number
effort: 'low'
timeoutMs: number
}) => Promise<{ isAnswered: boolean; text?: string }>
// 任何失敗(逾時、API 錯誤、空回覆、白名單外)都回傳 null,由呼叫端退回不注入。
export async function classify(complete: Complete, userText: string): Promise<FunctionTag | null> {
try {
const r = await complete({
model: 'haiku',
prompt: buildClassifyPrompt(userText),
maxTokens: 16,
effort: 'low',
timeoutMs: CLASSIFY_TIMEOUT_MS,
})
return r.isAnswered && r.text ? parseFunction(r.text) : null
} catch {
return null
}
}