Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summarization & Action items missing. Can add in a follow up.


## [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)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
24 changes: 24 additions & 0 deletions src/types/openapi.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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;
};

/**
Expand All @@ -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;
};

/**
Expand All @@ -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;
};

/**
Expand Down
147 changes: 147 additions & 0 deletions tests/unit/speech-understanding-effort.test.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
Loading