用 $.model.complete(Haiku)判斷回應的功能標籤,從角色索引取同標籤對白, 以只增不改的訊息附在使用者訊息之前;失敗一律退回不注入。 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
52 lines
1.7 KiB
TypeScript
52 lines
1.7 KiB
TypeScript
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
|
||
}
|
||
}
|