@mux/ai 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- export { A as AskQuestionsOptions, a as AskQuestionsResult, b as AskQuestionsType, c as AudioTranslationOptions, d as AudioTranslationResult, e as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, f as BurnedInCaptionsOptions, g as BurnedInCaptionsPromptOverrides, h as BurnedInCaptionsPromptSections, j as BurnedInCaptionsResult, C as CaptionReplacement, k as CensorMode, l as Chapter, m as ChapterSystemPromptSections, n as ChaptersOptions, o as ChaptersPromptOverrides, p as ChaptersPromptSections, q as ChaptersResult, r as ChaptersType, D as DEFAULT_DESCRIPTION_LENGTH, s as DEFAULT_SUMMARY_KEYWORD_LIMIT, t as DEFAULT_TITLE_LENGTH, E as EditCaptionsOptions, u as EditCaptionsResult, v as EmbeddingsOptions, w as EmbeddingsResult, x as EngagementInsightsOptions, y as EngagementInsightsResult, H as HIVE_SEXUAL_CATEGORIES, z as HIVE_VIOLENCE_CATEGORIES, F as HiveModerationOutput, G as HiveModerationSource, M as ModerationOptions, I as ModerationProvider, J as ModerationResult, K as MomentInsight, O as OverallInsight, P as ProfanityDetectionPayload, Q as Question, L as QuestionAnswer, N as QuestionAnswerType, R as ReplacementRecord, S as SummarizationOptions, T as SummarizationPromptOverrides, U as SummarizationPromptSections, V as SummaryAndTagsResult, W as SummaryType, X as ThumbnailModerationScore, Y as TranslationChunkingOptions, Z as TranslationOptions, _ as TranslationPayload, $ as TranslationResult, a0 as aggregateTokenUsage, a1 as applyOverrideLists, a2 as applyReplacements, a3 as askQuestions, a4 as buildReplacementRegex, a5 as burnedInCaptionsSchema, a6 as censorVttContent, a7 as chapterSchema, a8 as chaptersSchema, a9 as createReplacer, aa as editCaptions, ab as extractTranscriptSegmentsForHotspots, ac as generateChapters, ad as generateEmbeddings, ae as generateEngagementInsights, af as generateVideoEmbeddings, ag as getModerationScores, ah as getSummaryAndTags, ai as hasBurnedInCaptions, aj as mapShotsToHotspots, ak as profanityDetectionSchema, al as questionAnswerSchema, am as shouldSplitChunkTranslationError, an as summarySchema, ao as transformCueText, ap as translateAudio, aq as translateCaptions, ar as translationSchema } from '../index-yn3fUDgv.js';
1
+ export { A as AskQuestionsOptions, a as AskQuestionsResult, b as AskQuestionsType, c as AudioTranslationOptions, d as AudioTranslationResult, e as AutoCensorProfanityOptions, B as BurnedInCaptionsAnalysis, f as BurnedInCaptionsOptions, g as BurnedInCaptionsPromptOverrides, h as BurnedInCaptionsPromptSections, j as BurnedInCaptionsResult, C as CaptionReplacement, k as CensorMode, l as Chapter, m as ChapterSystemPromptSections, n as ChaptersOptions, o as ChaptersPromptOverrides, p as ChaptersPromptSections, q as ChaptersResult, r as ChaptersType, D as DEFAULT_DESCRIPTION_LENGTH, s as DEFAULT_SUMMARY_KEYWORD_LIMIT, t as DEFAULT_TITLE_LENGTH, E as EditCaptionsOptions, u as EditCaptionsResult, v as EmbeddingsOptions, w as EmbeddingsResult, x as EngagementInsightsOptions, y as EngagementInsightsResult, H as HIVE_SEXUAL_CATEGORIES, z as HIVE_VIOLENCE_CATEGORIES, F as HiveModerationOutput, G as HiveModerationSource, M as ModerationOptions, I as ModerationProvider, J as ModerationResult, K as MomentInsight, O as OverallInsight, P as ProfanityDetectionPayload, Q as Question, L as QuestionAnswer, N as QuestionAnswerType, R as ReplacementRecord, S as SummarizationOptions, T as SummarizationPromptOverrides, U as SummarizationPromptSections, V as SummaryAndTagsResult, W as SummaryType, X as ThumbnailModerationScore, Y as TranslationChunkingOptions, Z as TranslationOptions, _ as TranslationPayload, $ as TranslationResult, a0 as aggregateTokenUsage, a1 as applyOverrideLists, a2 as applyReplacements, a3 as askQuestions, a4 as buildReplacementRegex, a5 as burnedInCaptionsSchema, a6 as censorVttContent, a7 as chapterSchema, a8 as chaptersSchema, a9 as createReplacer, aa as editCaptions, ab as extractTranscriptSegmentsForHotspots, ac as generateChapters, ad as generateEmbeddings, ae as generateEngagementInsights, af as generateVideoEmbeddings, ag as getModerationScores, ah as getSummaryAndTags, ai as hasBurnedInCaptions, aj as mapShotsToHotspots, ak as profanityDetectionSchema, al as questionAnswerSchema, am as shouldSplitChunkTranslationError, an as summarySchema, ao as transformCueText, ap as translateAudio, aq as translateCaptions, ar as translationSchema } from '../index-CXC5gAHs.js';
2
2
  import 'zod';
3
3
  import '@ai-sdk/anthropic';
4
4
  import '@ai-sdk/google';
@@ -116,7 +116,7 @@ var MuxAiError = class extends Error {
116
116
  super(message);
117
117
  /** Structural brand for duck-type identification. */
118
118
  this.__robots_error = true;
119
- this.name = "MuxAiError";
119
+ this.name = "FatalError";
120
120
  this.publicType = opts?.type ?? "processing_error";
121
121
  this.publicMessage = message;
122
122
  this.retryable = opts?.retryable ?? false;
@@ -794,7 +794,7 @@ function renderSection(section) {
794
794
  throw new Error(`Invalid XML ${context} name: "${name}"`);
795
795
  }
796
796
  };
797
- const escapeXmlText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;/");
797
+ const escapeXmlText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
798
798
  const escapeXmlAttribute = (value) => escapeXmlText(value).replace(/"/g, "&quot;");
799
799
  if (!content.trim()) {
800
800
  return "";
@@ -1342,7 +1342,7 @@ async function fetchTranscriptForAsset(asset, playbackId, options = {}) {
1342
1342
 
1343
1343
  // src/workflows/ask-questions.ts
1344
1344
  var questionAnswerSchema = z2.object({
1345
- question: z2.string(),
1345
+ question: z2.string().describe("The full text of the original question"),
1346
1346
  answer: z2.string().nullable(),
1347
1347
  confidence: z2.number(),
1348
1348
  reasoning: z2.string(),
@@ -1388,12 +1388,14 @@ var SYSTEM_PROMPT = dedent`
1388
1388
  <task>
1389
1389
  For each question provided, you must:
1390
1390
  1. Analyze the storyboard frames and transcript (if provided)
1391
- 2. Answer with ONLY the allowed response options - no other values are acceptable
1391
+ 2. Answer with ONLY the allowed response options listed for that question - no other values are acceptable
1392
1392
  3. Provide a confidence score between 0 and 1 reflecting your certainty
1393
1393
  4. Explain your reasoning based on observable evidence
1394
1394
  </task>
1395
1395
 
1396
1396
  <answer_guidelines>
1397
+ - Each question is presented as its own <question> block with a <text> element (the question) and an <allowed_answers> element (the permitted response values)
1398
+ - Choose only from the values listed in that question's <allowed_answers> element
1397
1399
  - Choose the affirmative option only if you have clear evidence supporting it
1398
1400
  - Choose the negative/contradicting option if evidence contradicts or if insufficient evidence exists
1399
1401
  - Confidence should reflect the clarity and strength of evidence:
@@ -1441,7 +1443,7 @@ var SYSTEM_PROMPT = dedent`
1441
1443
  </relevance_filtering>
1442
1444
 
1443
1445
  <constraints>
1444
- - You MUST answer every relevant question with one of the allowed response options
1446
+ - You MUST answer every relevant question with one of its own listed allowed response options
1445
1447
  - Skip irrelevant questions as described in relevance_filtering
1446
1448
  - Only describe observable evidence from frames or transcript
1447
1449
  - Do not fabricate details or make unsupported assumptions
@@ -1476,12 +1478,14 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1476
1478
  <task>
1477
1479
  For each question provided, you must:
1478
1480
  1. Analyze the transcript
1479
- 2. Answer with ONLY the allowed response options - no other values are acceptable
1481
+ 2. Answer with ONLY the allowed response options listed for that question - no other values are acceptable
1480
1482
  3. Provide a confidence score between 0 and 1 reflecting your certainty
1481
1483
  4. Explain your reasoning based on observable evidence
1482
1484
  </task>
1483
1485
 
1484
1486
  <answer_guidelines>
1487
+ - Each question is presented as its own <question> block with a <text> element (the question) and an <allowed_answers> element (the permitted response values)
1488
+ - Choose only from the values listed in that question's <allowed_answers> element
1485
1489
  - Choose the affirmative option only if you have clear evidence supporting it
1486
1490
  - Choose the negative/contradicting option if evidence contradicts or if insufficient evidence exists
1487
1491
  - Confidence should reflect the clarity and strength of evidence:
@@ -1529,7 +1533,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1529
1533
  </relevance_filtering>
1530
1534
 
1531
1535
  <constraints>
1532
- - You MUST answer every relevant question with one of the allowed response options
1536
+ - You MUST answer every relevant question with one of its own listed allowed response options
1533
1537
  - Skip irrelevant questions as described in relevance_filtering
1534
1538
  - Only describe observable evidence from transcript content
1535
1539
  - Do not fabricate details or make unsupported assumptions
@@ -1544,46 +1548,43 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
1544
1548
  - GOOD: "The speaker describes running through a park"
1545
1549
  - Be specific and evidence-based
1546
1550
  </language_guidelines>`;
1547
- var askQuestionsPromptBuilder = createPromptBuilder({
1548
- template: {
1549
- questions: {
1550
- tag: "questions",
1551
- content: "Please answer the following questions about this content:"
1552
- }
1553
- },
1554
- sectionOrder: ["questions"]
1555
- });
1556
1551
  function buildUserPrompt({
1557
1552
  questions,
1558
- allowedAnswers,
1559
1553
  transcriptText,
1560
1554
  isCleanTranscript = true,
1561
1555
  isAudioOnly = false
1562
1556
  }) {
1563
- const questionsList = questions.map((q, idx) => `${idx + 1}. ${q.question}`).join("\n");
1564
- const questionsIntro = isAudioOnly ? "Please answer the following questions about this audio content using transcript evidence:" : "Please answer the following questions about this content:";
1565
- const questionsContent = dedent`
1566
- ${questionsIntro}
1567
-
1568
- ${questionsList}`;
1569
- const answerList = allowedAnswers.map((answer) => `"${answer}"`).join(", ");
1570
- const responseOptions = dedent`
1571
- <response_options>
1572
- Allowed answers: ${answerList}
1573
- </response_options>`;
1574
- const questionsSection = askQuestionsPromptBuilder.build({ questions: questionsContent });
1575
- if (!transcriptText) {
1576
- return `${questionsSection}
1577
-
1578
- ${responseOptions}`;
1557
+ const contentDescriptor = isAudioOnly ? "audio content using transcript evidence" : "content";
1558
+ const taskContent = dedent`
1559
+ Answer each question in the <questions> block below about the ${contentDescriptor}.
1560
+ Use only the values listed inside each question's own <allowed_answers> element.
1561
+ Return one answer per question, in the order the questions appear.
1562
+ If a question cannot be answered from the provided content, skip it as described in the system instructions.`;
1563
+ const taskSection = `<task>
1564
+ ${taskContent}
1565
+ </task>`;
1566
+ const questionBlocks = questions.map((q, idx) => {
1567
+ const textTag = renderSection({ tag: "text", content: q.question });
1568
+ const answersTag = renderSection({
1569
+ tag: "allowed_answers",
1570
+ content: q.answerOptions.map((a) => `"${a}"`).join(", ")
1571
+ });
1572
+ return `<question number="${idx + 1}">
1573
+ ${textTag}
1574
+ ${answersTag}
1575
+ </question>`;
1576
+ }).join("\n\n");
1577
+ const questionsSection = `<questions>
1578
+ ${questionBlocks}
1579
+ </questions>`;
1580
+ const sections = [];
1581
+ if (transcriptText) {
1582
+ const format = isCleanTranscript ? "plain text" : "WebVTT";
1583
+ sections.push(renderSection(createTranscriptSection(transcriptText, format)));
1579
1584
  }
1580
- const format = isCleanTranscript ? "plain text" : "WebVTT";
1581
- const transcriptSection = renderSection(createTranscriptSection(transcriptText, format));
1582
- return `${transcriptSection}
1583
-
1584
- ${questionsSection}
1585
-
1586
- ${responseOptions}`;
1585
+ sections.push(taskSection);
1586
+ sections.push(questionsSection);
1587
+ return sections.join("\n\n");
1587
1588
  }
1588
1589
  async function fetchImageAsBase64(imageUrl, imageDownloadOptions) {
1589
1590
  "use step";
@@ -1652,7 +1653,6 @@ async function askQuestions(assetId, questions, options) {
1652
1653
  provider = "openai",
1653
1654
  model,
1654
1655
  languageCode,
1655
- answerOptions,
1656
1656
  includeTranscript = true,
1657
1657
  cleanTranscript = true,
1658
1658
  imageSubmissionMode = "url",
@@ -1660,15 +1660,23 @@ async function askQuestions(assetId, questions, options) {
1660
1660
  storyboardWidth = 640,
1661
1661
  credentials
1662
1662
  } = options ?? {};
1663
- const normalizedAnswerOptions = Array.from(
1664
- new Set(
1665
- (answerOptions?.length ? answerOptions : ["yes", "no"]).map((option) => option.trim()).filter(Boolean)
1666
- )
1663
+ const normalizedQuestions = questions.map((q, idx) => {
1664
+ const options2 = Array.from(
1665
+ new Set(
1666
+ (q.answerOptions?.length ? q.answerOptions : ["yes", "no"]).map((o) => o.trim()).filter(Boolean)
1667
+ )
1668
+ );
1669
+ if (!options2.length) {
1670
+ throw new MuxAiError(
1671
+ `Question at index ${idx} has invalid answerOptions: must include at least one non-empty value.`,
1672
+ { type: "validation_error" }
1673
+ );
1674
+ }
1675
+ return { ...q, answerOptions: options2 };
1676
+ });
1677
+ const allowedAnswers = Array.from(
1678
+ new Set(normalizedQuestions.flatMap((q) => q.answerOptions))
1667
1679
  );
1668
- if (!normalizedAnswerOptions.length) {
1669
- throw new MuxAiError("answerOptions must include at least one non-empty value.", { type: "validation_error" });
1670
- }
1671
- const allowedAnswers = normalizedAnswerOptions;
1672
1680
  const modelConfig = resolveLanguageModelConfig({
1673
1681
  ...options,
1674
1682
  model,
@@ -1699,8 +1707,7 @@ async function askQuestions(assetId, questions, options) {
1699
1707
  }) : void 0;
1700
1708
  const transcriptText = transcriptResult?.transcriptText ?? "";
1701
1709
  const userPrompt = buildUserPrompt({
1702
- questions,
1703
- allowedAnswers,
1710
+ questions: normalizedQuestions,
1704
1711
  transcriptText,
1705
1712
  isCleanTranscript: cleanTranscript,
1706
1713
  isAudioOnly
@@ -1763,10 +1770,15 @@ async function askQuestions(assetId, questions, options) {
1763
1770
  `Failed to generate answers for all questions for asset ${assetId}.`
1764
1771
  );
1765
1772
  }
1766
- const answers = analysisResponse.result.answers.map((raw) => {
1773
+ const answers = analysisResponse.result.answers.map((raw, idx) => {
1767
1774
  const isSkipped = raw.skipped || raw.answer === SKIP_SENTINEL;
1775
+ if (!isSkipped && !normalizedQuestions[idx].answerOptions.includes(raw.answer)) {
1776
+ throw new MuxAiError(
1777
+ `Answer "${raw.answer}" for question ${idx} is not in allowed options: ${normalizedQuestions[idx].answerOptions.join(", ")}`
1778
+ );
1779
+ }
1768
1780
  return {
1769
- // Strip numbering prefix (e.g., "1. " or "2. ") from questions
1781
+ // Strip numbering prefix (e.g. "1. ") if the LLM prepends one
1770
1782
  question: raw.question.replace(/^\d+\.\s*/, ""),
1771
1783
  confidence: isSkipped ? 0 : Math.min(1, Math.max(0, raw.confidence)),
1772
1784
  reasoning: raw.reasoning,