@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.
@@ -38,6 +38,8 @@ interface EmbeddingModelIdByProvider {
38
38
  interface Question {
39
39
  /** The question text */
40
40
  question: string;
41
+ /** Allowed answers for this question (defaults to ["yes", "no"]). */
42
+ answerOptions?: string[];
41
43
  }
42
44
  /** A single answer to a question. */
43
45
  interface QuestionAnswer {
@@ -60,8 +62,6 @@ interface AskQuestionsOptions extends MuxAIOptions {
60
62
  model?: ModelIdByProvider[SupportedProvider];
61
63
  /** BCP 47 language code of the caption track to use (e.g. "en", "fr"). When omitted, prefers English if available. */
62
64
  languageCode?: string;
63
- /** Allowed answers for each question (defaults to ["yes", "no"]). */
64
- answerOptions?: string[];
65
65
  /** Fetch transcript alongside storyboard (defaults to true, required for audio-only assets). */
66
66
  includeTranscript?: boolean;
67
67
  /** Strip timestamps/markup from transcripts (defaults to true). */
package/dist/index.d.ts CHANGED
@@ -2,14 +2,14 @@ import { W as WorkflowCredentials, S as StoragePutObjectInput, a as StoragePresi
2
2
  export { A as AssetTextTrack, C as ChunkEmbedding, b as ChunkingStrategy, E as Encrypted, c as EncryptedPayload, I as ImageSubmissionMode, M as MuxAIOptions, d as MuxAsset, P as PlaybackAsset, e as PlaybackPolicy, f as StorageAdapter, T as TextChunk, g as TokenChunkingConfig, h as TokenUsage, i as ToneType, U as UsageMetadata, V as VTTChunkingConfig, j as VideoEmbeddingsResult, k as WorkflowCredentialsInput, l as WorkflowMuxClient, m as decryptFromWorkflow, n as encryptForWorkflow } from './shots-BFYOfBgN.js';
3
3
  import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@workflow/serde';
4
4
  export { i as primitives } from './index-CuBu_u5-.js';
5
- export { i as workflows } from './index-yn3fUDgv.js';
5
+ export { i as workflows } from './index-CXC5gAHs.js';
6
6
  import '@mux/mux-node';
7
7
  import 'zod';
8
8
  import '@ai-sdk/anthropic';
9
9
  import '@ai-sdk/google';
10
10
  import '@ai-sdk/openai';
11
11
 
12
- var version = "0.15.1";
12
+ var version = "0.16.0";
13
13
 
14
14
  type MuxAiErrorType = "validation_error" | "processing_error" | "timeout_error";
15
15
  /**
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // package.json
8
- var version = "0.15.1";
8
+ var version = "0.16.0";
9
9
 
10
10
  // src/lib/mux-ai-error.ts
11
11
  var MuxAiError = class extends Error {
@@ -13,7 +13,7 @@ var MuxAiError = class extends Error {
13
13
  super(message);
14
14
  /** Structural brand for duck-type identification. */
15
15
  this.__robots_error = true;
16
- this.name = "MuxAiError";
16
+ this.name = "FatalError";
17
17
  this.publicType = opts?.type ?? "processing_error";
18
18
  this.publicMessage = message;
19
19
  this.retryable = opts?.retryable ?? false;
@@ -2160,7 +2160,7 @@ function renderSection(section) {
2160
2160
  throw new Error(`Invalid XML ${context} name: "${name}"`);
2161
2161
  }
2162
2162
  };
2163
- const escapeXmlText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;/");
2163
+ const escapeXmlText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2164
2164
  const escapeXmlAttribute = (value) => escapeXmlText(value).replace(/"/g, "&quot;");
2165
2165
  if (!content.trim()) {
2166
2166
  return "";
@@ -2273,7 +2273,7 @@ async function withRetry(fn, {
2273
2273
 
2274
2274
  // src/workflows/ask-questions.ts
2275
2275
  var questionAnswerSchema = z2.object({
2276
- question: z2.string(),
2276
+ question: z2.string().describe("The full text of the original question"),
2277
2277
  answer: z2.string().nullable(),
2278
2278
  confidence: z2.number(),
2279
2279
  reasoning: z2.string(),
@@ -2319,12 +2319,14 @@ var SYSTEM_PROMPT = dedent`
2319
2319
  <task>
2320
2320
  For each question provided, you must:
2321
2321
  1. Analyze the storyboard frames and transcript (if provided)
2322
- 2. Answer with ONLY the allowed response options - no other values are acceptable
2322
+ 2. Answer with ONLY the allowed response options listed for that question - no other values are acceptable
2323
2323
  3. Provide a confidence score between 0 and 1 reflecting your certainty
2324
2324
  4. Explain your reasoning based on observable evidence
2325
2325
  </task>
2326
2326
 
2327
2327
  <answer_guidelines>
2328
+ - Each question is presented as its own <question> block with a <text> element (the question) and an <allowed_answers> element (the permitted response values)
2329
+ - Choose only from the values listed in that question's <allowed_answers> element
2328
2330
  - Choose the affirmative option only if you have clear evidence supporting it
2329
2331
  - Choose the negative/contradicting option if evidence contradicts or if insufficient evidence exists
2330
2332
  - Confidence should reflect the clarity and strength of evidence:
@@ -2372,7 +2374,7 @@ var SYSTEM_PROMPT = dedent`
2372
2374
  </relevance_filtering>
2373
2375
 
2374
2376
  <constraints>
2375
- - You MUST answer every relevant question with one of the allowed response options
2377
+ - You MUST answer every relevant question with one of its own listed allowed response options
2376
2378
  - Skip irrelevant questions as described in relevance_filtering
2377
2379
  - Only describe observable evidence from frames or transcript
2378
2380
  - Do not fabricate details or make unsupported assumptions
@@ -2407,12 +2409,14 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
2407
2409
  <task>
2408
2410
  For each question provided, you must:
2409
2411
  1. Analyze the transcript
2410
- 2. Answer with ONLY the allowed response options - no other values are acceptable
2412
+ 2. Answer with ONLY the allowed response options listed for that question - no other values are acceptable
2411
2413
  3. Provide a confidence score between 0 and 1 reflecting your certainty
2412
2414
  4. Explain your reasoning based on observable evidence
2413
2415
  </task>
2414
2416
 
2415
2417
  <answer_guidelines>
2418
+ - Each question is presented as its own <question> block with a <text> element (the question) and an <allowed_answers> element (the permitted response values)
2419
+ - Choose only from the values listed in that question's <allowed_answers> element
2416
2420
  - Choose the affirmative option only if you have clear evidence supporting it
2417
2421
  - Choose the negative/contradicting option if evidence contradicts or if insufficient evidence exists
2418
2422
  - Confidence should reflect the clarity and strength of evidence:
@@ -2460,7 +2464,7 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
2460
2464
  </relevance_filtering>
2461
2465
 
2462
2466
  <constraints>
2463
- - You MUST answer every relevant question with one of the allowed response options
2467
+ - You MUST answer every relevant question with one of its own listed allowed response options
2464
2468
  - Skip irrelevant questions as described in relevance_filtering
2465
2469
  - Only describe observable evidence from transcript content
2466
2470
  - Do not fabricate details or make unsupported assumptions
@@ -2475,46 +2479,43 @@ var AUDIO_ONLY_SYSTEM_PROMPT = dedent`
2475
2479
  - GOOD: "The speaker describes running through a park"
2476
2480
  - Be specific and evidence-based
2477
2481
  </language_guidelines>`;
2478
- var askQuestionsPromptBuilder = createPromptBuilder({
2479
- template: {
2480
- questions: {
2481
- tag: "questions",
2482
- content: "Please answer the following questions about this content:"
2483
- }
2484
- },
2485
- sectionOrder: ["questions"]
2486
- });
2487
2482
  function buildUserPrompt({
2488
2483
  questions,
2489
- allowedAnswers,
2490
2484
  transcriptText,
2491
2485
  isCleanTranscript = true,
2492
2486
  isAudioOnly = false
2493
2487
  }) {
2494
- const questionsList = questions.map((q, idx) => `${idx + 1}. ${q.question}`).join("\n");
2495
- const questionsIntro = isAudioOnly ? "Please answer the following questions about this audio content using transcript evidence:" : "Please answer the following questions about this content:";
2496
- const questionsContent = dedent`
2497
- ${questionsIntro}
2498
-
2499
- ${questionsList}`;
2500
- const answerList = allowedAnswers.map((answer) => `"${answer}"`).join(", ");
2501
- const responseOptions = dedent`
2502
- <response_options>
2503
- Allowed answers: ${answerList}
2504
- </response_options>`;
2505
- const questionsSection = askQuestionsPromptBuilder.build({ questions: questionsContent });
2506
- if (!transcriptText) {
2507
- return `${questionsSection}
2508
-
2509
- ${responseOptions}`;
2488
+ const contentDescriptor = isAudioOnly ? "audio content using transcript evidence" : "content";
2489
+ const taskContent = dedent`
2490
+ Answer each question in the <questions> block below about the ${contentDescriptor}.
2491
+ Use only the values listed inside each question's own <allowed_answers> element.
2492
+ Return one answer per question, in the order the questions appear.
2493
+ If a question cannot be answered from the provided content, skip it as described in the system instructions.`;
2494
+ const taskSection = `<task>
2495
+ ${taskContent}
2496
+ </task>`;
2497
+ const questionBlocks = questions.map((q, idx) => {
2498
+ const textTag = renderSection({ tag: "text", content: q.question });
2499
+ const answersTag = renderSection({
2500
+ tag: "allowed_answers",
2501
+ content: q.answerOptions.map((a) => `"${a}"`).join(", ")
2502
+ });
2503
+ return `<question number="${idx + 1}">
2504
+ ${textTag}
2505
+ ${answersTag}
2506
+ </question>`;
2507
+ }).join("\n\n");
2508
+ const questionsSection = `<questions>
2509
+ ${questionBlocks}
2510
+ </questions>`;
2511
+ const sections = [];
2512
+ if (transcriptText) {
2513
+ const format = isCleanTranscript ? "plain text" : "WebVTT";
2514
+ sections.push(renderSection(createTranscriptSection(transcriptText, format)));
2510
2515
  }
2511
- const format = isCleanTranscript ? "plain text" : "WebVTT";
2512
- const transcriptSection = renderSection(createTranscriptSection(transcriptText, format));
2513
- return `${transcriptSection}
2514
-
2515
- ${questionsSection}
2516
-
2517
- ${responseOptions}`;
2516
+ sections.push(taskSection);
2517
+ sections.push(questionsSection);
2518
+ return sections.join("\n\n");
2518
2519
  }
2519
2520
  async function fetchImageAsBase64(imageUrl, imageDownloadOptions) {
2520
2521
  "use step";
@@ -2583,7 +2584,6 @@ async function askQuestions(assetId, questions, options) {
2583
2584
  provider = "openai",
2584
2585
  model,
2585
2586
  languageCode,
2586
- answerOptions,
2587
2587
  includeTranscript = true,
2588
2588
  cleanTranscript = true,
2589
2589
  imageSubmissionMode = "url",
@@ -2591,15 +2591,23 @@ async function askQuestions(assetId, questions, options) {
2591
2591
  storyboardWidth = 640,
2592
2592
  credentials
2593
2593
  } = options ?? {};
2594
- const normalizedAnswerOptions = Array.from(
2595
- new Set(
2596
- (answerOptions?.length ? answerOptions : ["yes", "no"]).map((option) => option.trim()).filter(Boolean)
2597
- )
2594
+ const normalizedQuestions = questions.map((q, idx) => {
2595
+ const options2 = Array.from(
2596
+ new Set(
2597
+ (q.answerOptions?.length ? q.answerOptions : ["yes", "no"]).map((o) => o.trim()).filter(Boolean)
2598
+ )
2599
+ );
2600
+ if (!options2.length) {
2601
+ throw new MuxAiError(
2602
+ `Question at index ${idx} has invalid answerOptions: must include at least one non-empty value.`,
2603
+ { type: "validation_error" }
2604
+ );
2605
+ }
2606
+ return { ...q, answerOptions: options2 };
2607
+ });
2608
+ const allowedAnswers = Array.from(
2609
+ new Set(normalizedQuestions.flatMap((q) => q.answerOptions))
2598
2610
  );
2599
- if (!normalizedAnswerOptions.length) {
2600
- throw new MuxAiError("answerOptions must include at least one non-empty value.", { type: "validation_error" });
2601
- }
2602
- const allowedAnswers = normalizedAnswerOptions;
2603
2611
  const modelConfig = resolveLanguageModelConfig({
2604
2612
  ...options,
2605
2613
  model,
@@ -2630,8 +2638,7 @@ async function askQuestions(assetId, questions, options) {
2630
2638
  }) : void 0;
2631
2639
  const transcriptText = transcriptResult?.transcriptText ?? "";
2632
2640
  const userPrompt = buildUserPrompt({
2633
- questions,
2634
- allowedAnswers,
2641
+ questions: normalizedQuestions,
2635
2642
  transcriptText,
2636
2643
  isCleanTranscript: cleanTranscript,
2637
2644
  isAudioOnly
@@ -2694,10 +2701,15 @@ async function askQuestions(assetId, questions, options) {
2694
2701
  `Failed to generate answers for all questions for asset ${assetId}.`
2695
2702
  );
2696
2703
  }
2697
- const answers = analysisResponse.result.answers.map((raw) => {
2704
+ const answers = analysisResponse.result.answers.map((raw, idx) => {
2698
2705
  const isSkipped = raw.skipped || raw.answer === SKIP_SENTINEL;
2706
+ if (!isSkipped && !normalizedQuestions[idx].answerOptions.includes(raw.answer)) {
2707
+ throw new MuxAiError(
2708
+ `Answer "${raw.answer}" for question ${idx} is not in allowed options: ${normalizedQuestions[idx].answerOptions.join(", ")}`
2709
+ );
2710
+ }
2699
2711
  return {
2700
- // Strip numbering prefix (e.g., "1. " or "2. ") from questions
2712
+ // Strip numbering prefix (e.g. "1. ") if the LLM prepends one
2701
2713
  question: raw.question.replace(/^\d+\.\s*/, ""),
2702
2714
  confidence: isSkipped ? 0 : Math.min(1, Math.max(0, raw.confidence)),
2703
2715
  reasoning: raw.reasoning,