{"version":3,"sources":["../../src/voice/keyterm_detection.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport { z } from 'zod';\nimport { LLM as InferenceLLM } from '../inference/llm.js';\nimport { ChatContext, ChatMessage, type FunctionCall } from '../llm/chat_context.js';\nimport { LLM } from '../llm/llm.js';\nimport { tool } from '../llm/tool_context.js';\nimport { log } from '../log.js';\nimport type { LLMMetrics } from '../metrics/base.js';\nimport type { STT } from '../stt/stt.js';\nimport { Task, delay } from '../utils.js';\nimport { AgentSessionEventTypes, type ConversationItemAddedEvent } from './events.js';\n\n/**\n * Keyterm biasing for STTs that accept a term list.\n *\n * Can be passed as a plain object:\n *\n * ```ts\n * new AgentSession({\n *   keytermsOptions: {\n *     keyterms: ['LiveKit', 'Acme Corp'],\n *     keytermDetection: { enabled: true, turnInterval: 1 },\n *   },\n * });\n * ```\n */\nexport interface KeytermsOptions {\n  /** Static keyterms applied wherever the STT accepts a term list; never touched by detection. */\n  keyterms?: string[];\n  /** LLM-based keyterm extraction, for STTs that accept a term list. */\n  keytermDetection?: KeytermDetectionOptions;\n}\n\n/**\n * Configuration for automatic keyterm detection.\n *\n * Lives under the `keytermDetection` key of {@link KeytermsOptions}. Absent or\n * `{ enabled: false }` keeps detection off.\n */\nexport interface KeytermDetectionOptions {\n  /** Whether to run the background detector. Defaults to `false`. */\n  enabled?: boolean;\n  /**\n   * LLM used for extraction. An `LLM` instance, or a model string (e.g.\n   * `\"google/gemini-3.5-flash\"`) resolved via the inference gateway. Defaults to a\n   * built-in detection model; the agent's own LLM is not used.\n   */\n  llm?: LLM | string;\n  /** Run a pass once per N user turns. Defaults to `1`. */\n  turnInterval?: number;\n  /** Cap on the confirmed (applied) detected keyterms if provided. Defaults to `undefined`. */\n  maxKeyterms?: number;\n  /** Override the built-in extraction prompt. */\n  instructions?: string;\n  /**\n   * Milliseconds a single detection pass may run before it is dropped (no keyterm change).\n   * Defaults to `10_000`. Raise it if a slow detection `llm` needs longer.\n   */\n  timeout?: number;\n}\n\n// bound a single pass so a stuck LLM call can't hold the single-flight guard forever and\n// stall detection for the rest of the call; a timed-out pass simply makes no change\nconst DETECTION_TIMEOUT = 10_000;\n\nconst KEYTERM_DETECTION_DEFAULTS = {\n  enabled: false,\n  llm: undefined,\n  turnInterval: 1,\n  maxKeyterms: undefined,\n  instructions: undefined,\n  timeout: DETECTION_TIMEOUT,\n} as const;\n\n/** A fully-defaulted keyterm-detection config. @internal */\nexport interface ResolvedKeytermDetectionOptions {\n  enabled: boolean;\n  llm?: LLM | string;\n  turnInterval: number;\n  maxKeyterms?: number;\n  instructions?: string;\n  timeout: number;\n}\n\n/** A fully-defaulted keyterms config. @internal */\nexport interface ResolvedKeytermsOptions {\n  keyterms: string[];\n  keytermDetection: ResolvedKeytermDetectionOptions;\n}\n\n/** A pending term not confirmed within this many passes is dropped. @internal */\nexport const PENDING_TTL = 3;\nconst MAX_TRANSCRIPT_MESSAGES = 12;\n\n// default model for keyterm extraction when `keytermDetection.llm` is not set\nconst DEFAULT_DETECTION_MODEL = 'google/gemma-4-31b-it';\n\n// set LK_KEYTERMS_DEBUG=1 to log the input/output of every detection pass\nconst lkKeytermsDebug = parseInt(process.env.LK_KEYTERMS_DEBUG ?? '0', 10) !== 0;\n\n/** Return a fully-defaulted keyterm-detection config (`enabled` defaults to false). @internal */\nexport function resolveDetection(\n  config?: KeytermDetectionOptions,\n): ResolvedKeytermDetectionOptions {\n  const merged = { ...KEYTERM_DETECTION_DEFAULTS, ...(config ?? {}) };\n  return {\n    enabled: merged.enabled ?? false,\n    llm: merged.llm,\n    turnInterval: merged.turnInterval ?? 1,\n    maxKeyterms: merged.maxKeyterms,\n    instructions: merged.instructions,\n    timeout: merged.timeout ?? DETECTION_TIMEOUT,\n  };\n}\n\n/** Return a fully-defaulted keyterms config. @internal */\nexport function resolveKeytermsOptions(config?: KeytermsOptions): ResolvedKeytermsOptions {\n  const cfg = config ?? {};\n  return {\n    keyterms: [...(cfg.keyterms ?? [])],\n    keytermDetection: resolveDetection(cfg.keytermDetection),\n  };\n}\n\n/**\n * Resolve the configured detection `llm`: an `LLM` instance is used directly; a\n * model string (or the default model when unset) is created via the inference gateway.\n */\nfunction resolveDetectionLLM(configured?: LLM | string): LLM | undefined {\n  if (configured instanceof LLM) {\n    return configured;\n  }\n  const model = typeof configured === 'string' ? configured : DEFAULT_DETECTION_MODEL;\n  try {\n    return InferenceLLM.fromModelString(model);\n  } catch {\n    // never let detection setup break the session\n    log().warn(`keyterm detection: could not create detection LLM ${model}; skipping`);\n    return undefined;\n  }\n}\n\nconst DEFAULT_KEYTERM_INSTRUCTIONS = `\\\nYou maintain STT keyterms that bias a recognizer toward the correct spelling of distinctive \\\nwords (names, places, companies, products, technical terms). Each turn, adjust them with one \\\n\\`record_keyterms\\` call.\n\nA WRONG spelling biases the recognizer for the rest of the call with no recovery, so precision \\\nbeats coverage: apply only a spelling you can CORROBORATE, and when unsure change nothing.\n\nUSER lines are raw STT — often wrong, and the same error recurs, so repetition is NOT proof a \\\nspelling is right. ASSISTANT lines are the agent's own writing: trust the agent's confident use \\\nof its OWN names (brands, staff, locations) and confirm those promptly — but an assistant merely \\\nechoing the user's sounds, or hedging about a spelling, does NOT corroborate.\n\nCONFIRM a pending term only when corroborated by one of:\n  1. a letter-by-letter spell-out the assistant then accepts WITHOUT reservation — confirm \\\nexactly those letters, appending nothing;\n  2. the assistant's own confident use of that exact distinctive spelling;\n  3. an explicit user correction (\"no, not X — it's Y\").\nRecurrence alone never confirms.\n\nHEDGE RULE: if after a spell-out or name read-back the assistant signals the letters may be off \\\n(\"for now\", \"with that caveat\", \"may have that slightly off\", \"did I catch that?\", \"to be \\\nconfirmed\", \"I don't want to guess\", \"double-check\"), the spelling is unreliable — keep the term \\\nPENDING and never confirm it, EVEN IF the user replies \"yes\". Only a cleanly accepted spell-out \\\nconfirms.\n\nNever apply: a user-line word that sounds like a known term (it's that term misheard); a \\\ndistinctive name glued to an ordinary word (\"Blue Haven Hotel\" — keep the bare name pending); an \\\nodd phrase only the user says and the assistant never adopts; a fragment left by an interruption; \\\nordinary words or fillers.\n\nReport only CHANGES; never re-list an applied term.\n  - \\`pending\\`: a distinctive term seen but not yet corroborated;\n  - \\`confirm\\`: a pending term that just met the bar above;\n  - \\`remove\\`: only a spelling the user just corrected away. Applied terms are otherwise sticky.\nIf nothing meets the bar this turn, change nothing.`;\n\n// only used to elicit a structured tool call; never executed\nconst recordKeyterms = tool({\n  name: 'record_keyterms',\n  description: 'Update the STT keyterms based on the latest transcript.',\n  parameters: z.object({\n    pending: z\n      .array(z.string())\n      .describe('Distinctive terms seen but not yet trusted — tracked, not applied.'),\n    confirm: z\n      .array(z.string())\n      .describe('Pending terms the transcript has now corroborated — applied.'),\n    remove: z\n      .array(z.string())\n      .describe('Only a spelling the user corrected away; applied terms are otherwise sticky.'),\n  }),\n  execute: async () => {},\n});\n\n/**\n * The minimal session surface the detector needs. `AgentSession` satisfies this\n * structurally; tests can supply a lightweight fake.\n *\n */\nexport interface KeytermDetectorSession {\n  readonly history: ChatContext;\n  on(\n    event: AgentSessionEventTypes.ConversationItemAdded,\n    listener: (ev: ConversationItemAddedEvent) => void,\n  ): unknown;\n  off(\n    event: AgentSessionEventTypes.ConversationItemAdded,\n    listener: (ev: ConversationItemAddedEvent) => void,\n  ): unknown;\n}\n\nexport type KeytermDetectorCallbacks = {\n  ['metrics_collected']: (metrics: LLMMetrics) => void;\n};\n\n/**\n * Maintains the STT keyterm set and, when enabled, auto-detects keyterms during a call.\n *\n * Owned by the {@link AgentSession} so keyterm state survives agent handoffs. Each agent\n * activity binds it to that activity's STT via {@link KeytermDetector.start} and releases it via\n * {@link KeytermDetector.aclose}. When detection is on, an LLM extracts distinctive spellings from\n * the conversation; only confirmed terms are pushed to the STT, while pending terms are tracked\n * (and fed back to the detector) without biasing recognition.\n */\nexport class KeytermDetector extends (EventEmitter as new () => TypedEmitter<KeytermDetectorCallbacks>) {\n  private detection: ResolvedKeytermDetectionOptions;\n  private maxKeyterms?: number;\n  private turnInterval: number;\n  private instructions: string;\n  private detectionTimeout: number;\n\n  private staticTerms: string[];\n  /** confirmed terms, oldest first (for eviction) @internal */\n  _detectedTerms: string[] = [];\n  /** term -> pass it was added (for TTL) @internal */\n  _pendingTerms: Map<string, number> = new Map();\n  private tick = 0; // detection-pass counter\n\n  // bound per agent activity (see start/aclose)\n  private stt?: STT;\n  private llm?: LLM;\n  private session?: KeytermDetectorSession;\n  private turnCount = 0;\n  /** @internal */\n  _detectTask?: Task<void>;\n\n  #logger = log();\n\n  constructor(opts?: { staticKeyterms?: string[]; options?: KeytermDetectionOptions }) {\n    super();\n    const options = resolveDetection(opts?.options);\n    this.detection = options;\n    this.maxKeyterms = options.maxKeyterms;\n    this.turnInterval = Math.max(1, options.turnInterval);\n    this.instructions = options.instructions ?? DEFAULT_KEYTERM_INSTRUCTIONS;\n    this.detectionTimeout = options.timeout;\n\n    this.staticTerms = [...new Set(opts?.staticKeyterms ?? [])];\n    this.llm = options.llm instanceof LLM ? options.llm : undefined;\n  }\n\n  /** The effective list applied to the STT: static terms + confirmed detected terms. */\n  get keyterms(): string[] {\n    return [...new Set([...this.staticTerms, ...this._detectedTerms])];\n  }\n\n  get staticKeyterms(): string[] {\n    return [...this.staticTerms];\n  }\n\n  setStaticKeyterms(terms: string[]): void {\n    this.staticTerms = [...new Set(terms)];\n    if (this.stt !== undefined) {\n      this.stt._updateSessionKeyterms(this.keyterms);\n    }\n  }\n\n  swapStt(stt: STT | undefined): void {\n    if (stt === this.stt) {\n      return;\n    }\n    this.stt = stt;\n    if (stt !== undefined && (this.keyterms.length > 0 || stt.capabilities.keyterms)) {\n      stt._updateSessionKeyterms(this.keyterms);\n    }\n  }\n\n  /** Bind this activity's STT (always) and start detection (if enabled). */\n  start(session: KeytermDetectorSession, stt: STT): void {\n    // static keyterms must reach the recognizer even with detection disabled\n    if (stt !== this.stt) {\n      this.stt = stt;\n      // push even an empty list to a keyterm-capable STT: a reused instance may\n      // still hold session keyterms from a previous detector binding\n      if (this.keyterms.length > 0 || stt.capabilities.keyterms) {\n        this.stt._updateSessionKeyterms(this.keyterms);\n      }\n    }\n\n    if (!this.detection.enabled) {\n      return;\n    }\n\n    // don't waste LLM detection passes when no STT can consume the keyterms\n    if (!stt.capabilities.keyterms) {\n      this.#logger\n        .child({ stt: stt.label })\n        .warn(\n          'keyterm detection is enabled but the STT does not support keyterms; skipping detection',\n        );\n      return;\n    }\n\n    const detectLLM = resolveDetectionLLM(this.detection.llm);\n    if (detectLLM === undefined) {\n      this.#logger.warn('keyterm detection is enabled but no detection LLM is available; skipping');\n      return;\n    }\n\n    this.llm = detectLLM;\n    detectLLM.on('metrics_collected', this.forwardMetrics);\n    this.session = session;\n    this.turnCount = 0;\n    session.on(AgentSessionEventTypes.ConversationItemAdded, this.onConversationItemAdded);\n  }\n\n  /** Stop detection for the current activity; keyterm state is kept. */\n  async aclose(): Promise<void> {\n    if (this.llm !== undefined) {\n      this.llm.off('metrics_collected', this.forwardMetrics);\n    }\n    if (this.session !== undefined) {\n      this.session.off(AgentSessionEventTypes.ConversationItemAdded, this.onConversationItemAdded);\n      this.session = undefined;\n    }\n    if (this._detectTask !== undefined) {\n      await this._detectTask.cancelAndWait();\n      this._detectTask = undefined;\n    }\n  }\n\n  private forwardMetrics = (ev: LLMMetrics): void => {\n    this.emit('metrics_collected', ev);\n  };\n\n  private onConversationItemAdded = (ev: ConversationItemAddedEvent): void => {\n    const session = this.session;\n    if (session === undefined) {\n      return;\n    }\n\n    const item = ev.item;\n    // keyterm detection triggers on non-empty user turns\n    if (!(item instanceof ChatMessage) || item.role !== 'user' || !item.textContent) {\n      return;\n    }\n\n    this.turnCount += 1;\n    if (this.turnCount % this.turnInterval !== 0) {\n      return;\n    }\n\n    // single-flight: skip while a pass is still running\n    if (this._detectTask !== undefined && !this._detectTask.done) {\n      return;\n    }\n\n    // snapshot the transcript now so the pass isn't affected by later turns\n    const snapshot = KeytermDetector.snapshot(session);\n    this._detectTask = Task.from(async (controller) => {\n      try {\n        await this.runOnce(snapshot, controller.signal);\n      } catch (error) {\n        this.#logger.child({ error }).error('keyterm detection pass failed');\n        throw error;\n      }\n    });\n    // fire-and-forget: failures are logged above; keep the task awaitable without unhandled rejections\n    this._detectTask.result.catch(() => {});\n  };\n\n  private static snapshot(session: KeytermDetectorSession): ChatContext {\n    return session.history.copy({\n      excludeConfigUpdate: true,\n      excludeFunctionCall: true,\n      excludeHandoff: true,\n      excludeEmptyMessage: true,\n    });\n  }\n\n  /** @internal exposed for tests */\n  async runOnce(chatCtx: ChatContext, abortSignal?: AbortSignal): Promise<void> {\n    if (!(this.llm instanceof LLM)) {\n      return;\n    }\n\n    // show static terms as applied too, or the LLM keeps re-proposing them\n    const current: [string, boolean][] = [\n      ...this.staticTerms.map((t): [string, boolean] => [t, true]),\n      ...this._detectedTerms.map((t): [string, boolean] => [t, true]),\n      ...[...this._pendingTerms.keys()].map((t): [string, boolean] => [t, false]),\n    ];\n    const [pending, confirm, remove] = await detectKeyterms(this.llm, chatCtx, {\n      currentKeyterms: current,\n      instructions: this.instructions,\n      timeout: this.detectionTimeout,\n      abortSignal,\n    });\n\n    // cancelled mid-flight (e.g. activity shutdown): don't touch keyterm state\n    if (abortSignal?.aborted) {\n      return;\n    }\n\n    const before = this.keyterms;\n    this.tick += 1;\n\n    // update the keyterm state\n    for (const term of remove) {\n      this._pendingTerms.delete(term);\n      const idx = this._detectedTerms.indexOf(term);\n      if (idx !== -1) {\n        this._detectedTerms.splice(idx, 1);\n      }\n    }\n\n    for (const term of pending) {\n      // track a new candidate; ignore static terms and ones already known\n      if (term && !this.staticTerms.includes(term)) {\n        if (!this._detectedTerms.includes(term) && !this._pendingTerms.has(term)) {\n          this._pendingTerms.set(term, this.tick);\n        }\n      }\n    }\n\n    for (const term of confirm) {\n      if (term && !this.staticTerms.includes(term)) {\n        this._pendingTerms.delete(term); // promote out of pending\n        if (!this._detectedTerms.includes(term)) {\n          this._detectedTerms.push(term);\n        }\n      }\n    }\n\n    // drop pending terms that were never confirmed in time\n    for (const [term, addedAt] of [...this._pendingTerms.entries()]) {\n      if (this.tick - addedAt >= PENDING_TTL) {\n        this._pendingTerms.delete(term);\n      }\n    }\n\n    // evict oldest confirmed terms if over the cap\n    if (this.maxKeyterms !== undefined) {\n      while (this._detectedTerms.length > this.maxKeyterms) {\n        this._detectedTerms.shift();\n      }\n    }\n\n    // update the STT if the keyterms changed\n    const newKeyterms = this.keyterms;\n    if (\n      this.stt !== undefined &&\n      (newKeyterms.length !== before.length || newKeyterms.some((t, i) => t !== before[i]))\n    ) {\n      this.stt._updateSessionKeyterms(newKeyterms);\n      const beforeSet = new Set(before);\n      const newSet = new Set(newKeyterms);\n      this.#logger\n        .child({\n          added: newKeyterms.filter((t) => !beforeSet.has(t)),\n          removed: before.filter((t) => !newSet.has(t)),\n        })\n        .debug('keyterms changed');\n    }\n  }\n}\n\n/**\n * Run one extraction pass via a forced function call.\n *\n * Returns `[pending, confirm, remove]`.\n *\n * @internal exposed for tests\n */\nexport async function detectKeyterms(\n  llm: LLM,\n  chatCtx: ChatContext,\n  opts?: {\n    instructions?: string;\n    currentKeyterms?: [string, boolean][];\n    timeout?: number;\n    abortSignal?: AbortSignal;\n  },\n): Promise<[string[], string[], string[]]> {\n  const current = opts?.currentKeyterms ?? [];\n  const timeout = opts?.timeout ?? DETECTION_TIMEOUT;\n  const abortSignal = opts?.abortSignal;\n  if (abortSignal?.aborted) {\n    return [[], [], []];\n  }\n  const userMsg = formatInput(chatCtx, current);\n  if (userMsg === undefined) {\n    // no transcript yet — nothing to detect\n    return [[], [], []];\n  }\n  const reqCtx = ChatContext.empty();\n  reqCtx.addMessage({\n    role: 'system',\n    content: opts?.instructions ?? DEFAULT_KEYTERM_INSTRUCTIONS,\n  });\n  reqCtx.addMessage({ role: 'user', content: userMsg });\n\n  const stream = llm.chat({ chatCtx: reqCtx, toolCtx: [recordKeyterms], toolChoice: 'required' });\n  const timedOut = Symbol('keyterm-detection-timeout');\n  const timeoutController = new AbortController();\n  const timeoutPromise: Promise<typeof timedOut> = delay(timeout, {\n    signal: timeoutController.signal,\n  }).then(\n    () => timedOut,\n    () => timedOut, // aborted: the collect() already won the race\n  );\n  // cancellation parity with Python: closing the stream ends its output queue,\n  // which unblocks collect() immediately instead of waiting out the LLM request\n  const onAbort = () => stream.close();\n  abortSignal?.addEventListener('abort', onAbort, { once: true });\n  let raced: Awaited<ReturnType<typeof stream.collect>> | typeof timedOut;\n  try {\n    raced = await Promise.race([stream.collect(), timeoutPromise]);\n  } finally {\n    timeoutController.abort();\n    abortSignal?.removeEventListener('abort', onAbort);\n  }\n  if (abortSignal?.aborted) {\n    return [[], [], []];\n  }\n  if (raced === timedOut) {\n    stream.close();\n    log().warn(`keyterm detection: pass timed out after ${timeout}ms; skipping`);\n    return [[], [], []];\n  }\n  const result = parseToolCall(raced.toolCalls);\n\n  if (lkKeytermsDebug) {\n    debugDump(userMsg, result);\n  }\n  return result;\n}\n\n/**\n * Render the detector's user message: recent transcript + current keyterms.\n *\n * Returns `undefined` when the transcript holds no user/assistant text yet.\n *\n * @internal exposed for tests\n */\nexport function formatInput(\n  chatCtx: ChatContext,\n  currentKeyterms: [string, boolean][],\n): string | undefined {\n  // walk newest-first and stop once we have enough, then restore chronological order\n  const turns: string[] = [];\n  for (let i = chatCtx.items.length - 1; i >= 0; i--) {\n    const item = chatCtx.items[i]!;\n    if (!(item instanceof ChatMessage) || (item.role !== 'user' && item.role !== 'assistant')) {\n      continue;\n    }\n    const text = item.textContent;\n    if (text) {\n      // keep the message's line structure but drop blank lines, so the blank line\n      // between turns is the only blank line and reliably marks a turn boundary\n      const body = text\n        .split('\\n')\n        .filter((line) => line.trim())\n        .join('\\n');\n      turns.push(`${item.role.toUpperCase()}: ${body}`);\n      if (turns.length >= MAX_TRANSCRIPT_MESSAGES) {\n        break;\n      }\n    }\n  }\n  if (turns.length === 0) {\n    return undefined;\n  }\n  turns.reverse();\n\n  const applied = currentKeyterms.filter(([, ok]) => ok).map(([term]) => term);\n  const candidates = currentKeyterms.filter(([, ok]) => !ok).map(([term]) => term);\n  // always show both lists (even empty) so the model has explicit state to diff against\n  const sections = [\n    '## Transcript (USER = raw STT, may be wrong; ASSISTANT = correct spelling)\\n' +\n      turns.join('\\n\\n'), // blank line between turns\n    '## Applied keyterms (biasing the recognizer now)\\n' + (applied.join(', ') || '(none)'),\n    '## Candidate keyterms (seen, not yet applied)\\n' + (candidates.join(', ') || '(none)'),\n    'Update the keyterms from the latest turns, then call `record_keyterms` once.',\n  ];\n  return sections.join('\\n\\n'); // blank line + ## heading between sections\n}\n\n/**\n * Parse the `record_keyterms` tool call into `[pending, confirm, remove]`.\n *\n * @internal exposed for tests\n */\nexport function parseToolCall(toolCalls: FunctionCall[]): [string[], string[], string[]] {\n  const fnc = toolCalls.find((c) => c.name === 'record_keyterms');\n  if (fnc === undefined) {\n    return [[], [], []];\n  }\n  let data: Record<string, unknown>;\n  try {\n    data = JSON.parse(fnc.args);\n  } catch {\n    return [[], [], []];\n  }\n\n  const terms = (key: string): string[] => {\n    const value = data[key];\n    if (!Array.isArray(value)) {\n      return [];\n    }\n    return value.filter((t): t is string => typeof t === 'string' && t.trim().length > 0);\n  };\n\n  return [terms('pending'), terms('confirm'), terms('remove')];\n}\n\n/** Log the input/output of one detection pass (gated by `LK_KEYTERMS_DEBUG`). */\nfunction debugDump(userMsg: string, result: [string[], string[], string[]]): void {\n  const [pending, confirm, remove] = result;\n  log().debug(\n    {\n      'lk.pii.user_message': userMsg,\n      'lk.pii.pending_keyterms': pending,\n      'lk.pii.confirmed_keyterms': confirm,\n      'lk.pii.removed_keyterms': remove,\n    },\n    'keyterm detection result',\n  );\n}\n"],"mappings":"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","names":["import_llm","InferenceLLM"]}