diff --git a/CHANGELOG.md b/CHANGELOG.md index 17af2f3..26cabd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [4.36.6] + +- Add `effort` to the speech understanding feature requests (`SpeakerIdentificationRequest`, `TranslationRequest`, `CustomFormattingRequest`) — `"low"` (default) or `"medium"`, set per task, typed as the new `SpeechUnderstandingEffort`. The field was already accepted by the API but missing from the SDK types, so setting it failed to type check + ## [4.36.4] - Add `aac` to the streaming `encoding` options — accepts an AAC stream in ADTS framing. Like `opus`/`ogg_opus`, AAC is self-describing, so `sampleRate` is optional for it (it remains required for PCM encodings and for dual-channel mode) diff --git a/package.json b/package.json index c14271d..6ba404a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assemblyai", - "version": "4.36.5", + "version": "4.36.6", "description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.", "engines": { "node": ">=18" diff --git a/src/types/openapi.generated.ts b/src/types/openapi.generated.ts index b93d37d..5107e48 100644 --- a/src/types/openapi.generated.ts +++ b/src/types/openapi.generated.ts @@ -1508,6 +1508,12 @@ export type SeverityScoreSummary = { */ export type SpeakerType = "role" | "name"; +/** + * How much processing power to spend on a speech understanding task. 'medium' produces + * higher quality results on harder audio at a higher cost. + */ +export type SpeechUnderstandingEffort = "low" | "medium"; + /** * Speaker identification configuration for speech understanding */ @@ -1520,6 +1526,12 @@ export type SpeakerIdentificationRequest = { * Known speaker values (required when speaker_type is 'role') */ known_values?: string[]; + /** + * How much effort to spend on this task + * + * @defaultValue "low" + */ + effort?: SpeechUnderstandingEffort; }; /** @@ -1538,6 +1550,12 @@ export type TranslationRequest = { * Whether to match the original utterance structure in translations (default: false) */ match_original_utterance?: boolean; + /** + * How much effort to spend on this task + * + * @defaultValue "low" + */ + effort?: SpeechUnderstandingEffort; }; /** @@ -1556,6 +1574,12 @@ export type CustomFormattingRequest = { * Custom email format pattern (e.g., 'username\@domain.com') */ email?: string; + /** + * How much effort to spend on this task + * + * @defaultValue "low" + */ + effort?: SpeechUnderstandingEffort; }; /** diff --git a/tests/unit/speech-understanding-effort.test.ts b/tests/unit/speech-understanding-effort.test.ts new file mode 100644 index 0000000..1a11806 --- /dev/null +++ b/tests/unit/speech-understanding-effort.test.ts @@ -0,0 +1,147 @@ +import fetchMock from "jest-fetch-mock"; +import { + CustomFormattingRequest, + SpeakerIdentificationRequest, + SpeechUnderstandingEffort, + TranslationRequest, +} from "../../src"; +import { createClient, requestMatches } from "./utils"; + +fetchMock.enableMocks(); + +const assembly = createClient(); +const transcriptId = "transcript_123"; +const remoteAudioURL = "https://assembly.ai/espn.m4a"; + +beforeEach(() => { + jest.clearAllMocks(); + fetchMock.resetMocks(); + fetchMock.doMock(); +}); + +const mockSubmit = () => + fetchMock.doMockOnceIf( + requestMatches({ url: "/v2/transcript", method: "POST" }), + JSON.stringify({ id: transcriptId, status: "queued" }), + ); + +const submittedRequest = () => + JSON.parse(fetchMock.mock.calls[0][1]?.body as string).speech_understanding + .request; + +describe("speech understanding effort", () => { + it("should create transcript with speaker_identification effort", async () => { + const speakerIdentification: SpeakerIdentificationRequest = { + speaker_type: "name", + effort: "medium", + }; + mockSubmit(); + + const transcript = await assembly.transcripts.submit({ + audio_url: remoteAudioURL, + speaker_labels: true, + speech_understanding: { + request: { speaker_identification: speakerIdentification }, + }, + }); + + expect(transcript.id).toBe(transcriptId); + expect(transcript.status).toBe("queued"); + expect(submittedRequest().speaker_identification).toEqual( + speakerIdentification, + ); + }); + + it("should create transcript with translation effort", async () => { + const translation: TranslationRequest = { + target_languages: ["es", "fr"], + effort: "medium", + }; + mockSubmit(); + + await assembly.transcripts.submit({ + audio_url: remoteAudioURL, + speech_understanding: { request: { translation } }, + }); + + expect(submittedRequest().translation).toEqual(translation); + }); + + it("should create transcript with custom_formatting effort", async () => { + const customFormatting: CustomFormattingRequest = { + date: "mm/dd/yyyy", + effort: "medium", + }; + mockSubmit(); + + await assembly.transcripts.submit({ + audio_url: remoteAudioURL, + speech_understanding: { + request: { custom_formatting: customFormatting }, + }, + }); + + expect(submittedRequest().custom_formatting).toEqual(customFormatting); + }); + + it("should set effort per task independently", async () => { + mockSubmit(); + + await assembly.transcripts.submit({ + audio_url: remoteAudioURL, + speaker_labels: true, + speech_understanding: { + request: { + speaker_identification: { speaker_type: "name", effort: "medium" }, + translation: { target_languages: ["es"], effort: "low" }, + }, + }, + }); + + const request = submittedRequest(); + expect(request.speaker_identification.effort).toBe("medium"); + expect(request.translation.effort).toBe("low"); + }); + + it("should omit effort when it isn't set", async () => { + mockSubmit(); + + await assembly.transcripts.submit({ + audio_url: remoteAudioURL, + speaker_labels: true, + speech_understanding: { + request: { + speaker_identification: { + speaker_type: "role", + known_values: ["Agent", "Customer"], + }, + }, + }, + }); + + const speakerIdentification = submittedRequest().speaker_identification; + expect(speakerIdentification.speaker_type).toBe("role"); + expect(speakerIdentification.known_values).toEqual(["Agent", "Customer"]); + expect(speakerIdentification.effort).toBeUndefined(); + }); + + it("should accept every documented effort value", () => { + const efforts: SpeechUnderstandingEffort[] = ["low", "medium"]; + + for (const effort of efforts) { + const speakerIdentification: SpeakerIdentificationRequest = { + speaker_type: "name", + effort, + }; + const translation: TranslationRequest = { + target_languages: ["es"], + effort, + }; + const customFormatting: CustomFormattingRequest = { effort }; + + expect(speakerIdentification.effort).toBe(effort); + expect(translation.effort).toBe(effort); + expect(customFormatting.effort).toBe(effort); + } + }); +});